Skip to content

Commit 4845db6

Browse files
authored
Deprecate Python 3.9 builds (#611)
Signed-off-by: Adam Glustein <adam.glustein@point72.com>
1 parent d593256 commit 4845db6

13 files changed

Lines changed: 102 additions & 151 deletions

.github/workflows/build.yml

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@ jobs:
200200
python-version:
201201
- "3.11"
202202
cibuildwheel:
203-
- "cp39"
204203
- "cp310"
205204
- "cp311"
206205
- "cp312"
@@ -295,13 +294,6 @@ jobs:
295294
- name: Check Wheels
296295
run: make dist-check
297296

298-
- name: Upload Wheel
299-
uses: actions/upload-artifact@v4
300-
with:
301-
name: csp-dist-${{ runner.os }}-${{ runner.arch }}-3.9
302-
path: dist/*.whl
303-
if: ${{ matrix.cibuildwheel == 'cp39' }}
304-
305297
- name: Upload Wheel
306298
uses: actions/upload-artifact@v4
307299
with:
@@ -415,7 +407,6 @@ jobs:
415407
- macos-14
416408
- windows-2022
417409
python-version:
418-
- 3.9
419410
- "3.10"
420411
- 3.11
421412
- 3.12
@@ -426,15 +417,8 @@ jobs:
426417
##############################################
427418
# Things to exclude if not a full matrix run #
428419
##############################################
429-
# mac arm builds support py3.10+
430-
- os: macos-14
431-
python-version: "3.9"
432-
420+
433421
# Avoid extra resources for windows build
434-
- is-full-run: false
435-
os: windows-2022
436-
python-version: "3.9"
437-
438422
- is-full-run: false
439423
os: windows-2022
440424
python-version: "3.10"
@@ -448,10 +432,6 @@ jobs:
448432
python-version: "3.12"
449433

450434
# avoid unnecessary use of mac resources
451-
- is-full-run: false
452-
os: macos-14
453-
python-version: "3.9"
454-
455435
- is-full-run: false
456436
os: macos-14
457437
python-version: "3.10"
@@ -566,7 +546,7 @@ jobs:
566546
os:
567547
- ubuntu-24.04
568548
python-version:
569-
- 3.9
549+
- 3.11
570550

571551
runs-on: ${{ matrix.os }}
572552

@@ -644,7 +624,7 @@ jobs:
644624
os:
645625
- ubuntu-24.04
646626
python-version:
647-
- 3.9
627+
- "3.10"
648628
package:
649629
- sqlalchemy<2
650630
- perspective-python<3

.github/workflows/wiki-lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
os:
2525
- ubuntu-24.04
2626
python-version:
27-
- 3.9
27+
- 3.11
2828

2929
runs-on: ${{ matrix.os }}
3030

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
[![PyPI](https://img.shields.io/pypi/v/csp.svg?style=flat)](https://pypi.python.org/pypi/csp)
1111
[![License](https://img.shields.io/badge/license-Apache--2.0-green)](https://github.com/Point72/csp/LICENSE)
1212
[![Build Status](https://github.com/Point72/csp/actions/workflows/build.yml/badge.svg)](https://github.com/Point72/csp/actions/workflows/build.yml)
13-
[![Python Versions](https://img.shields.io/badge/python-3.9_%7C_3.10_%7C_3.11_%7C_3.12_%7C_3.13-blue)](https://github.com/Point72/csp/blob/main/pyproject.toml)
13+
[![Python Versions](https://img.shields.io/badge/python-3.10_%7C_3.11_%7C_3.12_%7C_3.13-blue)](https://github.com/Point72/csp/blob/main/pyproject.toml)
1414

1515
<br/>
1616

conda/dev-environment-unix.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ dependencies:
2929
- mdformat-tables>=1,<1.1
3030
- ninja
3131
- numpy>=2
32-
- pandas<2.3 # [py<310]
33-
- pandas # [py>=310]
32+
- pandas
3433
- pillow
3534
- polars
3635
- psutil
@@ -40,7 +39,7 @@ dependencies:
4039
- pytest-asyncio
4140
- pytest-cov
4241
- pytest-sugar
43-
- python<3.14
42+
- python>=3.10,<3.14
4443
- python-build
4544
- python-graphviz
4645
- python-rapidjson

conda/dev-environment-win.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ dependencies:
2929
- mdformat-tables>=1,<1.1
3030
- ninja
3131
- numpy>=2
32-
- pandas<2.3 # [py<310]
33-
- pandas # [py>=310]
32+
- pandas
3433
- pillow
3534
- polars
3635
- psutil
@@ -40,7 +39,7 @@ dependencies:
4039
- pytest-asyncio
4140
- pytest-cov
4241
- pytest-sugar
43-
- python<3.14
42+
- python>=3.10,<3.14
4443
- python-build
4544
- python-graphviz
4645
- python-rapidjson

cpp/csp/python/PyStructFastList_impl.h

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,6 @@
1010
#include <csp/python/VectorWrapper.h>
1111
#include <algorithm>
1212

13-
// The Py_TPFLAGS_SEQUENCE flag is used for 'match' statement to work. It is not available prior Python 3.10, so need to use conditional compilation
14-
#define IS_PYTHON_3_10_OR_GREATER ( PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 10 )
15-
#if IS_PYTHON_3_10_OR_GREATER
16-
#define SEQ_FLAG Py_TPFLAGS_SEQUENCE
17-
#else
18-
#define SEQ_FLAG 0
19-
#endif
2013

2114
namespace csp::python
2215
{
@@ -615,7 +608,7 @@ PyTypeObject PyStructFastList<StorageT>::PyType = {
615608
PyObject_GenericGetAttr, /* tp_getattro */
616609
0, /* tp_setattro */
617610
0, /* tp_as_buffer */
618-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | SEQ_FLAG, /* tp_flags */ // adding flag Py_TPFLAGS_SEQUENCE in Python >=3.10
611+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_SEQUENCE, /* tp_flags */
619612
"", /* tp_doc */
620613
( traverseproc ) PyStructFastList_tp_traverse<StorageT>, /* tp_traverse */
621614
( inquiry ) PyStructFastList_tp_clear<StorageT>, /* tp_clear */

csp/impl/types/pydantic_types.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import sys
21
import types
32
import typing
43
from inspect import isclass
@@ -181,7 +180,7 @@ def adjust_annotations(
181180
).typ
182181
return DynamicBasketPydantic[annotation_key, annotation_value]
183182
elif origin and args:
184-
if sys.version_info >= (3, 10) and origin is types.UnionType: # For PEP604, i.e. x|y
183+
if origin is types.UnionType: # For PEP604, i.e. x|y
185184
origin = typing.Union
186185
if origin is TsType:
187186
return TsType[

csp/impl/types/typing_utils.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# utils for dealing with typing types
22
import collections
3-
import sys
43
import types
54
import typing
65
import weakref
@@ -44,7 +43,7 @@ def create_instance(raw_data, validator):
4443
)
4544

4645

47-
class CspTypingUtils39:
46+
class CspTypingUtils310:
4847
_ORIGIN_COMPAT_MAP = {list: typing.List, set: typing.Set, dict: typing.Dict, tuple: typing.Tuple}
4948
_ARRAY_ORIGINS = (csp.typing.Numpy1DArray, csp.typing.NumpyNDArray)
5049
_GENERIC_ALIASES = (typing._GenericAlias, typing.GenericAlias)
@@ -82,7 +81,9 @@ def is_numpy_nd_array_type(cls, typ):
8281

8382
@classmethod
8483
def is_union_type(cls, typ):
85-
return isinstance(typ, typing._GenericAlias) and typ.__origin__ is typing.Union
84+
return (isinstance(typ, typing._GenericAlias) and typ.__origin__ is typing.Union) or isinstance(
85+
typ, types.UnionType
86+
)
8687

8788
@classmethod
8889
def is_literal_type(cls, typ):
@@ -111,20 +112,7 @@ def pretty_typename(cls, typ):
111112
return str(typ)
112113

113114

114-
CspTypingUtils = CspTypingUtils39
115-
116-
117-
if sys.version_info >= (3, 10):
118-
119-
class CspTypingUtils310(CspTypingUtils39):
120-
# To support PEP 604
121-
@classmethod
122-
def is_union_type(cls, typ):
123-
return (isinstance(typ, typing._GenericAlias) and typ.__origin__ is typing.Union) or isinstance(
124-
typ, types.UnionType
125-
)
126-
127-
CspTypingUtils = CspTypingUtils310
115+
CspTypingUtils = CspTypingUtils310
128116

129117

130118
class TsTypeValidator:

csp/tests/impl/test_struct.py

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4175,47 +4175,47 @@ class StructWithLiterals(csp.Struct):
41754175

41764176
def test_pipe_operator_types(self):
41774177
"""Test using the pipe operator for union types in Python 3.10+"""
4178-
if sys.version_info >= (3, 10): # Only run on Python 3.10+
4179-
# Define a class using various pipe operator combinations
4180-
class PipeTypesConfig(csp.Struct):
4181-
# Basic primitive types with pipe
4182-
id_field: str | int
4183-
# Pipe with None (similar to Optional)
4184-
description: str | None = None
4185-
# Multiple types with pipe
4186-
value: str | int | float | bool
4187-
# Container with pipe
4188-
tags: List[str] | Dict[str, str] | None = None
4189-
# Pipe with literal for comparison
4190-
status: Literal["active", "inactive"] | None = "active"
4191-
4192-
# Test all valid types
4193-
valid_cases = [
4194-
{"id_field": "string_id", "value": "string_value"},
4195-
{"id_field": 42, "value": 123},
4196-
{"id_field": "mixed", "value": 3.14},
4197-
{"id_field": 999, "value": True},
4198-
{"id_field": "with_desc", "value": 1, "description": "Description"},
4199-
{"id_field": "with_dict", "value": 1, "tags": None},
4200-
]
4201-
4202-
for case in valid_cases:
4203-
result = PipeTypesConfig.from_dict(case)
4204-
# use the other route to get back the result
4205-
result_to_dict_loop = TypeAdapter(PipeTypesConfig).validate_python(result.to_dict())
4206-
self.assertEqual(result, result_to_dict_loop)
4207-
4208-
# Test invalid values
4209-
invalid_cases = [
4210-
{"id_field": 3.14, "value": 1}, # Float for id_field
4211-
{"id_field": None, "value": 1}, # None for required id_field
4212-
{"id_field": "test", "value": {}}, # Dict for value
4213-
{"id_field": "test", "value": None}, # None for required value
4214-
{"id_field": "test", "value": 1, "status": "unknown"}, # Invalid literal
4215-
]
4216-
for case in invalid_cases:
4217-
with self.assertRaises(ValidationError):
4218-
TypeAdapter(PipeTypesConfig).validate_python(case)
4178+
4179+
# Define a class using various pipe operator combinations
4180+
class PipeTypesConfig(csp.Struct):
4181+
# Basic primitive types with pipe
4182+
id_field: str | int
4183+
# Pipe with None (similar to Optional)
4184+
description: str | None = None
4185+
# Multiple types with pipe
4186+
value: str | int | float | bool
4187+
# Container with pipe
4188+
tags: List[str] | Dict[str, str] | None = None
4189+
# Pipe with literal for comparison
4190+
status: Literal["active", "inactive"] | None = "active"
4191+
4192+
# Test all valid types
4193+
valid_cases = [
4194+
{"id_field": "string_id", "value": "string_value"},
4195+
{"id_field": 42, "value": 123},
4196+
{"id_field": "mixed", "value": 3.14},
4197+
{"id_field": 999, "value": True},
4198+
{"id_field": "with_desc", "value": 1, "description": "Description"},
4199+
{"id_field": "with_dict", "value": 1, "tags": None},
4200+
]
4201+
4202+
for case in valid_cases:
4203+
result = PipeTypesConfig.from_dict(case)
4204+
# use the other route to get back the result
4205+
result_to_dict_loop = TypeAdapter(PipeTypesConfig).validate_python(result.to_dict())
4206+
self.assertEqual(result, result_to_dict_loop)
4207+
4208+
# Test invalid values
4209+
invalid_cases = [
4210+
{"id_field": 3.14, "value": 1}, # Float for id_field
4211+
{"id_field": None, "value": 1}, # None for required id_field
4212+
{"id_field": "test", "value": {}}, # Dict for value
4213+
{"id_field": "test", "value": None}, # None for required value
4214+
{"id_field": "test", "value": 1, "status": "unknown"}, # Invalid literal
4215+
]
4216+
for case in invalid_cases:
4217+
with self.assertRaises(ValidationError):
4218+
TypeAdapter(PipeTypesConfig).validate_python(case)
42194219

42204220
def test__metadata_info(self):
42214221
class MyStruct(DerivedMixed):

csp/tests/impl/types/test_pydantic_types.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,7 @@ def test_other(self):
160160
self.assertAnnotationsEqual(adjust_annotations(MyGeneric[str]), MyGeneric[str])
161161

162162
def test_union_pipe(self):
163-
if sys.version_info >= (3, 10):
164-
self.assertAnnotationsEqual(adjust_annotations(str | float), Union[str, float])
163+
self.assertAnnotationsEqual(adjust_annotations(str | float), Union[str, float])
165164

166165
def test_make_optional(self):
167166
self.assertAnnotationsEqual(adjust_annotations(float, make_optional=True), Optional[float])

0 commit comments

Comments
 (0)