Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/hygiene.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions src/implicitdict/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
6 changes: 6 additions & 0 deletions tests/test_docpattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
4 changes: 2 additions & 2 deletions tests/test_normal_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
18 changes: 10 additions & 8 deletions tests/test_optional.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import json

import pytest

from implicitdict import ImplicitDict

from .test_types import OptionalData
Expand Down Expand Up @@ -43,11 +41,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 `"<FIELD_NAME>" in <OBJECT>` (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 `"<FIELD_NAME>" in <OBJECT>` (see above).
assert data.optional_field1 is None

s = json.dumps(data)
assert "required_field" in s
assert "optional_field1" not in s
Expand All @@ -62,7 +61,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 `"<FIELD_NAME>" in <OBJECT>` (see above).
assert getattr(data, "optional_field1", "getattrdefault") is None


def test_provide_optional_field():
Expand Down
Loading