diff --git a/docs/migrations/version-3-to-4.md b/docs/migrations/version-3-to-4.md index e90a1dd2f9..a33d27ff02 100644 --- a/docs/migrations/version-3-to-4.md +++ b/docs/migrations/version-3-to-4.md @@ -138,4 +138,74 @@ If you are using the Python preset `PYTHON_JSON_SERIALIZER_PRESET`, the function - deserializeFromJson + deserialize_from_json -``` \ No newline at end of file +``` + +### Type hints +Classes now have type hints on all properties and accessor functions. + +Before: +```python +from typing import Any, Dict +class ObjProperty: + def __init__(self, input): + if hasattr(input, 'number'): + self._number = input['number'] + if hasattr(input, 'additional_properties'): + self._additional_properties = input['additional_properties'] + + @property + def number(self): + return self._number + @number.setter + def number(self, number): + self._number = number + + @property + def additional_properties(self): + return self._additional_properties + @additional_properties.setter + def additional_properties(self, additional_properties): + self._additional_properties = additional_properties +``` + +After: +```python +from typing import Any, Dict +class ObjProperty: + def __init__(self, input: Dict): + if hasattr(input, 'number'): + self._number: float = input['number'] + if hasattr(input, 'additional_properties'): + self._additional_properties: dict[str, Any] = input['additional_properties'] + + @property + def number(self) -> float: + return self._number + @number.setter + def number(self, number: float): + self._number = number + + @property + def additional_properties(self) -> dict[str, Any]: + return self._additional_properties + @additional_properties.setter + def additional_properties(self, additional_properties: dict[str, Any]): + self._additional_properties = additional_properties +``` + +### Initialization of correct models +Before in the constructor, if a property was another class, they would not correctly be initialized. Now a new instance of the object is created. + +### Constants are now rendered +Before, constants where completely ignored, now they are respected and also means you don't have the possibility to change it through setters for example. + +```python +class Address: + def __init__(self, input: Dict): + self._street_name: str = 'someAddress' + + @property + def street_name(self) -> str: + return self._street_name +``` + diff --git a/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap b/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap index 327eb6d235..b65fc34153 100644 --- a/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap +++ b/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap @@ -3,26 +3,26 @@ exports[`Should be able to render python models and should generate \`implicit\` or \`explicit\` imports for models implementing referenced types: nested-model 1`] = ` Array [ " - +from typing import Any, Dict class ObjProperty: - def __init__(self, input): + def __init__(self, input: Dict): if hasattr(input, 'number'): - self._number = input.number + self._number: float = input['number'] if hasattr(input, 'additional_properties'): - self._additional_properties = input.additional_properties + self._additional_properties: dict[str, Any] = input['additional_properties'] @property - def number(self): + def number(self) -> float: return self._number @number.setter - def number(self, number): + def number(self, number: float): self._number = number @property - def additional_properties(self): + def additional_properties(self) -> dict[str, Any]: return self._additional_properties @additional_properties.setter - def additional_properties(self, additional_properties): + def additional_properties(self, additional_properties: dict[str, Any]): self._additional_properties = additional_properties ", ] @@ -31,26 +31,26 @@ class ObjProperty: exports[`Should be able to render python models and should generate \`implicit\` or \`explicit\` imports for models implementing referenced types: nested-model 2`] = ` Array [ " - +from typing import Any, Dict class ObjProperty: - def __init__(self, input): + def __init__(self, input: Dict): if hasattr(input, 'number'): - self._number = input.number + self._number: float = input['number'] if hasattr(input, 'additional_properties'): - self._additional_properties = input.additional_properties + self._additional_properties: dict[str, Any] = input['additional_properties'] @property - def number(self): + def number(self) -> float: return self._number @number.setter - def number(self, number): + def number(self, number: float): self._number = number @property - def additional_properties(self): + def additional_properties(self) -> dict[str, Any]: return self._additional_properties @additional_properties.setter - def additional_properties(self, additional_properties): + def additional_properties(self, additional_properties: dict[str, Any]): self._additional_properties = additional_properties ", ] @@ -59,26 +59,26 @@ class ObjProperty: exports[`Should be able to render python models and should generate \`implicit\` or \`explicit\` imports for models implementing referenced types: root-model-explicit-import 1`] = ` Array [ "from ObjProperty import ObjProperty - +from typing import Any, Dict class Root: - def __init__(self, input): + def __init__(self, input: Dict): if hasattr(input, 'email'): - self._email = input.email + self._email: str = input['email'] if hasattr(input, 'obj_property'): - self._obj_property = input.obj_property + self._obj_property: ObjProperty = ObjProperty(input['obj_property']) @property - def email(self): + def email(self) -> str: return self._email @email.setter - def email(self, email): + def email(self, email: str): self._email = email @property - def obj_property(self): + def obj_property(self) -> ObjProperty: return self._obj_property @obj_property.setter - def obj_property(self, obj_property): + def obj_property(self, obj_property: ObjProperty): self._obj_property = obj_property ", ] @@ -87,26 +87,26 @@ class Root: exports[`Should be able to render python models and should generate \`implicit\` or \`explicit\` imports for models implementing referenced types: root-model-implicit-import 1`] = ` Array [ "from ObjProperty import ObjProperty - +from typing import Any, Dict class Root: - def __init__(self, input): + def __init__(self, input: Dict): if hasattr(input, 'email'): - self._email = input.email + self._email: str = input['email'] if hasattr(input, 'obj_property'): - self._obj_property = input.obj_property + self._obj_property: ObjProperty = ObjProperty(input['obj_property']) @property - def email(self): + def email(self) -> str: return self._email @email.setter - def email(self, email): + def email(self, email: str): self._email = email @property - def obj_property(self): + def obj_property(self) -> ObjProperty: return self._obj_property @obj_property.setter - def obj_property(self, obj_property): + def obj_property(self, obj_property: ObjProperty): self._obj_property = obj_property ", ] diff --git a/examples/generate-python-models/__snapshots__/index.spec.ts.snap b/examples/generate-python-models/__snapshots__/index.spec.ts.snap index 89930c1e37..d417a91f92 100644 --- a/examples/generate-python-models/__snapshots__/index.spec.ts.snap +++ b/examples/generate-python-models/__snapshots__/index.spec.ts.snap @@ -3,15 +3,15 @@ exports[`Should be able to render python models and should log expected output to console 1`] = ` Array [ "class Root: - def __init__(self, input): + def __init__(self, input: Dict): if hasattr(input, 'email'): - self._email = input.email + self._email: str = input['email'] @property - def email(self): + def email(self) -> str: return self._email @email.setter - def email(self, email): + def email(self, email: str): self._email = email ", ] diff --git a/examples/python-generate-json-serializer-and-deserializer/__snapshots__/index.spec.ts.snap b/examples/python-generate-json-serializer-and-deserializer/__snapshots__/index.spec.ts.snap index 515f8473a7..30113f1a8f 100644 --- a/examples/python-generate-json-serializer-and-deserializer/__snapshots__/index.spec.ts.snap +++ b/examples/python-generate-json-serializer-and-deserializer/__snapshots__/index.spec.ts.snap @@ -3,24 +3,24 @@ exports[`Should be able to render JSON serialization and deserialization functions and should log expected output to console 1`] = ` Array [ "class Root: - def __init__(self, input): + def __init__(self, input: Dict): if hasattr(input, 'email'): - self._email = input.email + self._email: str = input['email'] if hasattr(input, 'additional_properties'): - self._additional_properties = input.additional_properties + self._additional_properties: dict[str, Any] = input['additional_properties'] @property - def email(self): + def email(self) -> str: return self._email @email.setter - def email(self, email): + def email(self, email: str): self._email = email @property - def additional_properties(self): + def additional_properties(self) -> dict[str, Any]: return self._additional_properties @additional_properties.setter - def additional_properties(self, additional_properties): + def additional_properties(self, additional_properties: dict[str, Any]): self._additional_properties = additional_properties def serialize_to_json(self): diff --git a/src/generators/python/PythonConstrainer.ts b/src/generators/python/PythonConstrainer.ts index 3acaaaf622..cd6971c424 100644 --- a/src/generators/python/PythonConstrainer.ts +++ b/src/generators/python/PythonConstrainer.ts @@ -16,7 +16,8 @@ export const PythonDefaultTypeMapping: PythonTypeMapping = { Reference({ constrainedModel }): string { return constrainedModel.name; }, - Any(): string { + Any({ dependencyManager }): string { + dependencyManager.addDependency('from typing import Any'); return 'Any'; }, Float(): string { @@ -37,8 +38,9 @@ export const PythonDefaultTypeMapping: PythonTypeMapping = { }); return `tuple[${tupleTypes.join(', ')}]`; }, - Array({ constrainedModel }): string { - return `list[${constrainedModel.valueModel.type}]`; + Array({ constrainedModel, dependencyManager }): string { + dependencyManager.addDependency('from typing import List'); + return `List[${constrainedModel.valueModel.type}]`; }, Enum({ constrainedModel }): string { //Returning name here because all enum models have been split out diff --git a/src/generators/python/PythonDependencyManager.ts b/src/generators/python/PythonDependencyManager.ts index 8df1d1073f..372864e0ae 100644 --- a/src/generators/python/PythonDependencyManager.ts +++ b/src/generators/python/PythonDependencyManager.ts @@ -19,4 +19,43 @@ export class PythonDependencyManager extends AbstractDependencyManager { model.name }`; } + + /** + * Renders all added dependencies a single time. + * + * For example `from typing import Dict` and `from typing import Any` would form a single import `from typing import Dict, Any` + */ + renderDependencies(): string[] { + const importMap: Record = {}; + const dependenciesToRender = []; + /** + * Split up each dependency that matches `from x import y` and keep everything else as is. + * + * Merge all `y` together and make sure they are unique and render the dependency as `from x import y1, y2, y3` + */ + for (const dependency of this.dependencies) { + const regex = /from ([A-Za-z0-9]+) import ([A-Za-z0-9,\s]+)/g; + const matches = regex.exec(dependency); + + if (!matches) { + dependenciesToRender.push(dependency); + } else { + const from = matches[1]; + if (!importMap[`${from}`]) { + importMap[`${from}`] = []; + } + const toImport = matches[2] + .split(',') + .map((importMatch) => importMatch.trim()); + importMap[`${from}`].push(...toImport); + } + } + for (const [from, toImport] of Object.entries(importMap)) { + const uniqueToImport = [...new Set(toImport)]; + dependenciesToRender.push( + `from ${from} import ${uniqueToImport.join(', ')}` + ); + } + return dependenciesToRender; + } } diff --git a/src/generators/python/PythonGenerator.ts b/src/generators/python/PythonGenerator.ts index d9ee6fca93..cc9024e1c8 100644 --- a/src/generators/python/PythonGenerator.ts +++ b/src/generators/python/PythonGenerator.ts @@ -239,7 +239,7 @@ ${outputModel.result}`; return RenderOutput.toRenderOutput({ result, renderedName: model.name, - dependencies: dependencyManagerToUse.dependencies + dependencies: dependencyManagerToUse.renderDependencies() }); } @@ -266,7 +266,7 @@ ${outputModel.result}`; return RenderOutput.toRenderOutput({ result, renderedName: model.name, - dependencies: dependencyManagerToUse.dependencies + dependencies: dependencyManagerToUse.renderDependencies() }); } } diff --git a/src/generators/python/constrainer/ConstantConstrainer.ts b/src/generators/python/constrainer/ConstantConstrainer.ts index 44bd50358d..82271cf9f9 100644 --- a/src/generators/python/constrainer/ConstantConstrainer.ts +++ b/src/generators/python/constrainer/ConstantConstrainer.ts @@ -1,7 +1,53 @@ +import { + ConstrainedMetaModel, + ConstrainedEnumModel, + ConstrainedMetaModelOptionsConst, + ConstrainedReferenceModel, + ConstrainedStringModel +} from '../../../models'; import { PythonConstantConstraint } from '../PythonGenerator'; +const getConstrainedEnumModelConstant = (args: { + constrainedMetaModel: ConstrainedMetaModel; + constrainedEnumModel: ConstrainedEnumModel; + constOptions: ConstrainedMetaModelOptionsConst; +}) => { + const constrainedEnumValueModel = args.constrainedEnumModel.values.find( + (value) => value.originalInput === args.constOptions.originalInput + ); + + if (constrainedEnumValueModel) { + return `${args.constrainedMetaModel.type}.${constrainedEnumValueModel.key}`; + } +}; + export function defaultConstantConstraints(): PythonConstantConstraint { - return () => { + return ({ constrainedMetaModel }) => { + const constOptions = constrainedMetaModel.options.const; + + if (!constOptions) { + return undefined; + } + + if ( + constrainedMetaModel instanceof ConstrainedReferenceModel && + constrainedMetaModel.ref instanceof ConstrainedEnumModel + ) { + return getConstrainedEnumModelConstant({ + constrainedMetaModel, + constrainedEnumModel: constrainedMetaModel.ref, + constOptions + }); + } else if (constrainedMetaModel instanceof ConstrainedEnumModel) { + return getConstrainedEnumModelConstant({ + constrainedMetaModel, + constrainedEnumModel: constrainedMetaModel, + constOptions + }); + } else if (constrainedMetaModel instanceof ConstrainedStringModel) { + return `'${constOptions.originalInput}'`; + } + return undefined; }; } diff --git a/src/generators/python/renderers/ClassRenderer.ts b/src/generators/python/renderers/ClassRenderer.ts index 4c52d56709..326b758f03 100644 --- a/src/generators/python/renderers/ClassRenderer.ts +++ b/src/generators/python/renderers/ClassRenderer.ts @@ -1,7 +1,8 @@ import { PythonRenderer } from '../PythonRenderer'; import { ConstrainedObjectModel, - ConstrainedObjectPropertyModel + ConstrainedObjectPropertyModel, + ConstrainedReferenceModel } from '../../../models'; import { PythonOptions } from '../PythonGenerator'; import { ClassPresetType } from '../PythonPreset'; @@ -81,33 +82,49 @@ export const PYTHON_DEFAULT_CLASS_PRESET: ClassPresetType = { const properties = model.properties || {}; let body = ''; if (Object.keys(properties).length > 0) { - const assigments = Object.values(properties).map((property) => { + const assignments = Object.values(properties).map((property) => { + if (property.property.options.const) { + return `self._${property.propertyName}: ${property.property.type} = ${property.property.options.const.value}`; + } + let assignment: string; + if (property.property instanceof ConstrainedReferenceModel) { + assignment = `self._${property.propertyName}: ${property.property.type} = ${property.property.type}(input['${property.propertyName}'])`; + } else { + assignment = `self._${property.propertyName}: ${property.property.type} = input['${property.propertyName}']`; + } if (!property.required) { - const propAssignment = `self._${property.propertyName} = input.${property.propertyName}`; return `if hasattr(input, '${property.propertyName}'): -${renderer.indent(propAssignment, 2)}`; +${renderer.indent(assignment, 2)}`; } - return `self._${property.propertyName} = input.${property.propertyName}`; + return assignment; }); - body = renderer.renderBlock(assigments); + body = renderer.renderBlock(assignments); } else { body = `""" No properties """`; } - return `def __init__(self, input): + renderer.dependencyManager.addDependency(`from typing import Dict`); + return `def __init__(self, input: Dict): ${renderer.indent(body, 2)}`; }, getter({ property, renderer }) { const propAssignment = `return self._${property.propertyName}`; return `@property -def ${property.propertyName}(self): +def ${property.propertyName}(self) -> ${property.property.type}: ${renderer.indent(propAssignment, 2)}`; }, setter({ property, renderer }) { + // if const value exists we should not render a setter + if (property.property.options.const?.value) { + return ''; + } + const propAssignment = `self._${property.propertyName} = ${property.propertyName}`; + const propArgument = `${property.propertyName}: ${property.property.type}`; + return `@${property.propertyName}.setter -def ${property.propertyName}(self, ${property.propertyName}): +def ${property.propertyName}(self, ${propArgument}): ${renderer.indent(propAssignment, 2)}`; } }; diff --git a/test/generators/python/PythonConstrainer.spec.ts b/test/generators/python/PythonConstrainer.spec.ts index 19651e1d45..3709204020 100644 --- a/test/generators/python/PythonConstrainer.spec.ts +++ b/test/generators/python/PythonConstrainer.spec.ts @@ -53,11 +53,18 @@ describe('PythonConstrainer', () => { describe('Any', () => { test('should render type', () => { const model = new ConstrainedAnyModel('test', undefined, {}, ''); + const dependencyManager = new PythonDependencyManager( + PythonGenerator.defaultOptions + ); const type = PythonDefaultTypeMapping.Any({ constrainedModel: model, - ...defaultOptions + ...defaultOptions, + dependencyManager }); expect(type).toEqual('Any'); + expect(dependencyManager.dependencies).toEqual([ + 'from typing import Any' + ]); }); }); describe('Float', () => { @@ -159,7 +166,7 @@ describe('PythonConstrainer', () => { constrainedModel: model, ...defaultOptions }); - expect(type).toEqual('list[str]'); + expect(type).toEqual('List[str]'); }); }); diff --git a/test/generators/python/PythonDependencyManager.spec.ts b/test/generators/python/PythonDependencyManager.spec.ts new file mode 100644 index 0000000000..6676bdac86 --- /dev/null +++ b/test/generators/python/PythonDependencyManager.spec.ts @@ -0,0 +1,22 @@ +import { PythonGenerator } from '../../../src/generators'; +import { PythonDependencyManager } from '../../../src/generators/python/PythonDependencyManager'; +describe('PythonDependencyManager', () => { + describe('renderDependencies()', () => { + test('should render simple dependency', () => { + const dependencyManager = new PythonDependencyManager( + PythonGenerator.defaultOptions, + ['import json'] + ); + expect(dependencyManager.renderDependencies()).toEqual([`import json`]); + }); + test('should render unique dependency', () => { + const dependencyManager = new PythonDependencyManager( + PythonGenerator.defaultOptions, + ['from x import y', 'from x import y2'] + ); + expect(dependencyManager.renderDependencies()).toEqual([ + `from x import y, y2` + ]); + }); + }); +}); diff --git a/test/generators/python/PythonGenerator.spec.ts b/test/generators/python/PythonGenerator.spec.ts index d98c5d8e7e..e9dee10f67 100644 --- a/test/generators/python/PythonGenerator.spec.ts +++ b/test/generators/python/PythonGenerator.spec.ts @@ -68,6 +68,19 @@ describe('PythonGenerator', () => { expect(models[0].result).toMatchSnapshot(); }); + test('should render constant types', async () => { + const doc = { + $id: 'Address', + type: 'object', + properties: { + street_name: { type: 'string', const: 'someAddress' } + }, + additionalProperties: false + }; + const models = await generator.generate(doc); + expect(models).toHaveLength(1); + expect(models[0].result).toMatchSnapshot(); + }); test('should render `class` type', async () => { const doc = { $id: 'Address', @@ -96,11 +109,9 @@ describe('PythonGenerator', () => { }, required: ['street_name', 'city', 'state', 'house_number', 'array_type'] }; - const expectedDependencies: string[] = []; const models = await generator.generate(doc); expect(models).toHaveLength(1); expect(models[0].result).toMatchSnapshot(); - expect(models[0].dependencies).toEqual(expectedDependencies); }); test('should work with custom preset for `class` type', async () => { @@ -131,12 +142,10 @@ describe('PythonGenerator', () => { } ] }); - const expectedDependencies: string[] = []; const models = await generator.generate(doc); expect(models).toHaveLength(1); expect(models[0].result).toMatchSnapshot(); - expect(models[0].dependencies).toEqual(expectedDependencies); }); test('should work with empty objects', async () => { const doc = { @@ -145,12 +154,10 @@ describe('PythonGenerator', () => { additionalProperties: false }; generator = new PythonGenerator(); - const expectedDependencies: string[] = []; const models = await generator.generate(doc); expect(models).toHaveLength(1); expect(models[0].result).toMatchSnapshot(); - expect(models[0].dependencies).toEqual(expectedDependencies); }); }); }); diff --git a/test/generators/python/PythonRenderer.spec.ts b/test/generators/python/PythonRenderer.spec.ts index d5f2c70858..9025e6774d 100644 --- a/test/generators/python/PythonRenderer.spec.ts +++ b/test/generators/python/PythonRenderer.spec.ts @@ -11,7 +11,7 @@ describe('PythonRenderer', () => { PythonGenerator.defaultOptions, new PythonGenerator(), [], - new ConstrainedObjectModel('', undefined, '', {}), + new ConstrainedObjectModel('', undefined, {}, '', {}), new InputMetaModel(), new PythonDependencyManager(PythonGenerator.defaultOptions) ); diff --git a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap index 51ba4f0538..be589e3ed0 100644 --- a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap +++ b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap @@ -2,92 +2,103 @@ exports[`PythonGenerator Class should not render reserved keyword 1`] = ` "class Address: - def __init__(self, input): + def __init__(self, input: Dict): if hasattr(input, 'reserved_del'): - self._reserved_del = input.reserved_del + self._reserved_del: str = input['reserved_del'] @property - def reserved_del(self): + def reserved_del(self) -> str: return self._reserved_del @reserved_del.setter - def reserved_del(self, reserved_del): + def reserved_del(self, reserved_del: str): self._reserved_del = reserved_del " `; exports[`PythonGenerator Class should render \`class\` type 1`] = ` "class Address: - def __init__(self, input): - self._street_name = input.street_name - self._city = input.city - self._state = input.state - self._house_number = input.house_number + def __init__(self, input: Dict): + self._street_name: str = input['street_name'] + self._city: str = input['city'] + self._state: str = input['state'] + self._house_number: float = input['house_number'] if hasattr(input, 'marriage'): - self._marriage = input.marriage + self._marriage: bool = input['marriage'] if hasattr(input, 'members'): - self._members = input.members - self._array_type = input.array_type + self._members: str | float | bool = input['members'] + self._array_type: List[str | float | Any] = input['array_type'] if hasattr(input, 'additional_properties'): - self._additional_properties = input.additional_properties + self._additional_properties: dict[str, Any | str] = input['additional_properties'] @property - def street_name(self): + def street_name(self) -> str: return self._street_name @street_name.setter - def street_name(self, street_name): + def street_name(self, street_name: str): self._street_name = street_name @property - def city(self): + def city(self) -> str: return self._city @city.setter - def city(self, city): + def city(self, city: str): self._city = city @property - def state(self): + def state(self) -> str: return self._state @state.setter - def state(self, state): + def state(self, state: str): self._state = state @property - def house_number(self): + def house_number(self) -> float: return self._house_number @house_number.setter - def house_number(self, house_number): + def house_number(self, house_number: float): self._house_number = house_number @property - def marriage(self): + def marriage(self) -> bool: return self._marriage @marriage.setter - def marriage(self, marriage): + def marriage(self, marriage: bool): self._marriage = marriage @property - def members(self): + def members(self) -> str | float | bool: return self._members @members.setter - def members(self, members): + def members(self, members: str | float | bool): self._members = members @property - def array_type(self): + def array_type(self) -> List[str | float | Any]: return self._array_type @array_type.setter - def array_type(self, array_type): + def array_type(self, array_type: List[str | float | Any]): self._array_type = array_type @property - def additional_properties(self): + def additional_properties(self) -> dict[str, Any | str]: return self._additional_properties @additional_properties.setter - def additional_properties(self, additional_properties): + def additional_properties(self, additional_properties: dict[str, Any | str]): self._additional_properties = additional_properties " `; +exports[`PythonGenerator Class should render constant types 1`] = ` +"class Address: + def __init__(self, input: Dict): + self._street_name: str = 'someAddress' + + @property + def street_name(self) -> str: + return self._street_name +" +`; + exports[`PythonGenerator Class should work with custom preset for \`class\` type 1`] = ` "class CustomClass: test1 @@ -95,35 +106,35 @@ exports[`PythonGenerator Class should work with custom preset for \`class\` type test1 - def __init__(self, input): + def __init__(self, input: Dict): if hasattr(input, 'property'): - self._property = input.property + self._property: str = input['property'] if hasattr(input, 'additional_properties'): - self._additional_properties = input.additional_properties + self._additional_properties: dict[str, Any] = input['additional_properties'] test2 @property - def property(self): + def property(self) -> str: return self._property test3 @property.setter - def property(self, property): + def property(self, property: str): self._property = property test2 @property - def additional_properties(self): + def additional_properties(self) -> dict[str, Any]: return self._additional_properties test3 @additional_properties.setter - def additional_properties(self, additional_properties): + def additional_properties(self, additional_properties: dict[str, Any]): self._additional_properties = additional_properties " `; exports[`PythonGenerator Class should work with empty objects 1`] = ` "class CustomClass: - def __init__(self, input): + def __init__(self, input: Dict): \\"\\"\\" No properties \\"\\"\\" diff --git a/test/generators/python/constrainer/ConstantConstrainer.spec.ts b/test/generators/python/constrainer/ConstantConstrainer.spec.ts index f41fcc3fad..812d4c827b 100644 --- a/test/generators/python/constrainer/ConstantConstrainer.spec.ts +++ b/test/generators/python/constrainer/ConstantConstrainer.spec.ts @@ -1,3 +1,10 @@ +import { + ConstrainedStringModel, + PythonGenerator, + ConstrainedReferenceModel, + ConstrainedEnumModel, + ConstrainedEnumValueModel +} from '../../../../src'; import { defaultConstantConstraints } from '../../../../src/generators/python/constrainer/ConstantConstrainer'; const mockConstantContext = { @@ -5,15 +12,73 @@ const mockConstantContext = { }; describe('defaultConstantConstraints', () => { - it('should return a function that returns undefined', () => { - const constantConstraintsFunction = defaultConstantConstraints(); - const result = constantConstraintsFunction(mockConstantContext); - expect(result).toBeUndefined(); + test('should not render anything if const options are not set', () => { + const constrainedConstant = defaultConstantConstraints()({ + constrainedMetaModel: new ConstrainedStringModel( + 'testStringModel', + undefined, + {}, + 'String' + ), + options: PythonGenerator.defaultOptions + }); + expect(constrainedConstant).toBeUndefined(); }); - it('should return a ConstantConstraint type', () => { - const constantConstraintsFunction = defaultConstantConstraints(); - const result = constantConstraintsFunction; - expect(typeof result).toBe('function'); + describe('ConstrainedReferenceModel with ConstrainedEnumModel', () => { + test('should render enum', () => { + const constrainedConstant = defaultConstantConstraints()({ + constrainedMetaModel: new ConstrainedReferenceModel( + 'testRefModel', + undefined, + { + const: { + originalInput: 'testValue' + } + }, + 'TestRefType', + new ConstrainedEnumModel( + 'testEnumModel', + undefined, + {}, + 'TestEnumType', + [new ConstrainedEnumValueModel('testKey', 'testValue', 'testValue')] + ) + ), + options: PythonGenerator.defaultOptions + }); + expect(constrainedConstant).toEqual('TestRefType.testKey'); + }); + }); + + describe('ConstrainedEnumModel', () => { + test('should render enum', () => { + const constrainedConstant = defaultConstantConstraints()({ + constrainedMetaModel: new ConstrainedEnumModel( + 'testEnumModel', + undefined, + { const: { originalInput: 'testValue' } }, + 'TestEnumType', + [new ConstrainedEnumValueModel('testKey', 'testValue', 'testValue')] + ), + options: PythonGenerator.defaultOptions + }); + expect(constrainedConstant).toEqual('TestEnumType.testKey'); + }); + }); + + describe('ConstrainedStringModel', () => { + test('should render enum', () => { + const constrainedConstant = defaultConstantConstraints()({ + constrainedMetaModel: new ConstrainedStringModel( + 'testStringModel', + undefined, + { const: { originalInput: 'testValue' } }, + 'String' + ), + options: PythonGenerator.defaultOptions + }); + expect(constrainedConstant).toEqual("'testValue'"); + }); }); }); diff --git a/test/generators/python/presets/__snapshots__/JsonSerializer.spec.ts.snap b/test/generators/python/presets/__snapshots__/JsonSerializer.spec.ts.snap index 8db873f290..b337ade90b 100644 --- a/test/generators/python/presets/__snapshots__/JsonSerializer.spec.ts.snap +++ b/test/generators/python/presets/__snapshots__/JsonSerializer.spec.ts.snap @@ -2,24 +2,24 @@ exports[`PYTHON_JSON_SERIALIZER_PRESET should render serializer and deserializer for class 1`] = ` "class Test: - def __init__(self, input): + def __init__(self, input: Dict): if hasattr(input, 'prop'): - self._prop = input.prop + self._prop: str = input['prop'] if hasattr(input, 'additional_properties'): - self._additional_properties = input.additional_properties + self._additional_properties: dict[str, Any] = input['additional_properties'] @property - def prop(self): + def prop(self) -> str: return self._prop @prop.setter - def prop(self, prop): + def prop(self, prop: str): self._prop = prop @property - def additional_properties(self): + def additional_properties(self) -> dict[str, Any]: return self._additional_properties @additional_properties.setter - def additional_properties(self, additional_properties): + def additional_properties(self, additional_properties: dict[str, Any]): self._additional_properties = additional_properties def serialize_to_json(self):