From 2fd0a139b2e30b2419cb89b6feb4b6754a86a14c Mon Sep 17 00:00:00 2001 From: Andrey Zaytsev Date: Fri, 4 Nov 2022 17:58:24 +0400 Subject: [PATCH] fix: implicit python import error (#981) --- docs/languages/Python.md | 4 +- .../generate-python-complete-models/README.md | 17 +++ .../__snapshots__/index.spec.ts.snap | 113 ++++++++++++++++++ .../index.spec.ts | 22 ++++ .../generate-python-complete-models/index.ts | 45 +++++++ .../package-lock.json | 10 ++ .../package.json | 12 ++ examples/generate-python-models/index.spec.ts | 5 +- examples/generate-python-models/index.ts | 4 +- src/generators/python/PythonGenerator.ts | 10 +- 10 files changed, 233 insertions(+), 9 deletions(-) create mode 100644 examples/generate-python-complete-models/README.md create mode 100644 examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap create mode 100644 examples/generate-python-complete-models/index.spec.ts create mode 100644 examples/generate-python-complete-models/index.ts create mode 100644 examples/generate-python-complete-models/package-lock.json create mode 100644 examples/generate-python-complete-models/package.json diff --git a/docs/languages/Python.md b/docs/languages/Python.md index 75906564aa..91a9173d4c 100644 --- a/docs/languages/Python.md +++ b/docs/languages/Python.md @@ -6,11 +6,11 @@ There are special use-cases that each language supports; this document pertains -- [Generate an Pydantic models](#generate-an-pydantic-models) +- [Generate Pydantic models](#generate-pydantic-models) -## Generate an Pydantic models +## Generate Pydantic models In some cases you might want to use [pydantic](https://pypi.org/project/pydantic/) data validation and settings management using Python type hints for the models. You can find an example of its use [here](../../examples/generate-python-pydantic-models/index.ts) diff --git a/examples/generate-python-complete-models/README.md b/examples/generate-python-complete-models/README.md new file mode 100644 index 0000000000..3b91996fbb --- /dev/null +++ b/examples/generate-python-complete-models/README.md @@ -0,0 +1,17 @@ +# Python models + +A basic example of complete model generation. + +## How to run this example + +Run this example using: + +```sh +npm i && npm run start +``` + +If you are on Windows, use the `start:windows` script instead: + +```sh +npm i && npm run start:windows +``` diff --git a/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap b/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap new file mode 100644 index 0000000000..974170c126 --- /dev/null +++ b/examples/generate-python-complete-models/__snapshots__/index.spec.ts.snap @@ -0,0 +1,113 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Should be able to render python models and should generate \`implicit\` or \`explicit\` imports for models implementing referenced types: nested-model 1`] = ` +Array [ + " + +class ObjProperty: + def __init__(self, input): + if hasattr(input, 'number'): + self._number = input.number + if hasattr(input, 'additionalProperties'): + self._additionalProperties = input.additionalProperties + + @property + def number(self): + return self._number + @number.setter + def number(self, number): + self._number = number + + @property + def additionalProperties(self): + return self._additionalProperties + @additionalProperties.setter + def additionalProperties(self, additionalProperties): + self._additionalProperties = additionalProperties +", +] +`; + +exports[`Should be able to render python models and should generate \`implicit\` or \`explicit\` imports for models implementing referenced types: nested-model 2`] = ` +Array [ + " + +class ObjProperty: + def __init__(self, input): + if hasattr(input, 'number'): + self._number = input.number + if hasattr(input, 'additionalProperties'): + self._additionalProperties = input.additionalProperties + + @property + def number(self): + return self._number + @number.setter + def number(self, number): + self._number = number + + @property + def additionalProperties(self): + return self._additionalProperties + @additionalProperties.setter + def additionalProperties(self, additionalProperties): + self._additionalProperties = additionalProperties +", +] +`; + +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 + +class Root: + def __init__(self, input): + if hasattr(input, 'email'): + self._email = input.email + if hasattr(input, 'objProperty'): + self._objProperty = input.objProperty + + @property + def email(self): + return self._email + @email.setter + def email(self, email): + self._email = email + + @property + def objProperty(self): + return self._objProperty + @objProperty.setter + def objProperty(self, objProperty): + self._objProperty = objProperty +", +] +`; + +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 + +class Root: + def __init__(self, input): + if hasattr(input, 'email'): + self._email = input.email + if hasattr(input, 'objProperty'): + self._objProperty = input.objProperty + + @property + def email(self): + return self._email + @email.setter + def email(self, email): + self._email = email + + @property + def objProperty(self): + return self._objProperty + @objProperty.setter + def objProperty(self, objProperty): + self._objProperty = objProperty +", +] +`; diff --git a/examples/generate-python-complete-models/index.spec.ts b/examples/generate-python-complete-models/index.spec.ts new file mode 100644 index 0000000000..1834e75f8a --- /dev/null +++ b/examples/generate-python-complete-models/index.spec.ts @@ -0,0 +1,22 @@ +import {generate} from './index'; + +const createLogMock = () => jest.spyOn(global.console, 'log').mockImplementation(() => { return; }); + +describe('Should be able to render python models', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + test('and should generate `implicit` or `explicit` imports for models implementing referenced types', async () => { + const spy = createLogMock(); + + await generate(); + + expect(spy.mock.calls.length).toEqual(4); + + expect(spy.mock.calls[0]).toMatchSnapshot('root-model-implicit-import'); + expect(spy.mock.calls[1]).toMatchSnapshot('nested-model'); + + expect(spy.mock.calls[2]).toMatchSnapshot('root-model-explicit-import'); + expect(spy.mock.calls[3]).toMatchSnapshot('nested-model'); + }); +}); diff --git a/examples/generate-python-complete-models/index.ts b/examples/generate-python-complete-models/index.ts new file mode 100644 index 0000000000..0b8f6e3907 --- /dev/null +++ b/examples/generate-python-complete-models/index.ts @@ -0,0 +1,45 @@ +import { PythonGenerator } from '../../src'; + +export const jsonSchemaDraft7 = { + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + additionalProperties: false, + properties: { + email: { + type: 'string', + format: 'email' + }, + objProperty: { + type: 'object', + properties: { + number: { + type: 'number' + } + } + } + } +}; + +export async function generate(): Promise { + const generator = new PythonGenerator(); + + let models = await generator.generateCompleteModels(jsonSchemaDraft7, { + importsStyle: 'implicit' + }); + + for (const model of models) { + console.log(model.result); + } + + models = await generator.generateCompleteModels(jsonSchemaDraft7, { + importsStyle: 'explicit' + }); + + for (const model of models) { + console.log(model.result); + } +} + +if (require.main === module) { + generate(); +} diff --git a/examples/generate-python-complete-models/package-lock.json b/examples/generate-python-complete-models/package-lock.json new file mode 100644 index 0000000000..d73d77e9df --- /dev/null +++ b/examples/generate-python-complete-models/package-lock.json @@ -0,0 +1,10 @@ +{ + "name": "generate-python-complete-models", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "hasInstallScript": true + } + } +} diff --git a/examples/generate-python-complete-models/package.json b/examples/generate-python-complete-models/package.json new file mode 100644 index 0000000000..fc37a4685d --- /dev/null +++ b/examples/generate-python-complete-models/package.json @@ -0,0 +1,12 @@ +{ + "config": { + "example_name": "generate-python-complete-models" + }, + "scripts": { + "install": "cd ../.. && npm i", + "start": "../../node_modules/.bin/ts-node --cwd ../../ ./examples/$npm_package_config_example_name/index.ts", + "start:windows": "..\\..\\node_modules\\.bin\\ts-node --cwd ..\\..\\ .\\examples\\%npm_package_config_example_name%\\index.ts", + "test": "../../node_modules/.bin/jest --config=../../jest.config.js ./examples/$npm_package_config_example_name/index.spec.ts", + "test:windows": "..\\..\\node_modules\\.bin\\jest --config=..\\..\\jest.config.js examples/%npm_package_config_example_name%/index.spec.ts" + } +} diff --git a/examples/generate-python-models/index.spec.ts b/examples/generate-python-models/index.spec.ts index 25e2dc237e..ec9da365f4 100644 --- a/examples/generate-python-models/index.spec.ts +++ b/examples/generate-python-models/index.spec.ts @@ -7,8 +7,7 @@ describe('Should be able to render python models', () => { }); test('and should log expected output to console', async () => { await generate(); - //Generate is called 2x, so even though we expect 1 model, we double it - expect(spy.mock.calls.length).toEqual(2); - expect(spy.mock.calls[1]).toMatchSnapshot(); + expect(spy.mock.calls.length).toEqual(1); + expect(spy.mock.calls[0]).toMatchSnapshot(); }); }); diff --git a/examples/generate-python-models/index.ts b/examples/generate-python-models/index.ts index f57279c3d1..86675cdbc3 100644 --- a/examples/generate-python-models/index.ts +++ b/examples/generate-python-models/index.ts @@ -19,4 +19,6 @@ export async function generate() : Promise { console.log(model.result); } } -generate(); +if (require.main === module) { + generate(); +} diff --git a/src/generators/python/PythonGenerator.ts b/src/generators/python/PythonGenerator.ts index 409309bd17..b190542f32 100644 --- a/src/generators/python/PythonGenerator.ts +++ b/src/generators/python/PythonGenerator.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { AbstractGenerator, CommonGeneratorOptions, @@ -16,6 +17,7 @@ import { DeepPartial, mergePartialAndDefault } from '../../utils/Partials'; export interface PythonOptions extends CommonGeneratorOptions { typeMapping: TypeMapping; constraints: Constraints; + importsStyle: 'explicit' | 'implicit'; } // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PythonRenderCompleteModelOptions {} @@ -24,7 +26,8 @@ export class PythonGenerator extends AbstractGenerator { + async renderCompleteModel(model: ConstrainedMetaModel, inputModel: InputMetaModel, options: PythonOptions): Promise { + const useExplicitImports = options.importsStyle === 'explicit'; const outputModel = await this.render(model, inputModel); const modelDependencies = model.getNearestDependencies().map((dependencyModel) => { - return `from ${dependencyModel.name} import ${dependencyModel.name}`; + return `from ${useExplicitImports ? '.' : ''}${dependencyModel.name} import ${dependencyModel.name}`; }); const outputContent = `${modelDependencies.join('\n')} ${outputModel.dependencies.join('\n')}