Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ dev = [
# See https://github.com/apify/apify-client-python/pull/582/ for more details.
# We explicitly constrain black>=24.3.0 to override the transitive dependency.
"black>=24.3.0",
"datamodel-code-generator[http,ruff]<1.0.0",
"datamodel-code-generator[http,ruff]>=0.57.0,<1.0.0",
"dycw-pytest-only<3.0.0",
"griffe<3.0.0",
"poethepoet<1.0.0",
Expand Down Expand Up @@ -229,6 +229,7 @@ use_annotated = true
wrap_string_literal = true
snake_case_field = true
use_subclass_enum = true
reuse_model = true
extra_fields = "allow"
allow_population_by_field_name = true
aliases = "datamodel_codegen_aliases.json"
Expand Down
28 changes: 0 additions & 28 deletions scripts/postprocess_generated_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
Applied to `_models.py`:
- Fix discriminator field names that use camelCase instead of snake_case (known issue with
discriminators on schemas referenced from array items).
- Deduplicate the inlined `Type(StrEnum)` that comes from ErrorResponse.yaml; rewire to `ErrorType`.
- Rewrite every `class X(StrEnum)` as `X = Literal[...]` so downstream code can pass plain strings
(and reuse the named alias in resource-client signatures) instead of enum members.
- Move the resulting `X = Literal[...]` definitions into `_literals.py`, leaving
Expand Down Expand Up @@ -97,32 +96,6 @@ def fix_discriminators(content: str) -> str:
return content


def deduplicate_error_type_enum(content: str) -> str:
"""Remove the duplicate `Type` enum and rewire references to `ErrorType`.

The `type` property on `ErrorResponse` discriminator subtypes (`RunFailedErrorDetail` etc.)
re-emits the value list of the named `ErrorType` enum as a separate `class Type(StrEnum)` —
upstream issue https://github.com/koxudaxi/datamodel-code-generator/issues/3104.
"""
tree = ast.parse(content)
type_node = next(
(n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == 'Type' and 'StrEnum' in _base_names(n)),
None,
)
if type_node is None:
return content

assert type_node.end_lineno is not None # noqa: S101
lines = content.split('\n')
del lines[type_node.lineno - 1 : type_node.end_lineno]
content = '\n'.join(lines)

# Lookbehinds are deliberately narrow: matching bare `\bType\b` would also rewrite `Type` in
# docstrings (`Content-Type`, `Type of event`), which broke an earlier version.
content = re.sub(r'(?<=: )Type\b|(?<=\| )Type\b|(?<=\[)Type\b', 'ErrorType', content)
return _collapse_blank_lines(content)


def convert_enums_to_literals(content: str) -> str:
"""Rewrite every `class X(StrEnum): ...` into an `X = Literal[...]` alias.

Expand Down Expand Up @@ -425,7 +398,6 @@ def postprocess_models(models_path: Path, literals_path: Path) -> list[Path]:
"""
original = models_path.read_text()
fixed = fix_discriminators(original)
fixed = deduplicate_error_type_enum(fixed)
fixed = convert_enums_to_literals(fixed)
fixed = add_docs_group_decorators(fixed, 'Models')
models_content, literals_content = split_literals_to_file(fixed)
Expand Down
31 changes: 9 additions & 22 deletions src/apify_client/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -841,15 +841,6 @@ class DecodeAndVerifyData(BaseModel):
is_verified_user: Annotated[bool, Field(alias='isVerifiedUser', examples=[False])]


@docs_group('Models')
class DecodeAndVerifyRequest(BaseModel):
model_config = ConfigDict(
extra='allow',
populate_by_name=True,
)
encoded: Annotated[str, Field(examples=['eyJwYXlsb2FkIjoiLi4uIiwic2lnbmF0dXJlIjoiLi4uIn0='])]


@docs_group('Models')
class DecodeAndVerifyResponse(BaseModel):
model_config = ConfigDict(
Expand Down Expand Up @@ -961,6 +952,11 @@ class EncodeAndSignData(BaseModel):
encoded: Annotated[str, Field(examples=['eyJwYXlsb2FkIjoiLi4uIiwic2lnbmF0dXJlIjoiLi4uIn0='])]


@docs_group('Models')
class DecodeAndVerifyRequest(EncodeAndSignData):
pass


@docs_group('Models')
class EncodeAndSignResponse(BaseModel):
model_config = ConfigDict(
Expand Down Expand Up @@ -3230,13 +3226,8 @@ class UpdateRunRequest(BaseModel):


@docs_group('Models')
class UpdateStoreRequest(BaseModel):
model_config = ConfigDict(
extra='allow',
populate_by_name=True,
)
name: str | None = None
general_access: Annotated[GeneralAccess | None, Field(alias='generalAccess')] = None
class UpdateStoreRequest(UpdateDatasetRequest):
pass


@docs_group('Models')
Expand Down Expand Up @@ -3448,12 +3439,8 @@ class WebhookDispatch(BaseModel):


@docs_group('Models')
class WebhookDispatchResponse(BaseModel):
model_config = ConfigDict(
extra='allow',
populate_by_name=True,
)
data: WebhookDispatch
class WebhookDispatchResponse(TestWebhookResponse):
pass


@docs_group('Models')
Expand Down
149 changes: 3 additions & 146 deletions tests/unit/test_postprocess_generated_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from scripts.postprocess_generated_models import (
add_docs_group_decorators,
convert_enums_to_literals,
deduplicate_error_type_enum,
fix_discriminators,
split_literals_to_file,
)
Expand Down Expand Up @@ -49,143 +48,6 @@ def test_fix_discriminators_does_not_touch_unrelated() -> None:
assert result == content


# -- deduplicate_error_type_enum ----------------------------------------------


def test_deduplicate_error_type_enum_removes_duplicate() -> None:
"""The duplicate `Type(StrEnum)` is dropped while the original `ErrorType` is preserved."""
content = textwrap.dedent("""\
class ErrorType(StrEnum):
SOME_ERROR = 'some-error'

class Type(StrEnum):
SOME_ERROR = 'some-error'
OTHER = 'other'

class ErrorResponse(BaseModel):
error_type: Type
""")
result = deduplicate_error_type_enum(content)
assert 'class Type(StrEnum)' not in result
assert 'class ErrorType(StrEnum)' in result


def test_deduplicate_error_type_enum_rewires_colon_annotation() -> None:
"""A `: Type` annotation is rewired to `: ErrorType` after the duplicate is dropped."""
content = textwrap.dedent("""\
class ErrorType(StrEnum):
SOME_ERROR = 'some-error'

class Type(StrEnum):
SOME_ERROR = 'some-error'

class ErrorResponse(BaseModel):
error_type: Type
""")
result = deduplicate_error_type_enum(content)
assert 'error_type: ErrorType' in result


def test_deduplicate_error_type_enum_rewires_union_annotation() -> None:
"""A `| Type` arm in a union annotation is rewired to `| ErrorType`."""
content = textwrap.dedent("""\
class ErrorType(StrEnum):
X = 'x'

class Type(StrEnum):
X = 'x'

class Foo(BaseModel):
field: str | Type
""")
result = deduplicate_error_type_enum(content)
assert '| ErrorType' in result
assert '| Type' not in result


def test_deduplicate_error_type_enum_rewires_bracket_annotation() -> None:
"""A `[Type]` subscript inside a generic annotation is rewired to `[ErrorType]`."""
content = textwrap.dedent("""\
class ErrorType(StrEnum):
X = 'x'

class Type(StrEnum):
X = 'x'

class Foo(BaseModel):
field: list[Type]
""")
result = deduplicate_error_type_enum(content)
assert 'list[ErrorType]' in result
assert 'list[Type]' not in result


def test_deduplicate_error_type_enum_collapses_extra_blank_lines() -> None:
"""Removing the duplicate enum collapses any resulting run of 4+ blank lines."""
content = "\nclass Type(StrEnum):\n X = 'x'\n\n\n\n\nclass Next(BaseModel):\n pass\n"
result = deduplicate_error_type_enum(content)
assert '\n\n\n\n' not in result


def test_deduplicate_error_type_enum_no_change_when_no_duplicate() -> None:
"""Source without a `Type(StrEnum)` duplicate passes through unchanged."""
content = textwrap.dedent("""\
class ErrorType(StrEnum):
SOME_ERROR = 'some-error'

class Foo(BaseModel):
field: ErrorType
""")
result = deduplicate_error_type_enum(content)
assert result == content


def test_deduplicate_error_type_enum_does_not_touch_type_in_class_names() -> None:
"""`Type` inside other class names (e.g. `ContentType`) is not rewritten."""
content = textwrap.dedent("""\
class ErrorType(StrEnum):
X = 'x'

class Type(StrEnum):
X = 'x'

class ContentType(BaseModel):
value: str
""")
result = deduplicate_error_type_enum(content)
assert 'class ContentType(BaseModel)' in result


def test_deduplicate_error_type_enum_handles_type_as_last_class() -> None:
"""The `Type` enum is removed even when it's the last top-level definition."""
content = textwrap.dedent("""\
class ErrorType(StrEnum):
X = 'x'

class Foo(BaseModel):
field: Type

class Type(StrEnum):
X = 'x'
""")
result = deduplicate_error_type_enum(content)
assert 'class Type(StrEnum)' not in result
assert 'field: ErrorType' in result


def test_deduplicate_error_type_enum_skips_non_strenum_type() -> None:
"""A `Type` class that is not a `StrEnum` (e.g. a Pydantic model) is left in place."""
content = textwrap.dedent("""\
class ErrorType(StrEnum):
X = 'x'

class Type(BaseModel):
value: str
""")
result = deduplicate_error_type_enum(content)
assert 'class Type(BaseModel)' in result


# -- add_docs_group_decorators ------------------------------------------------


Expand Down Expand Up @@ -417,7 +279,7 @@ class Status(StrEnum):


def test_full_pipeline() -> None:
"""All steps composed: discriminator fix, `Type` dedup, enum-to-literal, docs decorators."""
"""All steps composed: discriminator fix, enum-to-literal, docs decorators."""
content = textwrap.dedent("""\
from enum import StrEnum
from typing import Literal
Expand All @@ -430,26 +292,21 @@ class Zebra(BaseModel):
class ErrorType(StrEnum):
SOME_ERROR = 'some-error'

class Type(StrEnum):
SOME_ERROR = 'some-error'

class ErrorResponse(BaseModel):
error_type: Type
error_type: ErrorType

class Alpha(BaseModel):
name: str
""")
result = fix_discriminators(content)
result = deduplicate_error_type_enum(result)
result = convert_enums_to_literals(result)
result = add_docs_group_decorators(result, 'Models')

# Discriminator fixed.
assert "discriminator='pricing_model'" in result
assert "discriminator='pricingModel'" not in result

# Duplicate Type enum removed and references rewired, then the remaining enum converted.
assert 'class Type(StrEnum)' not in result
# The enum is converted to a Literal alias.
assert 'class ErrorType(StrEnum)' not in result
assert 'ErrorType = Literal[' in result
assert 'error_type: ErrorType' in result
Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading