Skip to content

Commit e780c92

Browse files
authored
Merge pull request #57 from pingelit/feature/py-schema-dev
Real life/proof of concept development for python and JSON schema
2 parents 712cd44 + 4f90c65 commit e780c92

4 files changed

Lines changed: 56 additions & 19 deletions

File tree

code-gen/generate_data_structures.cmake

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,13 +323,25 @@ function (generate_data_structures TARGET_LIBRARY)
323323
)
324324
endif ()
325325

326+
if (GEN_DATA_OUTPUT_PYTHON_PKG)
327+
if (NOT EXISTS ${GEN_DATA_IN_SOURCE_PATH}/${GEN_DATA_OUTPUT_PYTHON_PKG})
328+
file (MAKE_DIRECTORY ${GEN_DATA_IN_SOURCE_PATH})
329+
endif ()
330+
331+
file (COPY ${GEN_DATA_OUTPUT_BASE_DIR}/${GEN_DATA_OUTPUT_PYTHON_PKG}
332+
DESTINATION ${GEN_DATA_IN_SOURCE_PATH}/${GEN_DATA_OUTPUT_PYTHON_PKG}
333+
)
334+
endif ()
335+
326336
if (GEN_DATA_OUTPUT_SCHEMA)
327337
if (NOT EXISTS ${GEN_DATA_IN_SOURCE_PATH}/${GEN_DATA_OUTPUT_SCHEMA})
328338
file (MAKE_DIRECTORY ${GEN_DATA_IN_SOURCE_PATH})
329339
endif ()
330340

341+
get_filename_component (GEN_DATA_OUTPUT_SCHEMA_PARENT_DIR ${GEN_DATA_IN_SOURCE_PATH}/${GEN_DATA_OUTPUT_SCHEMA} DIRECTORY)
342+
331343
file (COPY ${GEN_DATA_OUTPUT_BASE_DIR}/${GEN_DATA_OUTPUT_SCHEMA}
332-
DESTINATION ${GEN_DATA_IN_SOURCE_PATH}/${GEN_DATA_OUTPUT_SCHEMA}
344+
DESTINATION ${GEN_DATA_OUTPUT_SCHEMA_PARENT_DIR}
333345
)
334346
endif ()
335347
endif ()

code-gen/src/poly_scribe_code_gen/py_gen.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -144,25 +144,39 @@ def _render_pyproject_toml(additional_data: AdditionalData) -> str:
144144

145145

146146
def _transform_types(parsed_idl: ParsedIDL) -> ParsedIDL:
147+
defined_types = set()
148+
149+
for struct_name in parsed_idl["structs"]:
150+
defined_types.add(struct_name)
151+
152+
for enum_name in parsed_idl["enums"]:
153+
defined_types.add(enum_name)
154+
155+
for typedef_name in parsed_idl["typedefs"]:
156+
defined_types.add(typedef_name)
157+
147158
for struct_name, struct_data in parsed_idl["structs"].items():
148159
for member_data in struct_data["members"].values():
149-
member_data["type"] = _transformer(member_data["type"], parsed_idl["inheritance_data"])
160+
member_data["type"] = _transformer(member_data["type"], parsed_idl["inheritance_data"], defined_types)
150161

151162
if member_data["default"] == "{}" and member_data["default_type"] is not None:
152163
# member_data["default"] = f"Field(default={member_data['default_type']}())"
153164
member_data["default"] = f"{member_data['default_type']}()"
154165

155166
if member_data["default"] == "{}" and member_data["default_type"] is None:
156167
# member_data["default"] = f"Field(default={member_data['type']}())"
157-
member_data["default"] = f"{member_data['type']}()"
168+
type_str = member_data["type"]
169+
if isinstance(type_str, str) and type_str.startswith('"') and type_str.endswith('"'):
170+
type_str = type_str[1:-1]
171+
member_data["default"] = f"{type_str}()"
158172

159173
if not member_data["required"] and member_data["default"] is None:
160174
member_data["type"] = f"Optional[{member_data['type']}]"
161175

162176
if "str" in member_data["type"] and member_data["default"] is not None:
163177
member_data["default"] = f'"{member_data["default"]}"'
164178

165-
if member_data["default"] is None:
179+
if member_data["default"] is None and member_data["required"] is False:
166180
member_data["default"] = "None"
167181

168182
# Check if a member named "type" is already present in the struct and raise an error if so
@@ -186,14 +200,14 @@ def _transform_types(parsed_idl: ParsedIDL) -> ParsedIDL:
186200
}
187201

188202
for type_def in parsed_idl["typedefs"].values():
189-
type_def["type"] = _transformer(type_def["type"], parsed_idl["inheritance_data"])
203+
type_def["type"] = _transformer(type_def["type"], parsed_idl["inheritance_data"], defined_types)
190204

191205
return parsed_idl
192206

193207

194-
def _transformer(type_input: dict[str, Any], inheritance_data: dict[str, list[str]]) -> str:
208+
def _transformer(type_input: dict[str, Any], inheritance_data: dict[str, list[str]], defined_types: set[str]) -> str:
195209
if isinstance(type_input, str):
196-
type_input = _get_polymorphic_type(type_input, inheritance_data)
210+
type_input_poly = _get_polymorphic_type(type_input, inheritance_data, defined_types)
197211

198212
conversion = {
199213
"string": "str",
@@ -214,14 +228,20 @@ def _transformer(type_input: dict[str, Any], inheritance_data: dict[str, list[st
214228
"unsigned long long": "int",
215229
}
216230

217-
return conversion.get(type_input, type_input)
231+
# check if type_input is in defined_types
232+
if type_input_poly in defined_types:
233+
return f'"{conversion.get(type_input_poly, type_input_poly)}"'
234+
235+
return conversion.get(type_input_poly, type_input_poly)
218236

219237
if type_input["union"]:
220-
contained_types = [_transformer(contained, inheritance_data) for contained in type_input["type_name"]]
238+
contained_types = [
239+
_transformer(contained, inheritance_data, defined_types) for contained in type_input["type_name"]
240+
]
221241
transformed_type = ",".join(contained_types)
222242
return f"Union[{transformed_type}]"
223243
if type_input["vector"]:
224-
transformed_type = _transformer(type_input["type_name"], inheritance_data)
244+
transformed_type = _transformer(type_input["type_name"], inheritance_data, defined_types)
225245

226246
if type_input["size"] is not None:
227247
return (
@@ -232,17 +252,17 @@ def _transformer(type_input: dict[str, Any], inheritance_data: dict[str, list[st
232252

233253
return f"List[{transformed_type}]"
234254
if type_input["map"]:
235-
key_type = _transformer(type_input["type_name"]["key"], inheritance_data)
255+
key_type = _transformer(type_input["type_name"]["key"], inheritance_data, defined_types)
236256
# TODO: here we can check if the type changed??
237257

238-
value_type = _transformer(type_input["type_name"]["value"], inheritance_data)
258+
value_type = _transformer(type_input["type_name"]["value"], inheritance_data, defined_types)
239259
return f"Dict[{key_type}, {value_type}]"
240260

241261
msg = f"Unknown type: {type_input}"
242262
raise ValueError(msg)
243263

244264

245-
def _get_polymorphic_type(type_input: str, inheritance_data: dict[str, list[str]]) -> str:
265+
def _get_polymorphic_type(type_input: str, inheritance_data: dict[str, list[str]], defined_types: set[str]) -> str:
246266
union_content = []
247267

248268
if type_input in inheritance_data:
@@ -255,6 +275,7 @@ def _get_polymorphic_type(type_input: str, inheritance_data: dict[str, list[str]
255275
union_content.append(base_type)
256276

257277
if union_content:
278+
union_content = [f'"{type_name}"' for type_name in union_content if type_name in defined_types]
258279
return f"Annotated[Union[{', '.join(union_content)}],Field(discriminator=\"type\")]"
259280

260281
return type_input

code-gen/src/poly_scribe_code_gen/templates/python.jinja

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ class {{ struct_name }}{% if struct_data["inheritance"] %}({{ struct_data["inher
6161

6262
{% endfor%}
6363

64+
{% for struct_name in structs %}
65+
{{ struct_name }}.model_rebuild()
66+
{% endfor %}
67+
6468
def load(model_type: Type[T], file: Union[Path, str]) -> T:
6569
if isinstance(file, str):
6670
file = Path(file).resolve()

code-gen/tests/py_gen_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def test__transform_types_structs() -> None:
101101
)
102102
assert (
103103
result["structs"]["BazQux"]["members"]["union"]["type"].replace(" ", "")
104-
== "Optional[Union[int,float,bool,FooBar]]"
104+
== 'Optional[Union[int,float,bool,"FooBar"]]'
105105
)
106106

107107

@@ -115,7 +115,7 @@ def test__transform_types_unknown_type() -> None:
115115
}
116116

117117
with pytest.raises(ValueError, match="Unknown type:"):
118-
py_gen._transformer(type_data, {})
118+
py_gen._transformer(type_data, {}, set())
119119

120120

121121
def test_render_template_typedefs() -> None:
@@ -191,7 +191,7 @@ def test_render_template_structs() -> None:
191191
" ", ""
192192
) in struct_body.replace(" ", "")
193193
elif match[0] == "BazQux":
194-
assert "union: Optional[Union[int, float, bool, FooBar]]".replace(" ", "") in struct_body.replace(" ", "")
194+
assert 'union: Optional[Union[int, float, bool, "FooBar"]]'.replace(" ", "") in struct_body.replace(" ", "")
195195

196196

197197
def test_render_template_struct_with_inheritance() -> None:
@@ -269,7 +269,7 @@ def test_render_template_struct_with_poly_inheritance() -> None:
269269
assert "baz: float".replace(" ", "") in struct_body.replace(" ", "")
270270
assert 'type: Literal["C"] = "C"'.replace(" ", "") in struct_body.replace(" ", "")
271271
elif match[0] == "Y":
272-
assert 'content: Annotated[Union[B,C,X],Field(discriminator="type")]'.replace(
272+
assert 'content: Annotated[Union["B","C","X"],Field(discriminator="type")]'.replace(
273273
" ", ""
274274
) in struct_body.replace(" ", "")
275275

@@ -400,7 +400,7 @@ def test__render_template_poly_derived_in_decl() -> None:
400400
for match in matches:
401401
struct_body = match[2]
402402
if match[0] == "Y":
403-
assert 'content: Optional[Annotated[Union[B, C, X], Field(discriminator="type")]] = None'.replace(
403+
assert 'content: Optional[Annotated[Union["B", "C", "X"], Field(discriminator="type")]] = None'.replace(
404404
" ", ""
405405
) in struct_body.replace(" ", "")
406406

@@ -598,7 +598,7 @@ def test_render_template_struct_with_empty_type_default() -> None:
598598
for match in matches:
599599
struct_body = match[2]
600600
if match[0] == "Data":
601-
assert 'base: Annotated[Union[Foo, Bar, Base],Field(discriminator="type")] = Foo()'.replace(
601+
assert 'base: Annotated[Union["Foo", "Bar", "Base"],Field(discriminator="type")] = Foo()'.replace(
602602
" ", ""
603603
) in struct_body.replace(" ", "")
604604
elif match[0] == "Base":

0 commit comments

Comments
 (0)