Skip to content

Commit 1f60fab

Browse files
authored
fix: add aliasing back in for Pydantic generator (#2302)
1 parent e78295d commit 1f60fab

File tree

4 files changed

+76
-11
lines changed

4 files changed

+76
-11
lines changed

examples/generate-python-pydantic-models/__snapshots__/index.spec.ts.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
exports[`Should be able to render python models and should log expected output to console: class-model 1`] = `
44
Array [
55
"class Root(BaseModel):
6-
optional_field: Optional[str] = Field(description='''this field is optional''', default=None)
7-
required_field: str = Field(description='''this field is required''')
8-
no_description: Optional[str] = Field(default=None)
6+
optional_field: Optional[str] = Field(description='''this field is optional''', default=None, alias='''optionalField''')
7+
required_field: str = Field(description='''this field is required''', alias='''requiredField''')
8+
no_description: Optional[str] = Field(default=None, alias='''noDescription''')
99
options: Optional[Options] = Field(default=None)
10-
content_type: Optional[str] = Field(default=None)
10+
content_type: Optional[str] = Field(default=None, alias='''content-type''')
1111
",
1212
]
1313
`;

src/generators/python/presets/Pydantic.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ const PYTHON_PYDANTIC_CLASS_PRESET: ClassPresetType<PythonOptions> = {
6363
) {
6464
decoratorArgs.push('exclude=True');
6565
}
66+
if (
67+
property.propertyName !== property.unconstrainedPropertyName &&
68+
(!(property.property instanceof ConstrainedDictionaryModel) ||
69+
property.property.serializationType !== 'unwrap')
70+
) {
71+
decoratorArgs.push(`alias='''${property.unconstrainedPropertyName}'''`);
72+
}
6673

6774
return `${propertyName}: ${type} = Field(${decoratorArgs.join(', ')})`;
6875
},

test/generators/python/presets/Pydantic.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,20 @@ describe('PYTHON_PYDANTIC_PRESET', () => {
163163
const models = await generator.generate(doc);
164164
expect(models.map((model) => model.result)).toMatchSnapshot();
165165
});
166+
167+
test('should always set alias', async () => {
168+
const doc = {
169+
title: 'AliasTest',
170+
type: 'object',
171+
required: ['testAlias'],
172+
properties: {
173+
testAlias: {
174+
type: 'string'
175+
}
176+
}
177+
};
178+
179+
const models = await generator.generate(doc);
180+
expect(models.map((model) => model.result)).toMatchSnapshot();
181+
});
166182
});

test/generators/python/presets/__snapshots__/Pydantic.spec.ts.snap

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,52 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`PYTHON_PYDANTIC_PRESET should always set alias 1`] = `
4+
Array [
5+
"class AliasTest(BaseModel):
6+
test_alias: str = Field(alias='''testAlias''')
7+
additional_properties: Optional[dict[str, Any]] = Field(default=None, exclude=True)
8+
9+
@model_serializer(mode='wrap')
10+
def custom_serializer(self, handler):
11+
serialized_self = handler(self)
12+
additional_properties = getattr(self, \\"additional_properties\\")
13+
if additional_properties is not None:
14+
for key, value in additional_properties.items():
15+
# Never overwrite existing values, to avoid clashes
16+
if not hasattr(serialized_self, key):
17+
serialized_self[key] = value
18+
19+
return serialized_self
20+
21+
@model_validator(mode='before')
22+
@classmethod
23+
def unwrap_additional_properties(cls, data):
24+
if not isinstance(data, dict):
25+
data = data.model_dump()
26+
json_properties = list(data.keys())
27+
known_object_properties = ['test_alias', 'additional_properties']
28+
unknown_object_properties = [element for element in json_properties if element not in known_object_properties]
29+
# Ignore attempts that validate regular models, only when unknown input is used we add unwrap extensions
30+
if len(unknown_object_properties) == 0:
31+
return data
32+
33+
known_json_properties = ['testAlias', 'additionalProperties']
34+
additional_properties = data.get('additional_properties', {})
35+
for obj_key in list(data.keys()):
36+
if not known_json_properties.__contains__(obj_key):
37+
additional_properties[obj_key] = data.pop(obj_key, None)
38+
data['additional_properties'] = additional_properties
39+
return data
40+
41+
",
42+
]
43+
`;
44+
345
exports[`PYTHON_PYDANTIC_PRESET should render default value for discriminator when using polymorphism 1`] = `
446
Array [
547
"",
648
"class Car(BaseModel):
7-
vehicle_type: VehicleType = Field(default=VehicleType.CAR, frozen=True)
49+
vehicle_type: VehicleType = Field(default=VehicleType.CAR, frozen=True, alias='''vehicleType''')
850
length: Optional[float] = Field(default=None)
951
additional_properties: Optional[dict[str, Any]] = Field(default=None, exclude=True)
1052
@@ -45,7 +87,7 @@ Array [
4587
CAR = \\"Car\\"
4688
TRUCK = \\"Truck\\"",
4789
"class Truck(BaseModel):
48-
vehicle_type: VehicleType = Field(default=VehicleType.TRUCK, frozen=True)
90+
vehicle_type: VehicleType = Field(default=VehicleType.TRUCK, frozen=True, alias='''vehicleType''')
4991
length: Optional[float] = Field(default=None)
5092
additional_properties: Optional[dict[str, Any]] = Field(default=None, exclude=True)
5193
@@ -88,7 +130,7 @@ Array [
88130
exports[`PYTHON_PYDANTIC_PRESET should render nullable union 1`] = `
89131
Array [
90132
"class NullableUnionTest(BaseModel):
91-
nullable_union_test: Optional[Union[Union1]] = Field(default=None)
133+
nullable_union_test: Optional[Union[Union1]] = Field(default=None, alias='''nullableUnionTest''')
92134
additional_properties: Optional[dict[str, Any]] = Field(default=None, exclude=True)
93135
94136
@model_serializer(mode='wrap')
@@ -125,7 +167,7 @@ Array [
125167
126168
",
127169
"class Union1(BaseModel):
128-
test_prop1: Optional[str] = Field(default=None)
170+
test_prop1: Optional[str] = Field(default=None, alias='''testProp1''')
129171
additional_properties: Optional[dict[str, Any]] = Field(default=None, exclude=True)
130172
131173
@model_serializer(mode='wrap')
@@ -210,7 +252,7 @@ exports[`PYTHON_PYDANTIC_PRESET should render pydantic for class 1`] = `
210252
exports[`PYTHON_PYDANTIC_PRESET should render union to support Python < 3.10 1`] = `
211253
Array [
212254
"class UnionTest(BaseModel):
213-
union_test: Optional[Union[Union1, Union2]] = Field(default=None)
255+
union_test: Optional[Union[Union1, Union2]] = Field(default=None, alias='''unionTest''')
214256
additional_properties: Optional[dict[str, Any]] = Field(default=None, exclude=True)
215257
216258
@model_serializer(mode='wrap')
@@ -247,7 +289,7 @@ Array [
247289
248290
",
249291
"class Union1(BaseModel):
250-
test_prop1: Optional[str] = Field(default=None)
292+
test_prop1: Optional[str] = Field(default=None, alias='''testProp1''')
251293
additional_properties: Optional[dict[str, Any]] = Field(default=None, exclude=True)
252294
253295
@model_serializer(mode='wrap')
@@ -284,7 +326,7 @@ Array [
284326
285327
",
286328
"class Union2(BaseModel):
287-
test_prop2: Optional[str] = Field(default=None)
329+
test_prop2: Optional[str] = Field(default=None, alias='''testProp2''')
288330
additional_properties: Optional[dict[str, Any]] = Field(default=None, exclude=True)
289331
290332
@model_serializer(mode='wrap')

0 commit comments

Comments
 (0)