Skip to content

fix: Element.attrs returns all values of a multivalued attribute (0.2.0) - #10

Merged
mzargham merged 2 commits into
mainfrom
fix/element-attrs-multivalued
Sep 19, 2026
Merged

mzargham merged 2 commits into
mainfrom
fix/element-attrs-multivalued

Conversation

@mzargham

Copy link
Copy Markdown
Contributor

What was wrong

Element.attrs walked the element's triples assigning attrs[name] = value, so each repeated value overwrote the last. An attribute declared multiple=True came back as one arbitrary value, with no indication the others existed — and which one you got depended on triple-store iteration order.

kc.add_vertex("d1", type="Doc", title="Guide", tag=["a", "b", "c"])
kc.element("d1").attrs["tag"]   # before: "c", or "a", or "b"
                                # after:  ["a", "b", "c"]

Found while building a downstream model where source and facts are both multivalued: provenance was being silently dropped on read.

What changed

  • An attribute declared multiple=True returns a sorted list, even when it currently holds one value — so the type doesn't depend on how many values happen to be recorded. Every other attribute is unchanged and still returns a string.
  • Sorting is what makes repeated reads agree; the store has no inherent order of its own.
  • Adds SchemaBuilder.multivalued_attributes(type_name). describe_type only returns repr strings, so nothing else exposed the descriptors to a caller that needed to read an element back faithfully.
  • Five regression tests, including that a single value is still a list, and that repeated reads agree.
  • Tutorial documents the behaviour.

Why 0.2.0 and not 0.1.1

main already carries a breaking change since the 0.1.0 release: the core namespace moved from https://example.org/kc# to https://w3id.org/kc# (f4a04a8). That is worth calling out, because a downstream SHACL constraint written against the wrong namespace matches nothing rather than failing — every edge silently appears untyped. I hit exactly this pinning a dependent repo to knowledgecomplex>=0.1.0 and resolving the stale PyPI wheel.

Verification

402 passed, 6 skipped — full suite, no regressions.

attrs walked the element's triples assigning attrs[name] = value, so each
repeated value overwrote the last. A caller reading an attribute declared
multiple=True got one arbitrary value with no indication the others existed,
and which one it got depended on triple-store iteration order.

An attribute declared multiple=True now comes back as a sorted list of its
values, even when it currently holds one, so the type does not depend on how
many values happen to be recorded. Every other attribute is unchanged.
Sorting is what makes repeated reads agree, the store having no inherent
order of its own.

Adds SchemaBuilder.multivalued_attributes(type_name), since describe_type
returns repr strings and nothing else exposed the descriptors.

Version 0.2.0 rather than 0.1.1: main already carries a breaking change
since the 0.1.0 release, the core namespace having moved from
https://example.org/kc# to https://w3id.org/kc#. A downstream SHACL
constraint written against the wrong namespace matches nothing rather than
failing, so every edge silently appears untyped.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Element.attrs currently catches Exception broadly, which can mask unexpected failures and should be narrowed to the expected error cases.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

This PR fixes Element.attrs so schema-declared multivalued attributes (multiple=True) are returned as a complete, deterministic collection (sorted list) instead of an arbitrary single value, and exposes schema metadata to let callers distinguish multivalued attributes when reading elements back.

Changes:

  • Update Element.attrs to aggregate and sort values for multivalued attributes while preserving single-valued behavior for others.
  • Add SchemaBuilder.multivalued_attributes(type_name) to report which attributes are declared multiple=True (including inheritance).
  • Add regression tests and tutorial documentation describing the new behavior; bump package version to 0.2.0.
File Description
knowledgecomplex/​graph.py Aggregates repeated attribute triples into sorted lists for schema-declared multivalued attributes.
knowledgecomplex/​schema.py Adds schema API to enumerate multivalued attributes for a given type (incl. inherited).
tests/​test_element.py Adds regression tests covering multivalued aggregation, stable typing, and schema reporting.
docs/​tutorial.md Documents attrs multivalued behavior and the new schema helper API.
pyproject.toml Bumps project version to 0.2.0.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread knowledgecomplex/graph.py
Comment on lines +135 to +140
try:
multivalued = self._kc._schema.multivalued_attributes(self.type)
except Exception:
# An element whose type is unregistered or absent still has attributes
# worth reading; fall back to treating them all as single-valued.
multivalued = frozenset()
Catching Exception could mask a real failure inside multivalued_attributes and
report it as "this type has no multivalued attributes", which would silently
return the very collapsed values this change set out to fix.

Only two errors are expected: ValueError from .type when the element carries no
user type, and SchemaError when that type is not registered. Both mean the same
thing — the schema cannot say which attributes are multivalued — and the
attributes are still worth reading, so the fallback stands for those and for
nothing else.

Two tests, one for each side of the boundary: an element with no registered type
still reads, and an unexpected error is not swallowed.
@mzargham

Copy link
Copy Markdown
Contributor Author

Addressed in f38214b.

Narrowed to (ValueError, SchemaError) — the two errors that can actually occur here: ValueError from .type when the element carries no user type, and SchemaError when that type is not registered. Both mean the same thing, that the schema cannot say which attributes are multivalued.

The catch was worth making specific for a reason beyond hygiene: a failure inside multivalued_attributes would have been reported as "this type has no multivalued attributes", and attrs would then have silently returned the collapsed single values this PR exists to fix. The broad except could have reintroduced the bug it was written to guard.

Added a test for each side of the boundary — an element with no registered type still reads its attributes, and an unexpected error surfaces rather than being swallowed.

Full suite: 404 passed, 6 skipped.

@mzargham
mzargham merged commit 20ac5bd into main Sep 19, 2026
2 checks passed
@mzargham
mzargham deleted the fix/element-attrs-multivalued branch September 19, 2026 18:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants