Skip to content

Commit

Permalink
fix: implicit python import error (#981)
Browse files Browse the repository at this point in the history
  • Loading branch information
zaytsevand authored Nov 4, 2022
1 parent 13eec21 commit 2fd0a13
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 9 deletions.
4 changes: 2 additions & 2 deletions docs/languages/Python.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ There are special use-cases that each language supports; this document pertains

<!-- toc -->

- [Generate an Pydantic models](#generate-an-pydantic-models)
- [Generate Pydantic models](#generate-pydantic-models)

<!-- tocstop -->

## 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)
17 changes: 17 additions & 0 deletions examples/generate-python-complete-models/README.md
Original file line number Diff line number Diff line change
@@ -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
```
Original file line number Diff line number Diff line change
@@ -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
",
]
`;
22 changes: 22 additions & 0 deletions examples/generate-python-complete-models/index.spec.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
45 changes: 45 additions & 0 deletions examples/generate-python-complete-models/index.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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();
}
10 changes: 10 additions & 0 deletions examples/generate-python-complete-models/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions examples/generate-python-complete-models/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
5 changes: 2 additions & 3 deletions examples/generate-python-models/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
4 changes: 3 additions & 1 deletion examples/generate-python-models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ export async function generate() : Promise<void> {
console.log(model.result);
}
}
generate();
if (require.main === module) {
generate();
}
10 changes: 7 additions & 3 deletions src/generators/python/PythonGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-console */
import {
AbstractGenerator,
CommonGeneratorOptions,
Expand All @@ -16,6 +17,7 @@ import { DeepPartial, mergePartialAndDefault } from '../../utils/Partials';
export interface PythonOptions extends CommonGeneratorOptions<PythonPreset> {
typeMapping: TypeMapping<PythonOptions>;
constraints: Constraints;
importsStyle: 'explicit' | 'implicit';
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PythonRenderCompleteModelOptions {}
Expand All @@ -24,7 +26,8 @@ export class PythonGenerator extends AbstractGenerator<PythonOptions, PythonRend
...defaultGeneratorOptions,
defaultPreset: PYTHON_DEFAULT_PRESET,
typeMapping: PythonDefaultTypeMapping,
constraints: PythonDefaultConstraints
constraints: PythonDefaultConstraints,
importsStyle: 'implicit'
};

constructor(
Expand Down Expand Up @@ -83,10 +86,11 @@ export class PythonGenerator extends AbstractGenerator<PythonOptions, PythonRend
* @param inputModel
* @param options used to render the full output
*/
async renderCompleteModel(model: ConstrainedMetaModel, inputModel: InputMetaModel): Promise<RenderOutput> {
async renderCompleteModel(model: ConstrainedMetaModel, inputModel: InputMetaModel, options: PythonOptions): Promise<RenderOutput> {
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')}
Expand Down

0 comments on commit 2fd0a13

Please sign in to comment.