Skip to content

Commit a93d786

Browse files
jmclaude
andcommitted
refactor: fold repeated model field parsing into _init_fields
Every model repeated the same two-step per attribute: assign a default, then `if 'x' in api_resource: self.x = api_resource['x']`. That was 36 membership blocks across the module, and the shape of it is what let a mutable default drift back to class level. Both steps now go through one shared helper on HALResource: self._init_fields(api_resource, name=None, netid=None, canLogIn=False, ...) Each field is named once, beside its default. `_init_fields` takes the value from the resource when the key is present and otherwise installs a per-instance copy of the default via `_fresh()`, so a declared mutable default can never be shared between instances. An optional positional-only `copy` argument carries the per-field copy depth the old code had - `deepcopy` for metadata, a shallow `_shallow` for checkSum and sections - instead of flattening them to one rule. Attribute *types* stay in the class body as bare annotations, which declare a type without creating a class attribute: mypy and pylint keep resolving every member, and the structural guard test stays absolute (there is deliberately no class-level spec constant needing an allowlist). Genuinely special logic stays written out: the HAL `_links`/`_embedded` envelope and its self-href placeholder, DSpaceObject's `dso=` copy path and its local-only lastModified/parent, Item's inArchive (whose default depends on whether an API resource was supplied at all), the `type` literal stamps, and ResourcePolicy's `_embedded` group fallback. Effect: 36 membership blocks -> 2 (both genuinely special), 369 -> 336 code lines. The exhaustive old-vs-new comparison - every class x {no-arg, {}, full resource, None} x every attribute, plus both `dso=` paths, `Item.from_dso`, `as_dict()` and `to_dict()` - reports exactly one difference: EntityType(None) now builds an empty instance instead of raising TypeError, matching every other model's `api_resource or {}` tolerance. EntityType is never constructed in either repository; the CHANGELOG records it alongside Group()/User(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 0be01ec commit a93d786

3 files changed

Lines changed: 180 additions & 158 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Date: Unreleased
2121
7. Model attribute defaults moved from the class body into `__init__` as plain
2222
instance attributes, so no instance can share (or mutate) a class-level
2323
`links`, `embedded`, `metadata`, `checkSum` or `sections` dict. `Group()` /
24-
`User()` accept a `None` API resource, and `Item.from_dso` /
24+
`User()` and `EntityType()` accept a `None` API resource, and `Item.from_dso` /
2525
`DSpaceObject(dso=...)` deep-copy metadata instead of aliasing it. Side effect:
2626
`id` on a `DSpaceObject` (and its subclasses) and `label` on an `EntityType`
2727
now default to `None` rather than raising `AttributeError` when the API

0 commit comments

Comments
 (0)