fix(core): return a list from load_entity and pair iris by title - #208
Conversation
- LoadEntityResult.entities is now List[OswBaseModel]; pydantic v1 tried the Union's first member, so an empty result became a truthy OswBaseModel that is neither subscriptable nor sized - resolve() keys the loaded entities by full page title instead of zipping them against request.iris; load_entity skips pages it cannot build, which shifted every later iri onto the wrong entity - an iri with no matching entity is logged and left out of nodes - export_entity_jsonld drops the now dead isinstance branch that hid the empty result from its NotFound check Closes #198
Release previewMerging this PR would release v2.8.1 (current: Changelog preview (truncated)## v2.8.1 (2026-09-23)
### Bug Fixes
- **core**: Map an unresolved iri to None instead of omitting it
([`19130e6`](https://github.com/OpenSemanticLab/osw-python/commit/19130e64d6ed56527ef240d8eef901169211e680))
- **core**: Return a list from load_entity and pair iris by title
([`6c6ee6d`](https://github.com/OpenSemanticLab/osw-python/commit/6c6ee6da03467fafc71a54a0caf3dbf4a3d51eb2))
### Documentation
- Point repo links at the versioned /latest/ docs paths
([#206](https://github.com/OpenSemanticLab/osw-python/pull/206),
[`c21ad24`](https://github.com/OpenSemanticLab/osw-python/commit/c21ad241078e64d05171b9ae2907c007590384b1))
Preview via python-semantic-release and conventional commits. |
oold types ResolveResult.nodes as Dict[str, Union[None, ...]] and indexes it by iri in three places without checking for the key, so omitting an unresolved iri turns a skipped page into a KeyError.
|
Follow-up commit 19130e6: an unresolved iri now maps to
The warning log is unchanged. The test now asserts |
Closes #198.
Changes
LoadEntityResult.entitiesis nowList[model.OswBaseModel]instead ofUnion[model.OswBaseModel, List[model.OswBaseModel]](
src/osw/core.py:1292).OswDefaultBackend.resolve()builds a{full_title: entity}lookup anditerates
request.iris, instead ofzip-ping the two lists(
src/osw/core.py:271-283)._logger.warningand leftout of
nodes.export_entity_jsonlddrops itsisinstance(entities, list)branch(
src/osw/service/ops/entities.py:94).tests/test_load_entity_result_shape.py.Rationale
The empty result was not a list. pydantic v1 tries the first member of a
Unionfirst, andOswBaseModel.validate([])succeeds because it falls backto
dict([]) == {}. A load that produced no entity therefore returnedOswBaseModel(), which is truthy, not subscriptable and has no length.Reproduced in the project environment.
load_entityis the only place thatconstructs
LoadEntityResultand always passes a list, so theUnionhad noother purpose. A non-empty list was never affected, and the element subclass
still survives validation.
resolve()paired iris with the wrong entities.load_entityskips apage whose schema is missing (
src/osw/core.py:1387) or whose constructionfails (
src/osw/core.py:1407). The returned list is then shorter than therequested titles, so
zipmoved every later iri onto the entity of afollowing page.
load_entityalready setsentity.meta.wiki_page.namespaceand
.title, which is whatget_full_titlereads, so the title is availablefor an exact pairing.
The
isinstancebranch inexport_entity_jsonldwrapped the non-list resultback into a list, so its
if not entitiescheck never raisedNotFound. Withthe field typed as a list the branch is dead code.
Verification
test_load_entity_result_keeps_empty_list_as_listandtest_resolve_pairs_iris_by_title_not_position.pytest tests -q: 800 passed.make check: pre-commit, ruff, ty and deptry all pass.Note
#166 changes the class
selection inside the same function but touches neither of these two
behaviours, so the two branches do not overlap in substance.