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 1f3271d74a..892f3ce60b 100644 --- a/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap +++ b/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap @@ -3,7 +3,7 @@ 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 class ObjProperty: def __init__(self, input: dict): if hasattr(input, \\"number\\"): @@ -31,7 +31,7 @@ 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 class ObjProperty: def __init__(self, input: dict): if hasattr(input, \\"number\\"): @@ -59,7 +59,7 @@ 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 class Root: def __init__(self, input: dict): if hasattr(input, \\"email\\"): @@ -87,7 +87,7 @@ 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 class Root: def __init__(self, input: dict): if hasattr(input, \\"email\\"): diff --git a/src/generators/python/PythonConstrainer.ts b/src/generators/python/PythonConstrainer.ts index 8da471ab6b..14606bca19 100644 --- a/src/generators/python/PythonConstrainer.ts +++ b/src/generators/python/PythonConstrainer.ts @@ -38,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..9cdaeba8b1 100644 --- a/src/generators/python/PythonDependencyManager.ts +++ b/src/generators/python/PythonDependencyManager.ts @@ -19,4 +19,44 @@ 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-z]+) import ([A-Za-z,\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()); + // eslint-disable-next-line security/detect-object-injection + 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/renderers/ClassRenderer.ts b/src/generators/python/renderers/ClassRenderer.ts index a563b7e5fa..f16ee6e79d 100644 --- a/src/generators/python/renderers/ClassRenderer.ts +++ b/src/generators/python/renderers/ClassRenderer.ts @@ -100,7 +100,8 @@ export const PYTHON_DEFAULT_CLASS_PRESET: ClassPresetType = { No properties """`; } - return `def __init__(self, input: dict): + renderer.dependencyManager.addDependency(`from typing import Dict`); + return `def __init__(self, input: Dict): ${renderer.indent(body)}`; }, getter({ property }) { diff --git a/test/generators/python/PythonConstrainer.spec.ts b/test/generators/python/PythonConstrainer.spec.ts index 6fcad4ff50..323c208394 100644 --- a/test/generators/python/PythonConstrainer.spec.ts +++ b/test/generators/python/PythonConstrainer.spec.ts @@ -166,7 +166,7 @@ describe('PythonConstrainer', () => { constrainedModel: model, ...defaultOptions }); - expect(type).toEqual('list[str]'); + expect(type).toEqual('List[str]'); }); }); diff --git a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap index 6606138468..376991a23a 100644 --- a/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap +++ b/test/generators/python/__snapshots__/PythonGenerator.spec.ts.snap @@ -2,7 +2,7 @@ exports[`PythonGenerator Class should not render reserved keyword 1`] = ` "class Address: - def __init__(self, input: dict): + def __init__(self, input: Dict): if hasattr(input, \\"reservedReservedDel\\"): self._reservedReservedDel: str = input[\\"reservedReservedDel\\"] if hasattr(input, \\"reservedDel\\"): @@ -26,7 +26,7 @@ exports[`PythonGenerator Class should not render reserved keyword 1`] = ` exports[`PythonGenerator Class should render \`class\` type 1`] = ` "class Address: - def __init__(self, input: dict): + def __init__(self, input: Dict): self._streetName: str = input[\\"streetName\\"] self._city: str = input[\\"city\\"] self._state: str = input[\\"state\\"] @@ -35,7 +35,7 @@ exports[`PythonGenerator Class should render \`class\` type 1`] = ` self._marriage: bool = input[\\"marriage\\"] if hasattr(input, \\"members\\"): self._members: str | float | bool = input[\\"members\\"] - self._arrayType: list[str | float | Any] = input[\\"arrayType\\"] + self._arrayType: List[str | float | Any] = input[\\"arrayType\\"] if hasattr(input, \\"additionalProperties\\"): self._additionalProperties: dict[Any | str, Any | str] = input[\\"additionalProperties\\"] @@ -82,10 +82,10 @@ exports[`PythonGenerator Class should render \`class\` type 1`] = ` self._members = members @property - def arrayType(self) -> list[str | float | Any]: + def arrayType(self) -> List[str | float | Any]: return self._arrayType @arrayType.setter - def arrayType(self, arrayType: list[str | float | Any]): + def arrayType(self, arrayType: List[str | float | Any]): self._arrayType = arrayType @property @@ -104,7 +104,7 @@ exports[`PythonGenerator Class should work with custom preset for \`class\` type test1 - def __init__(self, input: dict): + def __init__(self, input: Dict): if hasattr(input, \\"property\\"): self._property: str = input[\\"property\\"] if hasattr(input, \\"additionalProperties\\"): @@ -132,7 +132,7 @@ exports[`PythonGenerator Class should work with custom preset for \`class\` type exports[`PythonGenerator Class should work with empty objects 1`] = ` "class CustomClass: - def __init__(self, input: dict): + def __init__(self, input: Dict): \\"\\"\\" No properties \\"\\"\\" diff --git a/test/generators/python/presets/__snapshots__/JsonSerializer.spec.ts.snap b/test/generators/python/presets/__snapshots__/JsonSerializer.spec.ts.snap index af155bfcc2..d8535d34e1 100644 --- a/test/generators/python/presets/__snapshots__/JsonSerializer.spec.ts.snap +++ b/test/generators/python/presets/__snapshots__/JsonSerializer.spec.ts.snap @@ -2,7 +2,7 @@ exports[`PYTHON_JSON_SERIALIZER_PRESET should render serializer and deserializer for class 1`] = ` "class Test: - def __init__(self, input: dict): + def __init__(self, input: Dict): if hasattr(input, \\"prop\\"): self._prop: str = input[\\"prop\\"] if hasattr(input, \\"additionalProperties\\"):