Skip to content

Commit 5f38b93

Browse files
huwcbjoneslovasoadairiki
authored
Add Meta option to include non init-ed fields (#246)
* fix bitrot in tests * Add Meta option to include non init-ed fields Updates #60 * ensure fields are always compared in a deterministic order * maxdiff none * update pypy * skip failing test on python3.6 * Revert "skip failing test on python3.6" This reverts commit e8e29b9. * fixup! fix bitrot in tests We do care about the order of Union.union_fields. This partially reverts commit cbad82b. * Ignore union ordering --------- Co-authored-by: lovasoa <pere.jobs@gmail.com> Co-authored-by: Jeff Dairiki <dairiki@dairiki.org>
1 parent 06f0660 commit 5f38b93

File tree

5 files changed

+60
-7
lines changed

5 files changed

+60
-7
lines changed

.github/workflows/python-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
fail-fast: false
1313
matrix:
1414
os: ["ubuntu-latest"]
15-
python_version: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.9"]
15+
python_version: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.10"]
1616
include:
1717
- os: "ubuntu-20.04"
1818
python_version: "3.6"

marshmallow_dataclass/__init__.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ def class_schema(
297297
>>> person
298298
Person(name='Anonymous', friends=[Person(name='Roger Boucher', friends=[])])
299299
300+
Marking dataclass fields as non-initialized (``init=False``), by default, will result in those
301+
fields from being exluded in the schema. To override this behaviour, set the ``Meta`` option
302+
``include_non_init=True``.
300303
>>> @dataclasses.dataclass()
301304
... class C:
302305
... important: int = dataclasses.field(init=True, default=0)
@@ -310,6 +313,20 @@ def class_schema(
310313
>>> c
311314
C(important=9, unimportant=0)
312315
316+
>>> @dataclasses.dataclass()
317+
... class C:
318+
... class Meta:
319+
... include_non_init = True
320+
... important: int = dataclasses.field(init=True, default=0)
321+
... unimportant: int = dataclasses.field(init=False, default=0)
322+
...
323+
>>> c = class_schema(C)().load({
324+
... "important": 9, # This field will be imported
325+
... "unimportant": 9 # This field will be imported
326+
... }, unknown=marshmallow.EXCLUDE)
327+
>>> c
328+
C(important=9, unimportant=9)
329+
313330
>>> @dataclasses.dataclass
314331
... class Website:
315332
... url:str = dataclasses.field(metadata = {
@@ -408,6 +425,9 @@ def _internal_class_schema(
408425
if hasattr(v, "__marshmallow_hook__") or k in MEMBERS_WHITELIST
409426
}
410427

428+
# Determine whether we should include non-init fields
429+
include_non_init = getattr(getattr(clazz, "Meta", None), "include_non_init", False)
430+
411431
# Update the schema members to contain marshmallow fields instead of dataclass fields
412432
type_hints = get_type_hints(
413433
clazz, localns=clazz_frame.f_locals if clazz_frame else None
@@ -424,7 +444,7 @@ def _internal_class_schema(
424444
),
425445
)
426446
for field in fields
427-
if field.init
447+
if field.init or include_non_init
428448
)
429449

430450
schema_class = type(clazz.__name__, (_base_schema(clazz, base_schema),), attributes)

tests/test_class_schema.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,29 @@ class Second:
434434
{"first": {"second": {"first": None}}},
435435
)
436436

437+
def test_init_fields(self):
438+
@dataclasses.dataclass
439+
class NoMeta:
440+
no_init: str = dataclasses.field(init=False)
441+
442+
@dataclasses.dataclass
443+
class NoInit:
444+
class Meta:
445+
pass
446+
447+
no_init: str = dataclasses.field(init=False)
448+
449+
@dataclasses.dataclass
450+
class Init:
451+
class Meta:
452+
include_non_init = True
453+
454+
no_init: str = dataclasses.field(init=False)
455+
456+
self.assertNotIn("no_init", class_schema(NoMeta)().fields)
457+
self.assertNotIn("no_init", class_schema(NoInit)().fields)
458+
self.assertIn("no_init", class_schema(Init)().fields)
459+
437460

438461
if __name__ == "__main__":
439462
unittest.main()

tests/test_field_for_schema.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,24 @@
2121

2222

2323
class TestFieldForSchema(unittest.TestCase):
24+
maxDiff = None
25+
2426
def assertFieldsEqual(self, a: fields.Field, b: fields.Field):
2527
self.assertEqual(a.__class__, b.__class__, "field class")
2628

29+
def canonical(k, v):
30+
if k == "union_fields":
31+
# See https://github.com/lovasoa/marshmallow_dataclass/pull/246#issuecomment-1722291806
32+
return k, sorted(map(repr, v))
33+
elif inspect.isclass(v):
34+
return k, f"{v!r} ({v.__mro__!r})"
35+
else:
36+
return k, repr(v)
37+
2738
def attrs(x):
28-
return {
29-
k: f"{v!r} ({v.__mro__!r})" if inspect.isclass(v) else repr(v)
30-
for k, v in x.__dict__.items()
31-
if not k.startswith("_")
32-
}
39+
return sorted(
40+
canonical(k, v) for k, v in vars(x).items() if not k.startswith("_")
41+
)
3342

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

tests/test_mypy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
follow_imports = silent
77
plugins = marshmallow_dataclass.mypy
88
show_error_codes = true
9+
python_version = 3.6
910
env:
1011
- PYTHONPATH=.
1112
main: |

0 commit comments

Comments
 (0)