Skip to content

Commit e1e1f82

Browse files
authored
Python 3.12 build support (#221)
* resolve #13 - Python 3.12 build support. * python 3.12 - fix unbound local issue - changed how ts inputs to PyNode are reset to null due to new LOAD_FAST vs LOAD_FAST_CHECK opcodes in Python 3.12. Inject DELETE opcodes into bytecode rather than setting directly to null in c++ * cibuildwheel 2.11.2 -> 2.16.5 Signed-off-by: Rob Ambalu <[email protected]>
1 parent 964c77e commit e1e1f82

File tree

12 files changed

+46
-18
lines changed

12 files changed

+46
-18
lines changed

.github/actions/setup-caches/action.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ inputs:
1010
- 'cp39'
1111
- 'cp310'
1212
- 'cp311'
13+
- 'cp312'
1314
default: 'cp39'
1415

1516
runs:

.github/actions/setup-dependencies/action.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ inputs:
1010
- 'cp39'
1111
- 'cp310'
1212
- 'cp311'
13+
- 'cp312'
1314
default: 'cp39'
1415

1516
runs:

.github/actions/setup-python/action.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ inputs:
1010
- '3.9'
1111
- '3.10'
1212
- '3.11'
13+
- '3.12'
1314
default: '3.9'
1415

1516
runs:
@@ -33,4 +34,4 @@ runs:
3334

3435
- name: Install cibuildwheel and twine
3536
shell: bash
36-
run: pip install cibuildwheel==2.11.2 twine
37+
run: pip install cibuildwheel==2.16.5 twine

.github/workflows/build.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,13 @@ jobs:
179179
- "3.9"
180180
- "3.10"
181181
- "3.11"
182+
- "3.12"
182183
cibuildwheel:
183184
- "cp38"
184185
- "cp39"
185186
- "cp310"
186187
- "cp311"
188+
- "cp312"
187189
is-full-run:
188190
- ${{ needs.initialize.outputs.FULL_RUN == 'true' }}
189191
exclude:
@@ -198,24 +200,41 @@ jobs:
198200
cibuildwheel: "cp310"
199201
- python-version: "3.8"
200202
cibuildwheel: "cp311"
203+
- python-version: "3.8"
204+
cibuildwheel: "cp312"
201205
- python-version: "3.9"
202206
cibuildwheel: "cp38"
203207
- python-version: "3.9"
204208
cibuildwheel: "cp310"
205209
- python-version: "3.9"
206210
cibuildwheel: "cp311"
211+
- python-version: "3.9"
212+
cibuildwheel: "cp312"
207213
- python-version: "3.10"
208214
cibuildwheel: "cp38"
209215
- python-version: "3.10"
210216
cibuildwheel: "cp39"
211217
- python-version: "3.10"
212218
cibuildwheel: "cp311"
219+
- python-version: "3.10"
220+
cibuildwheel: "cp312"
213221
- python-version: "3.11"
214222
cibuildwheel: "cp38"
215223
- python-version: "3.11"
216224
cibuildwheel: "cp39"
217225
- python-version: "3.11"
218226
cibuildwheel: "cp310"
227+
- python-version: "3.11"
228+
cibuildwheel: "cp312"
229+
- python-version: "3.12"
230+
cibuildwheel: "cp38"
231+
- python-version: "3.12"
232+
cibuildwheel: "cp39"
233+
- python-version: "3.12"
234+
cibuildwheel: "cp310"
235+
- python-version: "3.12"
236+
cibuildwheel: "cp311"
237+
219238

220239
##############################################
221240
# Things to exclude if not a full matrix run #
@@ -402,6 +421,7 @@ jobs:
402421
- 3.9
403422
- "3.10"
404423
- 3.11
424+
- 3.12
405425
is-full-run:
406426
- ${{ needs.initialize.outputs.FULL_RUN == 'true' }}
407427
exclude:

.github/workflows/conda.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
- name: Set up Caches
5353
uses: ./.github/actions/setup-caches
5454
with:
55-
cibuildwheel: 'cp311'
55+
cibuildwheel: 'cp312'
5656

5757
- name: Python Lint Steps
5858
run: make lint

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ if(CSP_USE_CCACHE)
9898
endif()
9999

100100
if(NOT DEFINED CSP_PYTHON_VERSION)
101-
set(CSP_PYTHON_VERSION 3.11)
101+
set(CSP_PYTHON_VERSION 3.12)
102102
endif()
103103

104104
# Path to python folder for autogen

conda/dev-environment-unix.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ dependencies:
3838
- pytest-asyncio
3939
- pytest-cov
4040
- pytest-sugar
41-
- python<3.12
41+
- python<3.13
4242
- python-rapidjson
4343
- rapidjson
4444
- requests

cpp/csp/python/PyNode.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ void PyNode::init( PyObjectPtr inputs, PyObjectPtr outputs )
6161
m_localVars = ( PyObject *** ) calloc( numInputs(), sizeof( PyObject ** ) );
6262

6363
//printf( "Starting %s slots: %ld rank: %d\n", name(), slots, rank() );
64-
PyCodeObject * code = ( PyCodeObject * ) pygen -> gi_code;
6564
#if IS_PRE_PYTHON_3_11
65+
PyCodeObject * code = ( PyCodeObject * ) pygen -> gi_code;
6666
Py_ssize_t numCells = PyTuple_GET_SIZE( code -> co_cellvars );
6767
size_t cell2argIdx = 0;
6868
for( int stackloc = code -> co_argcount; stackloc < code -> co_nlocals + numCells; ++stackloc )
@@ -82,12 +82,13 @@ void PyNode::init( PyObjectPtr inputs, PyObjectPtr outputs )
8282
continue;
8383
var = &( ( ( PyCellObject * ) *var ) -> ob_ref );
8484
}
85-
//PY311 changes
85+
//PY311+ changes
8686
#else
87+
_PyInterpreterFrame * frame = ( _PyInterpreterFrame * ) pygen -> gi_iframe;
88+
PyCodeObject * code = frame -> f_code;
8789
int localPlusIndex = 0;
8890
for( int stackloc = code -> co_argcount; stackloc < code -> co_nlocalsplus; ++stackloc, ++localPlusIndex )
8991
{
90-
_PyInterpreterFrame * frame = ( _PyInterpreterFrame * ) pygen -> gi_iframe;
9192
PyObject **var = &frame -> localsplus[stackloc];
9293

9394
auto kind = _PyLocals_GetKind(code -> co_localspluskinds, localPlusIndex );
@@ -113,19 +114,18 @@ void PyNode::init( PyObjectPtr inputs, PyObjectPtr outputs )
113114
std::string vartype = PyUnicode_AsUTF8( PyTuple_GET_ITEM( *var, 0 ) );
114115
int index = fromPython<int64_t>( PyTuple_GET_ITEM( *var, 1 ) );
115116

116-
//decref tuple at this point its no longer needed and will be replaced
117-
Py_DECREF( *var );
118-
119117
if( vartype == INPUT_VAR_VAR )
120118
{
121119
CSP_ASSERT( !isInputBasket( index ) );
122120

123121
m_localVars[ index ] = var;
124-
//assign null to location so users get reference before assignment errors
125-
*var = nullptr;
122+
//These vars will be "deleted" from the python stack after start
126123
continue;
127124
}
128125

126+
//decref tuple at this point its no longer needed and will be replaced
127+
Py_DECREF( *var );
128+
129129
PyObject * newvalue = nullptr;
130130
if( vartype == NODEREF_VAR )
131131
newvalue = toPython( reinterpret_cast<uint64_t>( static_cast<csp::Node*>(this) ) );

csp/impl/wiring/node_parser.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,10 +843,14 @@ def _parse_impl(self):
843843
else:
844844
start_and_body = startblock + body
845845

846+
# delete ts_var variables *after* start so that they raise Unbound local exceptions if they get accessed before first tick
847+
del_vars = []
848+
for v in ts_vars:
849+
del_vars.append(ast.Delete(targets=[ast.Name(id=v.targets[0].id, ctx=ast.Del())]))
846850
# Yield before start block so we can setup stack frame before executing
847851
# However, this initial yield shouldn't be within the try-finally block, since if a node does not start, it's stop() logic should not be invoked
848852
# This avoids an issue where one node raises an exception upon start(), and then other nodes execute their stop() without having ever started
849-
start_and_body = [ast.Expr(value=ast.Yield(value=None))] + start_and_body
853+
start_and_body = [ast.Expr(value=ast.Yield(value=None))] + del_vars + start_and_body
850854
newbody = init_block + start_and_body
851855

852856
newfuncdef = ast.FunctionDef(name=self._name, body=newbody, returns=None)

csp/impl/wiring/runtime.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
def _normalize_run_times(starttime, endtime, realtime):
1919
if starttime is None:
2020
if realtime:
21-
starttime = datetime.utcnow()
21+
starttime = datetime.now(pytz.UTC).replace(tzinfo=None)
2222
else:
2323
raise RuntimeError("starttime argument is required")
2424
if endtime is None:
@@ -199,8 +199,8 @@ def run(
199199
mem_cache.clear(clear_user_objects=False)
200200

201201
# Ensure we dont start running realtime engines before starttime if its in the future
202-
if starttime > datetime.utcnow() and realtime:
203-
time.sleep((starttime - datetime.utcnow()).total_seconds())
202+
if starttime > datetime.now(pytz.UTC).replace(tzinfo=None) and realtime:
203+
time.sleep((starttime - datetime.now(pytz.UTC)).total_seconds())
204204

205205
with mem_cache:
206206
return engine.run(starttime, endtime)

0 commit comments

Comments
 (0)