Skip to content

Commit 4c94d06

Browse files
authored
Merge pull request #59 from pingelit/feature/py-improvements
Feature: minor python improvements
2 parents 7e94d63 + 8a2767d commit 4c94d06

7 files changed

Lines changed: 58 additions & 21 deletions

File tree

code-gen/src/poly_scribe_code_gen/cli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def poly_scribe_code_gen() -> int:
101101
if args.py:
102102
spec = importlib.util.spec_from_file_location(module_name, args.py)
103103
else:
104-
source_dir = args.py_package / "src" / args.py_package.name
104+
source_dir = args.py_package / "src" / additional_data["package"]
105105
init_file = source_dir / "__init__.py"
106106
if not init_file.exists():
107107
msg = f"Python package '{args.py_package}' does not contain an __init__.py file"

code-gen/src/poly_scribe_code_gen/py_gen.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import black
1515
import isort
1616
import jinja2
17-
from docstring_parser import DocstringStyle, compose
17+
from docstring_parser import Docstring, DocstringStyle, compose
1818

1919
from poly_scribe_code_gen._types import AdditionalData, ParsedIDL
2020

@@ -49,7 +49,7 @@ def generate_python_package(parsed_idl: ParsedIDL, additional_data: AdditionalDa
4949

5050
out_dir.mkdir(parents=True, exist_ok=True)
5151

52-
source_dir = out_dir / "src" / out_dir.name
52+
source_dir = out_dir / "src" / additional_data["package"]
5353
source_dir.mkdir(parents=True, exist_ok=True)
5454

5555
generate_python(parsed_idl, additional_data, source_dir / "__init__.py")
@@ -186,17 +186,23 @@ def _transform_types(parsed_idl: ParsedIDL) -> ParsedIDL:
186186

187187
for derived_types in parsed_idl["inheritance_data"].values():
188188
if struct_name in derived_types and not any(member == "type" for member in struct_data["members"]):
189+
doc_string = Docstring()
190+
doc_string.short_description = "Discriminator field"
189191
struct_data["members"]["type"] = {
190192
"type": f'Literal["{struct_name}"]',
191193
"default": f'"{struct_name}"',
194+
"block_comment": doc_string,
192195
}
193196

194197
if struct_name in parsed_idl["inheritance_data"] and not any(
195198
member == "type" for member in struct_data["members"]
196199
):
200+
doc_string = Docstring()
201+
doc_string.short_description = "Discriminator field"
197202
struct_data["members"]["type"] = {
198203
"type": f'Literal["{struct_name}"]',
199204
"default": f'"{struct_name}"',
205+
"block_comment": doc_string,
200206
}
201207

202208
for type_def in parsed_idl["typedefs"].values():

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,22 @@ class {{ struct_name }}{% if struct_data["inheritance"] %}({{ struct_data["inher
6666
{% endfor %}
6767

6868
def load(model_type: Type[T], file: Union[Path, str]) -> T:
69+
"""
70+
Load a model from a file.
71+
72+
This function loads a file from the file system and tries to parse it as a given type.
73+
74+
Args:
75+
model_type: The type of the model to load.
76+
file: The file to load the model from.
77+
78+
Returns:
79+
An instance of the model type.
80+
81+
Raises:
82+
FileNotFoundError: If the file does not exist.
83+
ValueError: If the file extension is not supported.
84+
"""
6985
if isinstance(file, str):
7086
file = Path(file).resolve()
7187
elif isinstance(file, Path):
@@ -92,6 +108,19 @@ def load(model_type: Type[T], file: Union[Path, str]) -> T:
92108

93109

94110
def save(file: Union[Path, str], model: BaseModel):
111+
"""
112+
Save a model to a file.
113+
114+
This function saves a data structure to the file system.
115+
116+
Args:
117+
file: The file to save the model to.
118+
model: The model to save.
119+
120+
Raises:
121+
TypeError: If the file argument is not a Path, str, or stream.
122+
ValueError: If the file extension is not supported.
123+
"""
95124
if isinstance(file, str): # local path to file
96125
file = Path(file).resolve()
97126
elif isinstance(file, Path):

code-gen/tests/py_gen_test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ def test_generate_python_package(tmp_path: Path) -> None:
467467

468468
validate_pyproject_toml(toml_result, additional_data)
469469

470-
init_file = tmp_path / "src" / tmp_path.name / "__init__.py"
470+
init_file = tmp_path / "src" / additional_data["package"] / "__init__.py"
471471
assert init_file.exists()
472472
with open(init_file) as f:
473473
content = f.read()
@@ -527,7 +527,7 @@ def test_render_template_comments() -> None:
527527
pattern = re.compile(r'"""\s*(.*?)\s*"""', re.DOTALL)
528528
matches = pattern.findall(result)
529529

530-
assert len(matches) == 8
530+
assert len(matches) == 10
531531
assert "Typedef comment\n\ninline typedef comment" in matches[0]
532532
assert "My Enum comment" in matches[1]
533533
assert "Enum value 1 comment" in matches[2]
@@ -541,6 +541,8 @@ def test_render_template_comments() -> None:
541541
assert "inline comment" in matches[5]
542542
assert "Short comment for foo" in matches[6]
543543
assert "Short comment for bar" in matches[7]
544+
assert "Load" in matches[8]
545+
assert "Save" in matches[9]
544546

545547

546548
def test__render_template_string_default_value() -> None:

test/integration_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import pytest
2-
import integration_data
2+
import integration_space
33
import os
44
import subprocess
55

@@ -30,7 +30,7 @@ def test_integration_data(test_num):
3030
json_data = data_struct.model_dump_json()
3131
assert json_data is not None
3232

33-
new_data_struct = integration_data.IntegrationTest.model_validate_json(json_data)
33+
new_data_struct = integration_space.IntegrationTest.model_validate_json(json_data)
3434

3535
assert data_struct == new_data_struct
3636

@@ -48,11 +48,11 @@ def test_integration_data_round_trip(input_format, output_format):
4848
py_out = Path(tmp_dir).absolute() / f"integration_py_out.{output_format}"
4949
cpp_out = Path(tmp_dir).absolute() / f"integration_cpp_out.{input_format}"
5050

51-
integration_data.save(py_out, data_struct)
51+
integration_space.save(py_out, data_struct)
5252

5353
subprocess.run([cpp_exe, cpp_out, py_out], check=True)
5454

55-
new_data = integration_data.load(integration_data.IntegrationTest, cpp_out)
55+
new_data = integration_space.load(integration_space.IntegrationTest, cpp_out)
5656

5757
compare_integration_data(data_struct, new_data)
5858

test/test_gen_data.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ if (NOT EXISTS "${expected_python_package}")
8989
endif ()
9090

9191
# Check if the generated Python package contains the expected __init__.py file
92-
set (expected_python_init_file "${expected_python_package}/src/integration_data/__init__.py")
92+
set (expected_python_init_file "${expected_python_package}/src/${expected_namespace}/__init__.py")
9393
if (NOT EXISTS "${expected_python_init_file}")
9494
message (SEND_ERROR "Expected Python __init__.py file does not exist: ${expected_python_init_file}")
9595
endif ()

test/utils.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import random
22
import string
33

4-
import integration_data
4+
import integration_space
55

66

77
def random_string(length: int) -> str:
88
return "".join(random.choice(string.ascii_letters) for _ in range(length))
99

1010

1111
def gen_random_base():
12-
obj = integration_data.Base(
12+
obj = integration_space.Base(
1313
vec_3d=[random.random() for _ in range(3)],
1414
union_member=random.choice([random.random(), random.randint(0, 100), None]),
1515
str_vec=[random_string(5) for _ in range(random.choice([1, 2, 5]))],
@@ -20,7 +20,7 @@ def gen_random_base():
2020
def gen_random_derived_one():
2121
base_dict = gen_random_base().model_dump()
2222
base_dict.pop("type", None)
23-
obj = integration_data.DerivedOne(
23+
obj = integration_space.DerivedOne(
2424
**base_dict,
2525
string_map={
2626
random_string(5): random_string(5) for _ in range(random.choice([1, 2, 5]))
@@ -32,17 +32,17 @@ def gen_random_derived_one():
3232
def gen_random_derived_two():
3333
base_dict = gen_random_base().model_dump()
3434
base_dict.pop("type", None)
35-
obj = integration_data.DerivedTwo(**base_dict)
35+
obj = integration_space.DerivedTwo(**base_dict)
3636
return obj
3737

3838

3939
def gen_random_non_poly_derived():
40-
obj = integration_data.NonPolyDerived(value=random.randint(0, 100))
40+
obj = integration_space.NonPolyDerived(value=random.randint(0, 100))
4141
return obj
4242

4343

4444
def gen_random_integration_test():
45-
obj = integration_data.IntegrationTest(
45+
obj = integration_space.IntegrationTest(
4646
object_map={
4747
random_string(5): random.choice(
4848
[gen_random_derived_one, gen_random_derived_two]
@@ -58,15 +58,15 @@ def gen_random_integration_test():
5858
for _ in range(2)
5959
],
6060
enum_value=random.choice(
61-
[integration_data.Enumeration.value1, integration_data.Enumeration.value2]
61+
[integration_space.Enumeration.value1, integration_space.Enumeration.value2]
6262
),
6363
non_poly_derived=gen_random_non_poly_derived(),
6464
)
6565
return obj
6666

6767

6868
def compare_integration_data(
69-
lhs: integration_data.IntegrationTest, rhs: integration_data.IntegrationTest
69+
lhs: integration_space.IntegrationTest, rhs: integration_space.IntegrationTest
7070
):
7171
assert lhs.non_poly_derived == rhs.non_poly_derived
7272
assert lhs.enum_value == rhs.enum_value
@@ -85,15 +85,15 @@ def compare_integration_data(
8585
compare_poly_structure(lhs.object_array[i], rhs.object_array[i])
8686

8787

88-
def compare_poly_structure(lhs: integration_data.Base, rhs: integration_data.Base):
88+
def compare_poly_structure(lhs: integration_space.Base, rhs: integration_space.Base):
8989
assert all(abs(a - b) < 1e-6 for a, b in zip(lhs.vec_3d, rhs.vec_3d))
9090
if isinstance(lhs.union_member, float) and isinstance(rhs.union_member, float):
9191
assert abs(lhs.union_member - rhs.union_member) < 1e-6
9292
else:
9393
assert lhs.union_member == rhs.union_member
9494
assert lhs.str_vec == rhs.str_vec
9595

96-
if isinstance(lhs, integration_data.DerivedOne):
96+
if isinstance(lhs, integration_space.DerivedOne):
9797
assert lhs.string_map == rhs.string_map
98-
elif isinstance(lhs, integration_data.DerivedTwo):
98+
elif isinstance(lhs, integration_space.DerivedTwo):
9999
assert lhs.optional_value == rhs.optional_value

0 commit comments

Comments
 (0)