Skip to content

Commit 7f50ce3

Browse files
authored
Merge pull request #1779 from appwrite/fix/python-serializer-field-selection
fix(python): honour include and exclude in generic model serializers
2 parents 9ca070e + 903104d commit 7f50ce3

1 file changed

Lines changed: 53 additions & 4 deletions

File tree

templates/python/package/models/model.py.twig

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{% set isGeneric = definition.additionalProperties or hasGenericProperty %}
33
from typing import Any, Dict, List, Optional, Union, cast{% if isGeneric %}, Generic, TypeVar, Type{% endif %}
44

5-
from pydantic import Field, PrivateAttr{{ definition.additionalProperties ? ', model_serializer' : '' }}
5+
from pydantic import Field, PrivateAttr{{ definition.additionalProperties ? ', TypeAdapter, model_serializer' : '' }}
66

77
from .base_model import AppwriteModel
88
{% set added = [] %}
@@ -31,6 +31,10 @@ from .{{ subSchema | caseSnake }} import {{ subSchema | caseUcfirst }}
3131

3232
T = TypeVar('T')
3333
{% endif %}
34+
{% if definition.additionalProperties %}
35+
36+
_PAYLOAD_ADAPTER = TypeAdapter(Dict[str, Any])
37+
{% endif %}
3438

3539
class {{ definition.name | caseUcfirst }}(AppwriteModel{% if isGeneric %}, Generic[T]{% endif %}):
3640
"""
@@ -113,26 +117,71 @@ class {{ definition.name | caseUcfirst }}(AppwriteModel{% if isGeneric %}, Gener
113117
def {{ 'data' | caseSnake | removeDollarSign }}(self, value: T) -> None:
114118
object.__setattr__(self, '_{{ 'data' | caseSnake | removeDollarSign }}', value)
115119

116-
def _serialize_data(self, info):
120+
def _serialize_data(self, info, include=None, exclude=None):
117121
if hasattr(self._{{ 'data' | caseSnake | removeDollarSign }}, 'model_dump'):
118122
return self._{{ 'data' | caseSnake | removeDollarSign }}.model_dump(
119123
mode=info.mode,
120124
by_alias=info.by_alias,
121125
exclude_unset=info.exclude_unset,
122126
exclude_defaults=info.exclude_defaults,
123127
exclude_none=info.exclude_none,
128+
include=include,
129+
exclude=exclude,
130+
)
131+
132+
if isinstance(self._{{ 'data' | caseSnake | removeDollarSign }}, dict) and (include is not None or exclude is not None):
133+
return _PAYLOAD_ADAPTER.dump_python(
134+
self._{{ 'data' | caseSnake | removeDollarSign }},
135+
mode=info.mode,
136+
by_alias=info.by_alias,
137+
exclude_unset=info.exclude_unset,
138+
exclude_defaults=info.exclude_defaults,
139+
exclude_none=info.exclude_none,
140+
include=include,
141+
exclude=exclude,
124142
)
125143

126144
return self._{{ 'data' | caseSnake | removeDollarSign }}
145+
{% if definition.properties | length > 0 %}
146+
147+
@staticmethod
148+
def _select_data(selector):
149+
"""
150+
Resolves a pydantic include/exclude selector against the '{{ 'data' }}' key, which is
151+
serialized here rather than declared as a field. Returns whether the key was
152+
named, and any nested selector to apply within it.
153+
"""
154+
if selector is None:
155+
return False, None
156+
157+
if isinstance(selector, dict):
158+
if '{{ 'data' }}' not in selector:
159+
return False, None
160+
161+
nested = selector['{{ 'data' }}']
162+
163+
return True, nested if isinstance(nested, (dict, set, frozenset, list, tuple)) else None
164+
165+
return '{{ 'data' }}' in selector, None
166+
{% endif %}
127167

128168
@model_serializer(mode='wrap')
129169
def _serialize_model(self, handler, info):
130170
result = handler(self)
131-
data = self._serialize_data(info)
132171
{% if definition.properties | length > 0 %}
133-
result['{{ 'data' }}'] = data
172+
included, include_fields = self._select_data(info.include)
173+
excluded, exclude_fields = self._select_data(info.exclude)
174+
175+
if info.include is not None and not included:
176+
return result
177+
178+
if excluded and exclude_fields is None:
179+
return result
180+
181+
result['{{ 'data' }}'] = self._serialize_data(info, include_fields, exclude_fields)
134182
return result
135183
{% else %}
184+
data = self._serialize_data(info, info.include, info.exclude)
136185
if isinstance(result, dict) and isinstance(data, dict):
137186
return {**result, **data}
138187

0 commit comments

Comments
 (0)