Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Meta option to include non init-ed fields #246

Merged
merged 9 commits into from
Sep 16, 2023
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/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python_version: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.9"]
python_version: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.10"]
include:
- os: "ubuntu-20.04"
python_version: "3.6"
Expand Down
22 changes: 21 additions & 1 deletion marshmallow_dataclass/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ def class_schema(
>>> person
Person(name='Anonymous', friends=[Person(name='Roger Boucher', friends=[])])

Marking dataclass fields as non-initialized (``init=False``), by default, will result in those
fields from being exluded in the schema. To override this behaviour, set the ``Meta`` option
``include_non_init=True``.
>>> @dataclasses.dataclass()
... class C:
... important: int = dataclasses.field(init=True, default=0)
Expand All @@ -310,6 +313,20 @@ def class_schema(
>>> c
C(important=9, unimportant=0)

>>> @dataclasses.dataclass()
... class C:
... class Meta:
... include_non_init = True
... important: int = dataclasses.field(init=True, default=0)
... unimportant: int = dataclasses.field(init=False, default=0)
...
>>> c = class_schema(C)().load({
... "important": 9, # This field will be imported
... "unimportant": 9 # This field will be imported
... }, unknown=marshmallow.EXCLUDE)
>>> c
C(important=9, unimportant=9)

>>> @dataclasses.dataclass
... class Website:
... url:str = dataclasses.field(metadata = {
Expand Down Expand Up @@ -408,6 +425,9 @@ def _internal_class_schema(
if hasattr(v, "__marshmallow_hook__") or k in MEMBERS_WHITELIST
}

# Determine whether we should include non-init fields
include_non_init = getattr(getattr(clazz, "Meta", None), "include_non_init", False)
huwcbjones marked this conversation as resolved.
Show resolved Hide resolved

# Update the schema members to contain marshmallow fields instead of dataclass fields
type_hints = get_type_hints(
clazz, localns=clazz_frame.f_locals if clazz_frame else None
Expand All @@ -424,7 +444,7 @@ def _internal_class_schema(
),
)
for field in fields
if field.init
if field.init or include_non_init
)

schema_class = type(clazz.__name__, (_base_schema(clazz, base_schema),), attributes)
Expand Down
23 changes: 23 additions & 0 deletions tests/test_class_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,29 @@ class Second:
{"first": {"second": {"first": None}}},
)

def test_init_fields(self):
@dataclasses.dataclass
class NoMeta:
no_init: str = dataclasses.field(init=False)

@dataclasses.dataclass
class NoInit:
class Meta:
pass

no_init: str = dataclasses.field(init=False)

@dataclasses.dataclass
class Init:
class Meta:
include_non_init = True

no_init: str = dataclasses.field(init=False)

self.assertNotIn("no_init", class_schema(NoMeta)().fields)
self.assertNotIn("no_init", class_schema(NoInit)().fields)
self.assertIn("no_init", class_schema(Init)().fields)


if __name__ == "__main__":
unittest.main()
19 changes: 14 additions & 5 deletions tests/test_field_for_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,24 @@


class TestFieldForSchema(unittest.TestCase):
maxDiff = None

def assertFieldsEqual(self, a: fields.Field, b: fields.Field):
self.assertEqual(a.__class__, b.__class__, "field class")

def canonical(k, v):
if k == "union_fields":
# See https://github.com/lovasoa/marshmallow_dataclass/pull/246#issuecomment-1722291806
return k, sorted(map(repr, v))
elif inspect.isclass(v):
return k, f"{v!r} ({v.__mro__!r})"
else:
return k, repr(v)

def attrs(x):
return {
k: f"{v!r} ({v.__mro__!r})" if inspect.isclass(v) else repr(v)
for k, v in x.__dict__.items()
if not k.startswith("_")
}
return sorted(
canonical(k, v) for k, v in vars(x).items() if not k.startswith("_")
)

self.assertEqual(attrs(a), attrs(b))

Expand Down
1 change: 1 addition & 0 deletions tests/test_mypy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
follow_imports = silent
plugins = marshmallow_dataclass.mypy
show_error_codes = true
python_version = 3.6
env:
- PYTHONPATH=.
main: |
Expand Down