From f364c88ebe19e07bf7747645c17f2b9795def26b Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Tue, 21 May 2024 09:28:36 +0200 Subject: [PATCH 1/2] temp --- .../python/PythonDependencyManager.ts | 2 +- src/generators/python/presets/Pydantic.ts | 58 ++++++++++++++++++- .../python/PythonDependencyManager.spec.ts | 4 +- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/generators/python/PythonDependencyManager.ts b/src/generators/python/PythonDependencyManager.ts index 0a892f7a19..45517e471e 100644 --- a/src/generators/python/PythonDependencyManager.ts +++ b/src/generators/python/PythonDependencyManager.ts @@ -41,7 +41,7 @@ export class PythonDependencyManager extends AbstractDependencyManager { const importMap: Record = {}; const dependenciesToRender = []; for (const dependency of individualDependencies) { - const regex = /from ([A-Za-z0-9]+) import ([A-Za-z0-9,\s]+)/g; + const regex = /from ([A-Za-z0-9]+) import ([A-Za-z0-9_\-,\s]+)/g; const matches = regex.exec(dependency); if (!matches) { diff --git a/src/generators/python/presets/Pydantic.ts b/src/generators/python/presets/Pydantic.ts index b47ddb6245..fb0fff7e38 100644 --- a/src/generators/python/presets/Pydantic.ts +++ b/src/generators/python/presets/Pydantic.ts @@ -1,5 +1,6 @@ import { ConstrainedDictionaryModel, + ConstrainedObjectPropertyModel, ConstrainedUnionModel } from '../../../models'; import { PythonOptions } from '../PythonGenerator'; @@ -64,7 +65,62 @@ const PYTHON_PYDANTIC_CLASS_PRESET: ClassPresetType = { }, ctor: () => '', getter: () => '', - setter: () => '' + setter: () => '', + additionalContent: ({ content, model, renderer }) => { + const allProperties = Object.keys(model.properties); + let dictionaryModel: ConstrainedObjectPropertyModel | undefined; + for (const property of Object.values(model.properties)) { + if ( + property.property instanceof ConstrainedDictionaryModel && + property.property.serializationType === 'unwrap' + ) { + dictionaryModel = property; + } + } + const shouldHaveFunctions = dictionaryModel !== undefined; + if (!shouldHaveFunctions) { + return content; + } + + renderer.dependencyManager.addDependency( + 'from pydantic import model_serializer, model_validator' + ); + // eslint-disable-next-line prettier/prettier + return `@model_serializer(mode='wrap') +def custom_serializer(self, handler): + serialized_self = handler(self) + ${dictionaryModel?.propertyName} = getattr(self, "${dictionaryModel?.propertyName}") + if ${dictionaryModel?.propertyName} is not None: + for key, value in ${dictionaryModel?.propertyName}.items(): + # Never overwrite existing values, to avoid clashes + if not hasattr(serialized_self, key): + serialized_self[key] = value + + return serialized_self + +@model_validator(mode='before') +@classmethod +def unwrap_${dictionaryModel?.propertyName}(cls, data): + json_properties = list(data.keys()) + known_object_properties = [${allProperties + .map((value) => `'${value}'`) + .join(', ')}] + unknown_object_properties = [element for element in json_properties if element not in known_object_properties] + # Ignore attempts that validate regular models, only when unknown input is used we add unwrap extensions + if len(unknown_object_properties) == 0: + return data + + known_json_properties = [${Object.values(model.properties) + .map((value) => `'${value.unconstrainedPropertyName}'`) + .join(', ')}] + ${dictionaryModel?.propertyName} = {} + for obj_key in list(data.keys()): + if not known_json_properties.__contains__(obj_key): + ${dictionaryModel?.propertyName}[obj_key] = data.pop(obj_key, None) + data['${dictionaryModel?.unconstrainedPropertyName}'] = ${dictionaryModel?.propertyName} + return data +${content}`; + } }; export const PYTHON_PYDANTIC_PRESET: PythonPreset = { diff --git a/test/generators/python/PythonDependencyManager.spec.ts b/test/generators/python/PythonDependencyManager.spec.ts index 6b3ded48b3..9ab1e15b11 100644 --- a/test/generators/python/PythonDependencyManager.spec.ts +++ b/test/generators/python/PythonDependencyManager.spec.ts @@ -12,10 +12,10 @@ describe('PythonDependencyManager', () => { test('should render unique dependency', () => { const dependencyManager = new PythonDependencyManager( PythonGenerator.defaultOptions, - ['from x import y', 'from x import y2'] + ['from x import y', 'from x import y2', 'from x import y_2'] ); expect(dependencyManager.renderDependencies()).toEqual([ - 'from x import y, y2' + 'from x import y, y2, y_2' ]); }); test('should render __future__ dependency first', () => { From d9787bf1c15cf1469c3012c170c1420000443c8f Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Mon, 24 Jun 2024 15:58:00 +0200 Subject: [PATCH 2/2] update snapshots --- .../__snapshots__/Pydantic.spec.ts.snap | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap b/test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap index ece4fb83fc..5b3293d316 100644 --- a/test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap +++ b/test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap @@ -7,6 +7,37 @@ exports[`PYTHON_PYDANTIC_PRESET should render pydantic for class 1`] = ` line description''', default=None) additional_properties: Optional[dict[str, Any]] = Field(exclude=True, default=None, alias='''additionalProperties''') + + @model_serializer(mode='wrap') + def custom_serializer(self, handler): + serialized_self = handler(self) + additional_properties = getattr(self, \\"additional_properties\\") + if additional_properties is not None: + for key, value in additional_properties.items(): + # Never overwrite existing values, to avoid clashes + if not hasattr(serialized_self, key): + serialized_self[key] = value + + return serialized_self + + @model_validator(mode='before') + @classmethod + def unwrap_additional_properties(cls, data): + json_properties = list(data.keys()) + known_object_properties = ['prop', 'additional_properties'] + unknown_object_properties = [element for element in json_properties if element not in known_object_properties] + # Ignore attempts that validate regular models, only when unknown input is used we add unwrap extensions + if len(unknown_object_properties) == 0: + return data + + known_json_properties = ['prop', 'additionalProperties'] + additional_properties = {} + for obj_key in list(data.keys()): + if not known_json_properties.__contains__(obj_key): + additional_properties[obj_key] = data.pop(obj_key, None) + data['additionalProperties'] = additional_properties + return data + " `; @@ -15,14 +46,107 @@ Array [ "class UnionTest(BaseModel): union_test: Optional[Union[Union1.Union1, Union2.Union2]] = Field(default=None, alias='''unionTest''') additional_properties: Optional[dict[str, Any]] = Field(exclude=True, default=None, alias='''additionalProperties''') + + @model_serializer(mode='wrap') + def custom_serializer(self, handler): + serialized_self = handler(self) + additional_properties = getattr(self, \\"additional_properties\\") + if additional_properties is not None: + for key, value in additional_properties.items(): + # Never overwrite existing values, to avoid clashes + if not hasattr(serialized_self, key): + serialized_self[key] = value + + return serialized_self + + @model_validator(mode='before') + @classmethod + def unwrap_additional_properties(cls, data): + json_properties = list(data.keys()) + known_object_properties = ['union_test', 'additional_properties'] + unknown_object_properties = [element for element in json_properties if element not in known_object_properties] + # Ignore attempts that validate regular models, only when unknown input is used we add unwrap extensions + if len(unknown_object_properties) == 0: + return data + + known_json_properties = ['unionTest', 'additionalProperties'] + additional_properties = {} + for obj_key in list(data.keys()): + if not known_json_properties.__contains__(obj_key): + additional_properties[obj_key] = data.pop(obj_key, None) + data['additionalProperties'] = additional_properties + return data + ", "class Union1(BaseModel): test_prop1: Optional[str] = Field(default=None, alias='''testProp1''') additional_properties: Optional[dict[str, Any]] = Field(exclude=True, default=None, alias='''additionalProperties''') + + @model_serializer(mode='wrap') + def custom_serializer(self, handler): + serialized_self = handler(self) + additional_properties = getattr(self, \\"additional_properties\\") + if additional_properties is not None: + for key, value in additional_properties.items(): + # Never overwrite existing values, to avoid clashes + if not hasattr(serialized_self, key): + serialized_self[key] = value + + return serialized_self + + @model_validator(mode='before') + @classmethod + def unwrap_additional_properties(cls, data): + json_properties = list(data.keys()) + known_object_properties = ['test_prop1', 'additional_properties'] + unknown_object_properties = [element for element in json_properties if element not in known_object_properties] + # Ignore attempts that validate regular models, only when unknown input is used we add unwrap extensions + if len(unknown_object_properties) == 0: + return data + + known_json_properties = ['testProp1', 'additionalProperties'] + additional_properties = {} + for obj_key in list(data.keys()): + if not known_json_properties.__contains__(obj_key): + additional_properties[obj_key] = data.pop(obj_key, None) + data['additionalProperties'] = additional_properties + return data + ", "class Union2(BaseModel): test_prop2: Optional[str] = Field(default=None, alias='''testProp2''') additional_properties: Optional[dict[str, Any]] = Field(exclude=True, default=None, alias='''additionalProperties''') + + @model_serializer(mode='wrap') + def custom_serializer(self, handler): + serialized_self = handler(self) + additional_properties = getattr(self, \\"additional_properties\\") + if additional_properties is not None: + for key, value in additional_properties.items(): + # Never overwrite existing values, to avoid clashes + if not hasattr(serialized_self, key): + serialized_self[key] = value + + return serialized_self + + @model_validator(mode='before') + @classmethod + def unwrap_additional_properties(cls, data): + json_properties = list(data.keys()) + known_object_properties = ['test_prop2', 'additional_properties'] + unknown_object_properties = [element for element in json_properties if element not in known_object_properties] + # Ignore attempts that validate regular models, only when unknown input is used we add unwrap extensions + if len(unknown_object_properties) == 0: + return data + + known_json_properties = ['testProp2', 'additionalProperties'] + additional_properties = {} + for obj_key in list(data.keys()): + if not known_json_properties.__contains__(obj_key): + additional_properties[obj_key] = data.pop(obj_key, None) + data['additionalProperties'] = additional_properties + return data + ", ] `;