|
| 1 | +"""Tests for union_property.py.jinja's `transform` macro. |
| 2 | +
|
| 3 | +See issue: redundant `isinstance` dispatch when every union member is a model. |
| 4 | +""" |
| 5 | + |
| 6 | +import pytest |
| 7 | +from jinja2 import Environment, PackageLoader |
| 8 | + |
| 9 | +from openapi_python_client import TEMPLATE_FILTERS |
| 10 | + |
| 11 | + |
| 12 | +@pytest.fixture |
| 13 | +def jinja_env() -> Environment: |
| 14 | + """A jinja2 environment matching the one used for real code generation. |
| 15 | +
|
| 16 | + Needs the `loopcontrols` extension (for `{% continue %}`) which the shared `env` fixture in |
| 17 | + `tests/test_templates/conftest.py` does not enable. |
| 18 | + """ |
| 19 | + env = Environment( |
| 20 | + loader=PackageLoader("openapi_python_client"), |
| 21 | + trim_blocks=True, |
| 22 | + lstrip_blocks=True, |
| 23 | + extensions=["jinja2.ext.loopcontrols"], |
| 24 | + ) |
| 25 | + env.filters.update(TEMPLATE_FILTERS) |
| 26 | + return env |
| 27 | + |
| 28 | + |
| 29 | +def test_transform_homogeneous_model_union_skips_isinstance_dispatch( |
| 30 | + jinja_env: Environment, union_property_factory, model_property_factory |
| 31 | +) -> None: |
| 32 | + """When every union member is a model, `.to_dict()` is called unconditionally (no per-member isinstance |
| 33 | + dispatch), since every branch's body is identical regardless of which member actually matched. |
| 34 | + """ |
| 35 | + model_a = model_property_factory(name="model_a", required=True) |
| 36 | + model_b = model_property_factory(name="model_b", required=True) |
| 37 | + union = union_property_factory(name="both_models", required=False, inner_properties=[model_a, model_b]) |
| 38 | + |
| 39 | + template = jinja_env.get_template("property_templates/union_property.py.jinja") |
| 40 | + result = template.module.transform(union, "self.both_models", "both_models") |
| 41 | + |
| 42 | + assert "isinstance(self.both_models, Unset)" in result |
| 43 | + assert "isinstance(self.both_models, MyClass)" not in result |
| 44 | + assert result.count(".to_dict()") == 1 |
| 45 | + |
| 46 | + |
| 47 | +def test_transform_mixed_union_keeps_isinstance_dispatch( |
| 48 | + jinja_env: Environment, union_property_factory, model_property_factory, string_property_factory |
| 49 | +) -> None: |
| 50 | + """Mixed unions (model + non-model members) must keep the existing per-member isinstance dispatch, |
| 51 | + since those branches genuinely differ in behavior. |
| 52 | + """ |
| 53 | + model_a = model_property_factory(name="model_a", required=True) |
| 54 | + string_b = string_property_factory(name="string_b", required=True) |
| 55 | + union = union_property_factory(name="mixed", required=False, inner_properties=[model_a, string_b]) |
| 56 | + |
| 57 | + template = jinja_env.get_template("property_templates/union_property.py.jinja") |
| 58 | + result = template.module.transform(union, "self.mixed", "mixed") |
| 59 | + |
| 60 | + assert "isinstance(self.mixed, MyClass)" in result |
| 61 | + assert result.count(".to_dict()") == 1 |
| 62 | + |
| 63 | + |
| 64 | +def test_transform_required_homogeneous_model_union_has_no_guard( |
| 65 | + jinja_env: Environment, union_property_factory, model_property_factory |
| 66 | +) -> None: |
| 67 | + """A required homogeneous-model union can never be Unset, so `.to_dict()` is called with no guard at all.""" |
| 68 | + model_a = model_property_factory(name="model_a", required=True) |
| 69 | + model_b = model_property_factory(name="model_b", required=True) |
| 70 | + union = union_property_factory(name="both_models", required=True, inner_properties=[model_a, model_b]) |
| 71 | + |
| 72 | + template = jinja_env.get_template("property_templates/union_property.py.jinja") |
| 73 | + result = template.module.transform(union, "self.both_models", "both_models") |
| 74 | + |
| 75 | + assert "isinstance" not in result |
| 76 | + assert result.strip() == "both_models: dict[str, Any]\nboth_models = self.both_models.to_dict()" |
| 77 | + |
| 78 | + |
| 79 | +def test_transform_skip_unset_homogeneous_model_union_still_guards_unset( |
| 80 | + jinja_env: Environment, union_property_factory, model_property_factory |
| 81 | +) -> None: |
| 82 | + """Request bodies render `transform` with `skip_unset=True` (the `UNSET` default is assigned elsewhere), but an |
| 83 | + optional homogeneous-model union can still *be* `Unset` at runtime, so `.to_dict()` must still be guarded -- |
| 84 | + unconditionally calling it here would raise `AttributeError`/fail type-checking (`Unset` has no `to_dict`). |
| 85 | + """ |
| 86 | + model_a = model_property_factory(name="model_a", required=True) |
| 87 | + union = union_property_factory(name="body", required=False, inner_properties=[model_a]) |
| 88 | + |
| 89 | + template = jinja_env.get_template("property_templates/union_property.py.jinja") |
| 90 | + result = template.module.transform(union, "body", '_kwargs["json"]', skip_unset=True, declare_type=False) |
| 91 | + |
| 92 | + assert result.strip() == 'if not isinstance(body, Unset):\n _kwargs["json"] = body.to_dict()' |
0 commit comments