Skip to content

Add Pydantic models and uv support to the Python SDK#1375

Open
ChiragAgg5k wants to merge 13 commits intomasterfrom
feat/python-pydantic-models
Open

Add Pydantic models and uv support to the Python SDK#1375
ChiragAgg5k wants to merge 13 commits intomasterfrom
feat/python-pydantic-models

Conversation

@ChiragAgg5k
Copy link
Member

@ChiragAgg5k ChiragAgg5k commented Mar 6, 2026

Summary

  • generate Pydantic-based request and response models for the Python SDK
  • add Python packaging support for uv via generated pyproject.toml
  • improve generated Python model docs and fix service/model name collisions during response parsing
  • NEW: add Generic[T] support for models with dynamic fields (e.g., Row)
    • allows type-safe access to user-defined fields via custom Pydantic models
    • supports both single row (get_row) and list operations (list_rows)

Verification

  • php example.php python
  • composer lint-twig
  • python3 -m compileall examples/python/appwrite
  • live Appwrite E2E checks against generated Python SDK:
    • Locale.get() returns typed Locale
    • Locale.list_countries() returns typed CountryList
    • Health.get() returns typed HealthStatus
    • NEW: TablesDB.get_row(..., model_type=Post) returns Row[Post] with typed data field
    • NEW: TablesDB.list_rows(..., model_type=Post) returns RowList[Post] with typed rows

Generic Models Usage

Define your custom Pydantic model:

from pydantic import BaseModel
from datetime import datetime
from typing import Optional

class Post(BaseModel):
    postId: int
    authorId: int
    title: str
    content: str
    createdAt: datetime
    updatedAt: datetime
    isPublished: bool
    excerpt: Optional[str] = None

Fetch with type-safe access:

# Single row
row = client.tables_db.get_row(
    database_id="test-db",
    table_id="posts",
    row_id="...",
    model_type=Post
)
print(row.data.title)  # Fully typed!
print(row.data.postId)  # int, not Any

# List of rows
result = client.tables_db.list_rows(
    database_id="test-db",
    table_id="posts",
    model_type=Post
)
for row in result.rows:
    print(row.data.title)  # Also typed!

Notes

  • a project-scoped live call (Project.list_variables()) returned 401 missing scopes (["projects.read"]) with the provided API key, which indicates a key-permission issue rather than an SDK parsing/runtime issue.

Files Changed

  • src/SDK/Language/Python.php: Added hasGenericType() method and Twig filters
  • templates/python/package/models/model.py.twig: Added Generic[T] support, data property, with_data() method
  • templates/python/package/services/service.py.twig: Added model_type parameter
  • templates/python/base/requests/api.twig: Updated to use with_data() for generic responses

Summary by CodeRabbit

  • New Features

    • Generated Pydantic model classes with from_dict/to_dict/to_json and package-level exports
    • Automatic response parsing into model instances (including Union support) and value normalization
    • Request-model generation and examples for docs/examples
    • Generic type support for models with additionalProperties - type-safe access to dynamic fields
  • Updates

    • Improved Python typing and richer type generation (enums, arrays, sub-schemas; objects => Dict[str, Any])
    • Packaging: pyproject/requirements/setup updated to require pydantic and Python 3.9–3.12
  • Tests

    • Added uniform response printing helper in Python tests
  • Documentation

    • Added alternative installation instructions using uv

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Pydantic-based model scaffolding, value normalization, response parsing (including Union support), expanded Python type helpers and request-model examples, and packaging templates (pyproject, requirements) including pydantic dependency — updates codegen templates and language helpers accordingly.

Changes

Cohort / File(s) Summary
Language core & model helpers
src/SDK/Language/Python.php
Adds many protected helper methods for type/model naming, normalization, request-model examples, and updates TYPE_OBJECT to Dict[str, Any]; registers new Twig filters for model/type resolution and examples.
Model templates
templates/python/package/models/base_model.py.twig, templates/python/package/models/__init__.py.twig, templates/python/package/models/model.py.twig, templates/python/package/models/request_model.py.twig
Adds AppwriteModel base class, ModelType TypeVar, generates Pydantic models and request-models as AppwriteModel subclasses, and creates models package init to re-export models.
Request/response templates
templates/python/base/requests/api.twig, templates/python/base/requests/file.twig
Assigns client response to a variable, normalizes header values, and routes responses through _parse_response supporting single models or Union of models; falls back to raw response.
Service runtime & generated services
templates/python/package/service.py.twig, templates/python/package/services/service.py.twig
Adds _normalize_value and _parse_response helpers, ModelType TypeVar, conditional/aliased model imports, and updates generated method return type hints to concrete model types or Union of models.
Encoders & serialization
templates/python/package/encoders/value_class_encoder.py.twig
Imports AppwriteModel and serializes AppwriteModel instances via to_dict() in ValueClassEncoder.default before enum handling.
Parameters handling
templates/python/base/params.twig
Wraps path, query, body/formData, and header parameter values with self._normalize_value(...) to ensure models/enums are normalized.
Packaging & requirements
templates/python/pyproject.toml.twig, templates/python/requirements.txt.twig, templates/python/setup.py.twig, templates/python/README.md.twig
Adds pyproject template, broadens requests pin to a range, adds pydantic>=2,<3, updates setup classifiers and python_requires, and adds README installation variant.
Docs/examples & tests
templates/python/docs/example.md.twig, tests/languages/python/tests.py
Generates model-based examples for parameters/responses, imports response models (including Union scenarios), adds print_result helper in tests and replaces direct prints to support dicts or model instances.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Service
    participant HTTP_API as HTTP_API
    participant Parser as _parse_response
    participant Model as PydanticModel

    Client->>Service: call method(params)
    Service->>Service: _normalize_value(params)
    Service->>HTTP_API: perform HTTP request
    HTTP_API-->>Service: response (dict/JSON)
    Service->>Parser: _parse_response(response, model or union_models)

    alt union_models provided
        Parser->>Model: try model.model_validate(response)
        alt success
            Model-->>Parser: validated instance
        else
            Parser->>Model: try next model
        end
    else single model provided
        Parser->>Model: model.model_validate(response)
        Model-->>Parser: validated instance
    else no model
        Parser-->>Service: return raw response
    end

    Parser-->>Service: typed result
    Service-->>Client: return typed result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Poem

🐇 I nibble fields and stitch their types,
Pydantic pillows, tidy bytes,
Responses parsed, unions tried,
Values normalized and tied,
The SDK hops on polished flights.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main changes: adding Pydantic models for Python SDK data validation and uv support via pyproject.toml configuration.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/python-pydantic-models

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
templates/python/package/services/service.py.twig (1)

69-85: ⚠️ Potential issue | 🟠 Major

Keep the return annotation aligned with the filtered response-model set.

If method.responseModels contains more than one entry but only one concrete model survives filtering, Line 85 falls back to Dict[str, Any]. templates/python/base/requests/api.twig Line 17 and templates/python/base/requests/file.twig Line 31 still parse and return that model, so the generated public type is wrong on a path this PR explicitly adds.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@templates/python/package/services/service.py.twig` around lines 69 - 85, The
return type logic falls back to Dict[str, Any] when method.responseModels had
multiple entries but filtering left exactly one concrete model; update the
conditional that builds the return annotation (the block that inspects
validResponseModels, method.responseModel and method.type for {{ method.name |
caseSnake }}) so that: if validResponseModels|length > 1 produce Union[...] as
before, else if validResponseModels|length == 1 emit that single model (apply
the same service-name-to-Model rename logic used elsewhere), else fall back to
the existing method.responseModel check, and only then to Dict[str, Any];
reference the validResponseModels variable and the return annotation generation
around the def ... ) -> ... line to apply the fix.
🧹 Nitpick comments (1)
templates/python/requirements.txt.twig (1)

1-2: Consider consistent version pinning strategy.

The requests package is pinned to an exact version (2.31.0) while pydantic uses a version range (>=2,<3). This inconsistency is minor but could be intentional. If this file is meant for reproducible development environments, consider pinning both; if it's for general compatibility, consider using ranges for both.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@templates/python/requirements.txt.twig` around lines 1 - 2, The requirements
file mixes exact pinning for requests and a range for pydantic; choose a
consistent strategy and update both entries accordingly: either pin both (e.g.,
change pydantic to an exact version matching your tested env) or loosen both to
ranges (e.g., change requests to a compatible range like requests>=2.31,<3);
update the lines referencing "requests" and "pydantic" in
templates/python/requirements.txt.twig to reflect the chosen consistent
versioning approach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/SDK/Language/Python.php`:
- Around line 490-535: The fallback path in getModelPropertyType currently
forces 'nullable' => false which drops schema nullability; update the array
passed to getTypeName in getModelPropertyType so you still force 'required' =>
true but preserve the original nullable flag (e.g. 'nullable' =>
$property['nullable'] ?? false) instead of overriding it to false, so
required-but-nullable fields remain nullable when types are generated.

In `@templates/python/package/services/service.py.twig`:
- Around line 20-24: The parameter type annotations in the template must match
the aliased import when a model name collides with the service name: update the
parameter annotation generation in
templates/python/package/services/service.py.twig (the use of {{ parameter |
getPropertyType(method) | raw }}) to apply the same collision check used for
return types (compare (parameter.model | caseUcfirst) to (service.name |
caseUcfirst)) and render the aliased name (e.g., {{ parameter.model |
caseUcfirst }}Model) when they match; alternatively, adjust
getPropertyType()/getTypeName() in the Python codegen so it returns the aliased
PascalCaseModel name for parameters whose model equals service.name to ensure
annotations reference an imported symbol.

---

Outside diff comments:
In `@templates/python/package/services/service.py.twig`:
- Around line 69-85: The return type logic falls back to Dict[str, Any] when
method.responseModels had multiple entries but filtering left exactly one
concrete model; update the conditional that builds the return annotation (the
block that inspects validResponseModels, method.responseModel and method.type
for {{ method.name | caseSnake }}) so that: if validResponseModels|length > 1
produce Union[...] as before, else if validResponseModels|length == 1 emit that
single model (apply the same service-name-to-Model rename logic used elsewhere),
else fall back to the existing method.responseModel check, and only then to
Dict[str, Any]; reference the validResponseModels variable and the return
annotation generation around the def ... ) -> ... line to apply the fix.

---

Nitpick comments:
In `@templates/python/requirements.txt.twig`:
- Around line 1-2: The requirements file mixes exact pinning for requests and a
range for pydantic; choose a consistent strategy and update both entries
accordingly: either pin both (e.g., change pydantic to an exact version matching
your tested env) or loosen both to ranges (e.g., change requests to a compatible
range like requests>=2.31,<3); update the lines referencing "requests" and
"pydantic" in templates/python/requirements.txt.twig to reflect the chosen
consistent versioning approach.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bfcaf903-eb37-4546-8a2d-c30c04bc525b

📥 Commits

Reviewing files that changed from the base of the PR and between d63e5a3 and cf4525e.

📒 Files selected for processing (15)
  • src/SDK/Language/Python.php
  • templates/python/README.md.twig
  • templates/python/base/params.twig
  • templates/python/base/requests/api.twig
  • templates/python/base/requests/file.twig
  • templates/python/package/encoders/value_class_encoder.py.twig
  • templates/python/package/models/__init__.py.twig
  • templates/python/package/models/base_model.py.twig
  • templates/python/package/models/model.py.twig
  • templates/python/package/models/request_model.py.twig
  • templates/python/package/service.py.twig
  • templates/python/package/services/service.py.twig
  • templates/python/pyproject.toml.twig
  • templates/python/requirements.txt.twig
  • templates/python/setup.py.twig

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/SDK/Language/Python.php`:
- Around line 490-535: The getModelPropertyType function returns bare types for
special-case branches (sub_schemas, sub_schema, enum arrays, enums, and object)
and doesn't respect nullable, causing Pydantic validation failures; define a
small helper closure (e.g., $wrapNullable as suggested) inside
getModelPropertyType that checks ($property['nullable'] ?? false) and returns
'Optional[TYPE]' when needed, then apply $wrapNullable to the return values for
the sub_schemas branch (unionType), sub_schema branch (modelType), the enum
array branch (enumType wrapped in List[...] then wrapped), the enum branch
(enumType or List[...] wrapped), and the object branch ('Dict[str, Any]') so
every path preserves nullable semantics before returning.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f95faf7b-5101-4cb2-a915-b2590124c606

📥 Commits

Reviewing files that changed from the base of the PR and between cf4525e and 0e70dc8.

📒 Files selected for processing (3)
  • src/SDK/Language/Python.php
  • templates/python/package/services/service.py.twig
  • templates/python/requirements.txt.twig

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
tests/languages/python/tests.py (1)

89-97: Add one typed request-model call here.

These cases still only exercise raw dict/list[dict] payloads, so they won't catch regressions in the new AppwriteModel normalization/serialization path. Please add at least one request that constructs the generated request model directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/languages/python/tests.py` around lines 89 - 97, Add a test that
constructs and passes a generated request-model instance (instead of a raw dict)
to the client to exercise AppwriteModel normalization/serialization;
specifically, import the generated model class for the player request (e.g., the
Player or CreatePlayerRequest model) and call general.create_player(...) with an
instance of that model (mirroring the existing dict payload used by
general.create_player/general.create_players) so the AppwriteModel code path is
exercised during tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@templates/python/docs/example.md.twig`:
- Around line 15-18: The docs example imports request models without guarding
against name collisions with the service class; update
templates/python/docs/example.md.twig so when computing modelName (the existing
variable) you check against the service class name (the symbol used to import
the service on line 3) and, if equal, import the model as an alias like {{
modelName | caseUcfirst }}Model (update the import line that currently does
"from {{ spec.title | caseSnake }}.models import {{ modelName | caseUcfirst }}"
to use the aliased identifier when collision detected and add the aliased name
to added). Also modify the requestModelExample filter in
src/SDK/Language/Python.php to accept the service name as an extra parameter
and, when the service and model names collide, emit constructor calls using the
aliased model identifier (e.g., FooModel) so example code references the correct
symbol.

In `@templates/python/package/models/request_model.py.twig`:
- Around line 50-52: The current to_dict() implementation in AppwriteModel calls
model_dump(by_alias=True, mode='json') (see base_model.py.twig and
templates/python/package/models/request_model.py.twig) which causes unset
optional fields to be serialized as null; update to call
model_dump(by_alias=True, mode='json', exclude_unset=True) so that omitted
fields are excluded while still preserving explicitly-set nulls for nullable
properties — modify the AppwriteModel.to_dict() (or the function that invokes
model_dump) to add exclude_unset=True and run existing tests to ensure nullable
fields remain serializable when explicitly set.

In `@templates/python/package/services/service.py.twig`:
- Around line 18-27: The import block in
templates/python/package/services/service.py.twig only checks parameter.model so
array-of-model parameters (parameter.array.model) aren't imported causing
NameError; update the import logic to derive a single modelName (e.g.
parameter.model ?? parameter.array.model ?? null) and use that for the existing
added-check and import generation (adjust the existing conditional that
references parameter.model to reference modelName instead) so List[Model] types
emitted by getServicePropertyType() have their model imported.

---

Nitpick comments:
In `@tests/languages/python/tests.py`:
- Around line 89-97: Add a test that constructs and passes a generated
request-model instance (instead of a raw dict) to the client to exercise
AppwriteModel normalization/serialization; specifically, import the generated
model class for the player request (e.g., the Player or CreatePlayerRequest
model) and call general.create_player(...) with an instance of that model
(mirroring the existing dict payload used by
general.create_player/general.create_players) so the AppwriteModel code path is
exercised during tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5b64c9a8-a7db-4d03-9b3c-a6d44b522b27

📥 Commits

Reviewing files that changed from the base of the PR and between 0e70dc8 and 43546ba.

📒 Files selected for processing (6)
  • src/SDK/Language/Python.php
  • templates/python/docs/example.md.twig
  • templates/python/package/models/model.py.twig
  • templates/python/package/models/request_model.py.twig
  • templates/python/package/services/service.py.twig
  • tests/languages/python/tests.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • templates/python/package/models/model.py.twig

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@templates/python/docs/example.md.twig`:
- Around line 62-63: The generated Python return type uses Union but the
template never imports it; update the template to conditionally add "from typing
import Union" when multiple response models are present by duplicating the
filter logic used to compute validResponseModels (i.e. check "{% if
method.responseModels is defined and method.responseModels | filter((m) => m and
m != 'any') | length > 1 %}" before the imports) or alternatively move the
existing validResponseModels computation ahead of the import block so you can
use "{% if validResponseModels | length > 1 %}" to emit the import; modify the
import block accordingly so result: Union[...] has a matching import.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 33838c89-a576-43c6-827a-4bef8b1cc1ef

📥 Commits

Reviewing files that changed from the base of the PR and between 43546ba and 4fdf6d8.

📒 Files selected for processing (1)
  • templates/python/docs/example.md.twig

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
templates/python/package/services/service.py.twig (1)

18-28: ⚠️ Potential issue | 🟠 Major

Use a non-empty fallback when picking modelName.

Line 18 stops at parameter.model even when it is an empty string, but Line 103 still resolves array-of-model annotations from array.model via getServicePropertyType(). In that shape, the generated signature becomes List[Foo] without importing Foo, so the service module still breaks on import. The same selector is repeated in templates/python/docs/example.md.twig:49-58 and src/SDK/Language/Python.php:489-503, so it is worth fixing all three together.

Suggested fix
-{% set modelName = parameter.model ?? parameter.array.model ?? null %}
+{% set modelName = parameter.model is not empty ? parameter.model : (parameter.array.model is not empty ? parameter.array.model : null) %}
 {% if modelName %}
 {% if modelName not in added %}
 {% if (modelName | caseUcfirst) == (service.name | caseUcfirst) %}
 from ..models.{{ modelName | caseSnake }} import {{ modelName | caseUcfirst }} as {{ modelName | caseUcfirst }}Model;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@templates/python/package/services/service.py.twig` around lines 18 - 28, The
code selects modelName using parameter.model even when it's an empty string,
causing imports to be skipped for array models; change the selection to prefer a
non-empty value (e.g., set modelName to parameter.model if parameter.model is
defined and not empty, otherwise to parameter.array.model when present) so the
import logic that checks added and generates the from ..models import runs
correctly; apply the same non-empty fallback fix in the other two affected
locations (the docs example template and the PHP logic around
getServicePropertyType()) and ensure the conditional uses a truthy check rather
than just existence for variable modelName and for the merge into added.
🧹 Nitpick comments (1)
templates/python/package/models/base_model.py.twig (1)

23-27: Keep to_json() aligned with to_dict().

templates/python/package/service.py.twig:16-32 and templates/python/package/encoders/value_class_encoder.py.twig:7-17 both treat to_dict() as the canonical serializer and omit unset fields. to_json() currently keeps them, so the two public helpers can emit different payloads for the same model instance.

Suggested fix
     def to_dict(self) -> Dict[str, Any]:
         return self.model_dump(by_alias=True, mode='json', exclude_unset=True)
 
     def to_json(self) -> str:
-        return self.model_dump_json(by_alias=True)
+        return self.model_dump_json(by_alias=True, exclude_unset=True)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@templates/python/package/models/base_model.py.twig` around lines 23 - 27, The
to_json() method must match to_dict()'s behaviour: update to_json() (currently
calling model_dump_json) to pass the same arguments used by to_dict() —
by_alias=True, mode='json', and exclude_unset=True — so both public serializers
(to_dict and to_json) produce identical payloads for the same model instance;
locate the to_json and to_dict methods in base_model.py.twig and ensure
model_dump_json is called with those exact kwargs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/SDK/Language/Python.php`:
- Around line 648-671: getResponseType currently returns raw model names and can
produce types that collide with the service class; update it to use the
collision-aware naming helper getDocsModelTypeName instead of getModelName when
resolving response model names (both inside the array_map for responseModels and
the single responseModel branch), and ensure the union created via getUnionType
uses those collision-aware names so the generated Returns doc in service.py.twig
references valid types.

---

Duplicate comments:
In `@templates/python/package/services/service.py.twig`:
- Around line 18-28: The code selects modelName using parameter.model even when
it's an empty string, causing imports to be skipped for array models; change the
selection to prefer a non-empty value (e.g., set modelName to parameter.model if
parameter.model is defined and not empty, otherwise to parameter.array.model
when present) so the import logic that checks added and generates the from
..models import runs correctly; apply the same non-empty fallback fix in the
other two affected locations (the docs example template and the PHP logic around
getServicePropertyType()) and ensure the conditional uses a truthy check rather
than just existence for variable modelName and for the merge into added.

---

Nitpick comments:
In `@templates/python/package/models/base_model.py.twig`:
- Around line 23-27: The to_json() method must match to_dict()'s behaviour:
update to_json() (currently calling model_dump_json) to pass the same arguments
used by to_dict() — by_alias=True, mode='json', and exclude_unset=True — so both
public serializers (to_dict and to_json) produce identical payloads for the same
model instance; locate the to_json and to_dict methods in base_model.py.twig and
ensure model_dump_json is called with those exact kwargs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: af7faeb0-918b-46d2-bc88-a3eb123454e9

📥 Commits

Reviewing files that changed from the base of the PR and between 4fdf6d8 and 30e5134.

📒 Files selected for processing (5)
  • src/SDK/Language/Python.php
  • templates/python/docs/example.md.twig
  • templates/python/package/models/base_model.py.twig
  • templates/python/package/services/service.py.twig
  • tests/languages/python/tests.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/languages/python/tests.py

Add generic type support for models with additionalProperties (e.g., Row):

- Add hasGenericType() method and Twig filters to detect generic models
- Update model.py.twig to generate Generic[T] classes with:
  - PrivateAttr _data for storing typed user data
  - data property with type T for IDE autocomplete
  - with_data() class method for typed deserialization
  - to_dict() override for proper serialization
- Update service.py.twig to accept model_type parameter
- Update api.twig to use with_data() for generic response models
- Support nested generic types (RowList -> Row[T])

Usage:
  row = tables.get_row(..., model_type=Post)
  print(row.data.title)  # Fully typed access

  rows = tables.list_rows(..., model_type=Post)
  for row in rows.rows:
      print(row.data.title)  # Also typed!
@ChiragAgg5k ChiragAgg5k force-pushed the feat/python-pydantic-models branch from edbc435 to 46fb9f7 Compare March 7, 2026 13:06
@ChiragAgg5k ChiragAgg5k force-pushed the feat/python-pydantic-models branch from 46fb9f7 to 604d389 Compare March 11, 2026 02:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants