From 2f28a4fd58cb16857856302846e640ff02c0389a Mon Sep 17 00:00:00 2001 From: Benjamin Pelletier Date: Fri, 18 Sep 2026 02:06:33 +0000 Subject: [PATCH 1/3] Return None for missing Optional field attribute access --- src/implicitdict/__init__.py | 11 +++++++---- tests/test_normal_usage.py | 4 ++-- tests/test_optional.py | 16 ++++++++++------ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/implicitdict/__init__.py b/src/implicitdict/__init__.py index 75fb39d..4ae1d7e 100644 --- a/src/implicitdict/__init__.py +++ b/src/implicitdict/__init__.py @@ -160,10 +160,13 @@ def __getattribute__(self, item): self_type_name = _fullname(self_type) if self_type_name in fields_info_by_type: if item in fields_info_by_type[self_type_name].all_fields: - try: - return self[item] - except KeyError: - raise AttributeError + if item in fields_info_by_type[self_type_name].optional_fields: + return self[item] if item in self else None + else: + try: + return self[item] + except KeyError: + raise AttributeError return super().__getattribute__(item) def __setattr__(self, key, value): diff --git a/tests/test_normal_usage.py b/tests/test_normal_usage.py index e08cc6e..7634c26 100644 --- a/tests/test_normal_usage.py +++ b/tests/test_normal_usage.py @@ -24,8 +24,8 @@ def test_basic_usage(): # Optional fields that aren't specified simply don't exist assert "baz" not in data - with pytest.raises(AttributeError): - assert data.baz == 0 + # ...but they default to None when the attribute is accessed + assert data.baz is None # Optional fields can be omitted (fields with defaults are optional) data = NormalUsageData(foo="asdf") diff --git a/tests/test_optional.py b/tests/test_optional.py index 3205ec2..3e57494 100644 --- a/tests/test_optional.py +++ b/tests/test_optional.py @@ -43,11 +43,12 @@ def test_minimally_defined(): assert "field_with_default" in data assert "optional_field2_with_none_default" in data assert "optional_field3_with_default" in data - with pytest.raises(AttributeError): - # Trying to reference the Optional field will result in a AttributeError - # To determine whether an Optional field is present, the user must check - # whether `"" in ` (see above). - assert data.optional_field1 is None + + # Referencing the Optional field will return None. + # To differentiate between an actual value of None and the field being absent, + # the user must check whether `"" in ` (see above). + assert data.optional_field1 is None + s = json.dumps(data) assert "required_field" in s assert "optional_field1" not in s @@ -62,7 +63,10 @@ def test_getattr(): data = OptionalData.example_values()["minimally_defined"] assert "optional_field1" not in data - assert getattr(data, "optional_field1", "getattrdefault") == "getattrdefault" + + # To differentiate between an actual value of None and the field being absent, + # the user must check whether `"" in ` (see above). + assert getattr(data, "optional_field1", "getattrdefault") is None def test_provide_optional_field(): From abddba4ad60f9af2aab7456eef02fa593a15842a Mon Sep 17 00:00:00 2001 From: Benjamin Pelletier Date: Fri, 18 Sep 2026 02:36:19 +0000 Subject: [PATCH 2/3] `uv run --index https://pypi.org/simple ruff check --fix` --- tests/test_optional.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_optional.py b/tests/test_optional.py index 3e57494..9148685 100644 --- a/tests/test_optional.py +++ b/tests/test_optional.py @@ -1,7 +1,5 @@ import json -import pytest - from implicitdict import ImplicitDict from .test_types import OptionalData From 310b4b99ea5e3f9d770dc9a09533904dce117fef Mon Sep 17 00:00:00 2001 From: Benjamin Pelletier Date: Fri, 18 Sep 2026 02:46:49 +0000 Subject: [PATCH 3/3] Fix basedpyright, correct Action name --- .github/workflows/hygiene.yaml | 2 +- tests/test_docpattern.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/hygiene.yaml b/.github/workflows/hygiene.yaml index cfb96da..9fcce52 100644 --- a/.github/workflows/hygiene.yaml +++ b/.github/workflows/hygiene.yaml @@ -11,7 +11,7 @@ on: jobs: hygiene: runs-on: ubuntu-latest - name: Ruff + name: ruff and basedpyright steps: - uses: actions/checkout@v3 - name: setup-python diff --git a/tests/test_docpattern.py b/tests/test_docpattern.py index 2029c21..e9f1954 100644 --- a/tests/test_docpattern.py +++ b/tests/test_docpattern.py @@ -8,10 +8,16 @@ class ResponseType(ImplicitDict): pass +class QueryResponse(ImplicitDict): + json: dict + + class Query(ImplicitDict): participant_id: str | None """If specified, identifier of the USS/participant hosting the server involved in this query.""" + response: QueryResponse + def parse_json_result(self, parse_type: type[ResponseType]) -> ResponseType: """Parses the JSON result into the specified type.