Skip to content

Commit 6e36611

Browse files
committed
migrate to uv and python 3.14 support
1 parent 0b67860 commit 6e36611

4 files changed

Lines changed: 81 additions & 815 deletions

File tree

.github/workflows/tests.yml

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,40 +10,37 @@ jobs:
1010
pylama:
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v2
14-
- name: Setup python3.10
15-
uses: actions/setup-python@v2
13+
- uses: actions/checkout@v4
14+
- name: Setup uv
15+
uses: astral-sh/setup-uv@v4
1616
with:
17-
python-version: "3.11"
18-
- run: python -m pip install poetry
19-
- run: poetry install
20-
- run: poetry run pylama
17+
python-version: "3.12"
18+
- run: uv sync
19+
- run: uv run pylama
2120
env:
2221
FORCE_COLOR: 1
2322
mypy:
2423
runs-on: ubuntu-latest
2524
steps:
26-
- uses: actions/checkout@v2
27-
- name: Setup python3.10
28-
uses: actions/setup-python@v2
25+
- uses: actions/checkout@v4
26+
- name: Setup uv
27+
uses: astral-sh/setup-uv@v4
2928
with:
30-
python-version: "3.11"
31-
- run: python -m pip install poetry
32-
- run: poetry install
33-
- run: poetry run mypy
29+
python-version: "3.12"
30+
- run: uv sync
31+
- run: uv run mypy
3432
env:
3533
FORCE_COLOR: 1
3634
docs:
3735
runs-on: ubuntu-latest
3836
steps:
39-
- uses: actions/checkout@v2
40-
- name: Setup python3.10
41-
uses: actions/setup-python@v2
37+
- uses: actions/checkout@v4
38+
- name: Setup uv
39+
uses: astral-sh/setup-uv@v4
4240
with:
43-
python-version: "3.11"
44-
- run: python -m pip install poetry
45-
- run: poetry install
46-
- run: poetry run pytest -svv README.md
41+
python-version: "3.12"
42+
- run: uv sync
43+
- run: uv run pytest -svv README.md
4744
env:
4845
FORCE_COLOR: 1
4946

@@ -60,24 +57,24 @@ jobs:
6057
- '3.11'
6158
- '3.12'
6259
- '3.13'
60+
- '3.14'
6361
steps:
64-
- uses: actions/checkout@v2
65-
- name: Setup python${{ matrix.python }}
66-
uses: actions/setup-python@v2
62+
- uses: actions/checkout@v4
63+
- name: Setup uv with python${{ matrix.python }}
64+
uses: astral-sh/setup-uv@v4
6765
with:
6866
python-version: "${{ matrix.python }}"
69-
- run: python -m pip install poetry
70-
- run: poetry install
67+
- run: uv sync
7168
- run: >-
72-
poetry run pytest \
69+
uv run pytest \
7370
-vv \
7471
--cov=argclass \
7572
--cov-report=term-missing \
7673
--doctest-modules \
7774
tests
7875
env:
7976
FORCE_COLOR: 1
80-
- run: poetry run coveralls
77+
- run: uv run coveralls
8178
env:
8279
COVERALLS_PARALLEL: 'true'
8380
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}

argclass/__init__.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -215,17 +215,21 @@ def __new__(
215215
mcs, name: str, bases: Tuple[Type["StoreMeta"], ...],
216216
attrs: Dict[str, Any],
217217
) -> "StoreMeta":
218+
# Create the class first to ensure annotations are available
219+
# Python 3.14+ (PEP 649) defers annotation evaluation
220+
cls = super().__new__(mcs, name, bases, attrs)
221+
218222
annotations = merge_annotations(
219-
attrs.get("__annotations__", {}), *bases,
223+
getattr(cls, "__annotations__", {}), *bases,
220224
)
221-
attrs["__annotations__"] = annotations
222-
attrs["_fields"] = tuple(
225+
cls.__annotations__ = annotations
226+
cls._fields = tuple(
223227
filter(
224228
lambda x: not x.startswith("_"),
225229
annotations.keys(),
226230
),
227231
)
228-
return super().__new__(mcs, name, bases, attrs)
232+
return cls
229233

230234

231235
class Store(metaclass=StoreMeta):
@@ -236,10 +240,12 @@ def __new__(cls, **kwargs: Any) -> "Store":
236240
obj = super().__new__(cls)
237241

238242
type_map: Dict[str, Tuple[Type, Any]] = {}
239-
for key, value in obj.__annotations__.items():
243+
# Use cls.__annotations__ instead of obj.__annotations__ to avoid
244+
# triggering __getattr__ before _values is initialized (Python 3.14+)
245+
for key, value in cls.__annotations__.items():
240246
if key.startswith("_"):
241247
continue
242-
type_map[key] = (value, getattr(obj, key, cls._default_value))
248+
type_map[key] = (value, getattr(cls, key, cls._default_value))
243249

244250
for key, (value_type, default) in type_map.items():
245251
if default is cls._default_value and key not in kwargs:
@@ -420,8 +426,14 @@ def __new__(
420426
mcs, name: str, bases: Tuple[Type["Meta"], ...],
421427
attrs: Dict[str, Any],
422428
) -> "Meta":
429+
# Create the class first to ensure annotations are available
430+
# Python 3.14+ (PEP 649) defers annotation evaluation, so
431+
# __annotations__ may not be in attrs during class creation
432+
cls = super().__new__(mcs, name, bases, attrs)
433+
434+
# Now get annotations from the created class
423435
annotations = merge_annotations(
424-
attrs.get("__annotations__", {}), *bases,
436+
getattr(cls, "__annotations__", {}), *bases,
425437
)
426438

427439
arguments = {}
@@ -441,7 +453,7 @@ def __new__(
441453
if not isinstance(
442454
argument, (TypedArgument, AbstractGroup, AbstractParser),
443455
):
444-
attrs[key] = ...
456+
setattr(cls, key, ...)
445457

446458
is_required = argument is None or argument is Ellipsis
447459

@@ -481,10 +493,9 @@ def __new__(
481493
elif isinstance(value, AbstractParser):
482494
subparsers[key] = value
483495

484-
attrs["__arguments__"] = MappingProxyType(arguments)
485-
attrs["__argument_groups__"] = MappingProxyType(argument_groups)
486-
attrs["__subparsers__"] = MappingProxyType(subparsers)
487-
cls = super().__new__(mcs, name, bases, attrs)
496+
cls.__arguments__ = MappingProxyType(arguments)
497+
cls.__argument_groups__ = MappingProxyType(argument_groups)
498+
cls.__subparsers__ = MappingProxyType(subparsers)
488499
return cls
489500

490501

0 commit comments

Comments
 (0)