Skip to content

Commit

Permalink
fix!: add python types, contructor and constants (#1858)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaslagoni authored Mar 13, 2024
1 parent 5fad06c commit 5da0c62
Show file tree
Hide file tree
Showing 16 changed files with 406 additions and 120 deletions.
72 changes: 71 additions & 1 deletion docs/migrations/version-3-to-4.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,74 @@ If you are using the Python preset `PYTHON_JSON_SERIALIZER_PRESET`, the function

- deserializeFromJson
+ deserialize_from_json
```
```

### 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
```

Original file line number Diff line number Diff line change
Expand Up @@ -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
",
]
Expand All @@ -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
",
]
Expand All @@ -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
",
]
Expand All @@ -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
",
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
",
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
8 changes: 5 additions & 3 deletions src/generators/python/PythonConstrainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
39 changes: 39 additions & 0 deletions src/generators/python/PythonDependencyManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string[]> = {};
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;
}
}
4 changes: 2 additions & 2 deletions src/generators/python/PythonGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ ${outputModel.result}`;
return RenderOutput.toRenderOutput({
result,
renderedName: model.name,
dependencies: dependencyManagerToUse.dependencies
dependencies: dependencyManagerToUse.renderDependencies()
});
}

Expand All @@ -266,7 +266,7 @@ ${outputModel.result}`;
return RenderOutput.toRenderOutput({
result,
renderedName: model.name,
dependencies: dependencyManagerToUse.dependencies
dependencies: dependencyManagerToUse.renderDependencies()
});
}
}
Loading

0 comments on commit 5da0c62

Please sign in to comment.