sdcreader 4.3.1__tar.gz → 4.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. {sdcreader-4.3.1/src/sdcreader.egg-info → sdcreader-4.4.0}/PKG-INFO +5 -3
  2. {sdcreader-4.3.1 → sdcreader-4.4.0}/README.md +4 -2
  3. {sdcreader-4.3.1 → sdcreader-4.4.0}/pyproject.toml +1 -1
  4. {sdcreader-4.3.1 → sdcreader-4.4.0}/src/sdcreader/__init__.py +1 -1
  5. {sdcreader-4.3.1 → sdcreader-4.4.0}/src/sdcreader/mcp_server.py +2 -1
  6. {sdcreader-4.3.1 → sdcreader-4.4.0}/src/sdcreader/record.py +24 -8
  7. {sdcreader-4.3.1 → sdcreader-4.4.0/src/sdcreader.egg-info}/PKG-INFO +5 -3
  8. {sdcreader-4.3.1 → sdcreader-4.4.0}/tests/test_record.py +23 -0
  9. {sdcreader-4.3.1 → sdcreader-4.4.0}/LICENSE +0 -0
  10. {sdcreader-4.3.1 → sdcreader-4.4.0}/NOTICE +0 -0
  11. {sdcreader-4.3.1 → sdcreader-4.4.0}/setup.cfg +0 -0
  12. {sdcreader-4.3.1 → sdcreader-4.4.0}/src/sdcreader/mcp.py +0 -0
  13. {sdcreader-4.3.1 → sdcreader-4.4.0}/src/sdcreader/model.py +0 -0
  14. {sdcreader-4.3.1 → sdcreader-4.4.0}/src/sdcreader/package.py +0 -0
  15. {sdcreader-4.3.1 → sdcreader-4.4.0}/src/sdcreader.egg-info/SOURCES.txt +0 -0
  16. {sdcreader-4.3.1 → sdcreader-4.4.0}/src/sdcreader.egg-info/dependency_links.txt +0 -0
  17. {sdcreader-4.3.1 → sdcreader-4.4.0}/src/sdcreader.egg-info/entry_points.txt +0 -0
  18. {sdcreader-4.3.1 → sdcreader-4.4.0}/src/sdcreader.egg-info/requires.txt +0 -0
  19. {sdcreader-4.3.1 → sdcreader-4.4.0}/src/sdcreader.egg-info/top_level.txt +0 -0
  20. {sdcreader-4.3.1 → sdcreader-4.4.0}/tests/test_mcp_scaffold.py +0 -0
  21. {sdcreader-4.3.1 → sdcreader-4.4.0}/tests/test_mcp_server.py +0 -0
  22. {sdcreader-4.3.1 → sdcreader-4.4.0}/tests/test_reader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sdcreader
3
- Version: 4.3.1
3
+ Version: 4.4.0
4
4
  Summary: Read a published Semantic Data Charter model's package for projection into other standards (CDIF, DCAT-US, DCAT-AP, HealthDCAT-AP); MCP server included.
5
5
  Author: Axius SDC, Inc.
6
6
  License: Apache-2.0
@@ -97,8 +97,10 @@ that defines it. `source` is the record's bytes, its text or a path; a record of
97
97
 
98
98
  - **`Record`**: `attributes` (`instance_id`, `instance_version`, `creation_timestamp`, `current-state`, ...),
99
99
  `values` in document order, and `exceptional`, the values that carry an Exceptional Value.
100
- - **`RecordValue`**: the `component`, its `path` of clusters (root first), an `occurrence` index for a component
101
- repeated under the same path, its `position`, its `elements` as the record holds them (a quantity's units nested:
100
+ - **`RecordValue`**: the `component`, its `path` of clusters (root first), `instances` parallel to the path (which
101
+ occurrence each cluster is among the same cluster's repeats under the same parent; `instance_key` pairs them with the
102
+ clusters' identifiers, one key per cluster instance), an `occurrence` index for a component repeated within its
103
+ cluster instance, its `position`, its `elements` as the record holds them (a quantity's units nested:
102
104
  `{"xdquantity-value": "1", "xdquantity-units": {"label": "Trade Units", "xdstring-value": "EA"}}`), and its
103
105
  `context` (`act`, `vtb`, `vte`, `tr`, `modified`, `latitude`, `longitude`).
104
106
  - **`ExceptionalValue`**: the ISO 21090 null flavor (`code`, `name`) and its `origin`: `source` when it stands in
@@ -72,8 +72,10 @@ that defines it. `source` is the record's bytes, its text or a path; a record of
72
72
 
73
73
  - **`Record`**: `attributes` (`instance_id`, `instance_version`, `creation_timestamp`, `current-state`, ...),
74
74
  `values` in document order, and `exceptional`, the values that carry an Exceptional Value.
75
- - **`RecordValue`**: the `component`, its `path` of clusters (root first), an `occurrence` index for a component
76
- repeated under the same path, its `position`, its `elements` as the record holds them (a quantity's units nested:
75
+ - **`RecordValue`**: the `component`, its `path` of clusters (root first), `instances` parallel to the path (which
76
+ occurrence each cluster is among the same cluster's repeats under the same parent; `instance_key` pairs them with the
77
+ clusters' identifiers, one key per cluster instance), an `occurrence` index for a component repeated within its
78
+ cluster instance, its `position`, its `elements` as the record holds them (a quantity's units nested:
77
79
  `{"xdquantity-value": "1", "xdquantity-units": {"label": "Trade Units", "xdstring-value": "EA"}}`), and its
78
80
  `context` (`act`, `vtb`, `vte`, `tr`, `modified`, `latitude`, `longitude`).
79
81
  - **`ExceptionalValue`**: the ISO 21090 null flavor (`code`, `name`) and its `origin`: `source` when it stands in
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sdcreader"
7
- version = "4.3.1"
7
+ version = "4.4.0"
8
8
  description = "Read a published Semantic Data Charter model's package for projection into other standards (CDIF, DCAT-US, DCAT-AP, HealthDCAT-AP); MCP server included."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -16,7 +16,7 @@ from .model import (CLOSE, DC_DEFAULTS, EXACT, HAS_UNIT, IDENTIFIER, LEAF_TYPES,
16
16
  from .package import DEFAULT_HOST, ModelPackage, PackageError, fetch_package, load_package
17
17
  from .record import CONTEXT_ELEMENTS, EXCEPTIONAL_VALUES, RECORD_ATTRIBUTES, VALUE_ELEMENTS, ExceptionalValue, Record, RecordError, RecordValue, read_record
18
18
 
19
- __version__ = "4.3.1"
19
+ __version__ = "4.4.0"
20
20
  __all__ = ["ModelPackage", "PackageError", "DEFAULT_HOST", "load_package", "fetch_package", "read_model", "read_header", "enumeration_iri",
21
21
  "Model", "Component", "Leaf", "Code", "LEAF_TYPES", "NUMERIC_TYPES", "QUANTIFIED_TYPES", "DC_DEFAULTS", "IDENTIFIER", "SOURCE",
22
22
  "PUBLISHER", "EXACT", "CLOSE", "SEE_ALSO", "HAS_UNIT", "read_record", "Record", "RecordValue", "RecordError", "ExceptionalValue",
@@ -311,7 +311,8 @@ def _handle_read_record(args: dict[str, Any]) -> Any:
311
311
  values = [
312
312
  {
313
313
  "position": v.position, "ct_id": v.component.ct_id, "label": v.component.label, "sdc_type": v.component.sdc_type,
314
- "path": [p.label for p in v.path], "occurrence": v.occurrence, "elements": v.elements, "context": v.context,
314
+ "path": [p.label for p in v.path], "instances": list(v.instances), "occurrence": v.occurrence,
315
+ "elements": v.elements, "context": v.context,
315
316
  "exceptional": ({"code": v.exceptional.code, "name": v.exceptional.name, "origin": v.exceptional.origin}
316
317
  if v.exceptional else None),
317
318
  }
@@ -71,12 +71,21 @@ class ExceptionalValue:
71
71
  class RecordValue:
72
72
  component: Component
73
73
  path: list[Component] # the clusters above it, root first
74
- occurrence: int # 0 for the first time this component appears under this path, 1 for the next, ...
74
+ occurrence: int # 0 for the first time this component appears in its cluster instance, 1 for the next
75
75
  position: int # document order among the record's values, 0-based
76
76
  elements: dict # the value elements as the record holds them: name -> text, or name -> dict for a
77
77
  # nested element (a quantity's units: {"label": "Trade Units", "xdstring-value": "EA"})
78
78
  context: dict[str, str] # act, vtb, vte, tr, modified, latitude, longitude, where present
79
79
  exceptional: ExceptionalValue | None = None
80
+ instances: tuple[int, ...] = () # parallel to `path`: which occurrence each cluster is among the same cluster's
81
+ # repeats under the same parent instance (4.4.0). Two lines of one Line cluster
82
+ # in one Lines cluster are (..., 0) and (..., 1).
83
+
84
+ @property
85
+ def instance_key(self) -> tuple[tuple[str, int], ...]:
86
+ """The value's cluster instance, root first: (ct_id, occurrence) for every cluster above it. Values with equal
87
+ keys belong to the same cluster instance; a mapper writes one target element per key."""
88
+ return tuple(zip((c.ct_id for c in self.path), self.instances))
80
89
 
81
90
  @property
82
91
  def rejected(self) -> dict:
@@ -147,9 +156,11 @@ def read_record(source: bytes | str | Path, model: Model) -> Record:
147
156
  attributes[n] = _text(el)
148
157
 
149
158
  record = Record(model=model, attributes=attributes)
150
- seen: dict[tuple, int] = {}
159
+ seen: dict[tuple, int] = {} # leaf occurrences, per cluster instance
160
+ clusters: dict[tuple, int] = {} # cluster occurrences, per parent cluster instance
151
161
 
152
- def walk(el, path: list[Component]):
162
+ def walk(el, path: list[Component], instances: tuple[int, ...]):
163
+ parent = tuple(zip((c.ct_id for c in path), instances))
153
164
  for child in el:
154
165
  if not _is_sdc4(child):
155
166
  continue
@@ -158,16 +169,21 @@ def read_record(source: bytes | str | Path, model: Model) -> Record:
158
169
  continue
159
170
  comp = model.components.get(n[3:])
160
171
  if comp is None: # an adapter: pass through to the component inside
161
- walk(child, path)
172
+ walk(child, path, instances)
162
173
  elif comp.sdc_type == "Cluster" or comp.sdc_type not in LEAF_TYPES:
163
- walk(child, path + [comp]) # a cluster, or a structural component (audit, party, ...) holding values
174
+ key = (parent, comp.ct_id) # a cluster, or a structural component (audit, party, ...) holding values
175
+ k = clusters.get(key, 0)
176
+ clusters[key] = k + 1
177
+ walk(child, path + [comp], instances + (k,))
164
178
  else:
165
- key = (comp.ct_id, tuple(c.ct_id for c in path))
179
+ key = (comp.ct_id, parent)
166
180
  occurrence = seen.get(key, 0)
167
181
  seen[key] = occurrence + 1
168
- record.values.append(_value(child, comp, path, occurrence, len(record.values)))
182
+ v = _value(child, comp, path, occurrence, len(record.values))
183
+ v.instances = instances
184
+ record.values.append(v)
169
185
 
170
- walk(root, [])
186
+ walk(root, [], ())
171
187
  return record
172
188
 
173
189
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sdcreader
3
- Version: 4.3.1
3
+ Version: 4.4.0
4
4
  Summary: Read a published Semantic Data Charter model's package for projection into other standards (CDIF, DCAT-US, DCAT-AP, HealthDCAT-AP); MCP server included.
5
5
  Author: Axius SDC, Inc.
6
6
  License: Apache-2.0
@@ -97,8 +97,10 @@ that defines it. `source` is the record's bytes, its text or a path; a record of
97
97
 
98
98
  - **`Record`**: `attributes` (`instance_id`, `instance_version`, `creation_timestamp`, `current-state`, ...),
99
99
  `values` in document order, and `exceptional`, the values that carry an Exceptional Value.
100
- - **`RecordValue`**: the `component`, its `path` of clusters (root first), an `occurrence` index for a component
101
- repeated under the same path, its `position`, its `elements` as the record holds them (a quantity's units nested:
100
+ - **`RecordValue`**: the `component`, its `path` of clusters (root first), `instances` parallel to the path (which
101
+ occurrence each cluster is among the same cluster's repeats under the same parent; `instance_key` pairs them with the
102
+ clusters' identifiers, one key per cluster instance), an `occurrence` index for a component repeated within its
103
+ cluster instance, its `position`, its `elements` as the record holds them (a quantity's units nested:
102
104
  `{"xdquantity-value": "1", "xdquantity-units": {"label": "Trade Units", "xdstring-value": "EA"}}`), and its
103
105
  `context` (`act`, `vtb`, `vte`, `tr`, `modified`, `latitude`, `longitude`).
104
106
  - **`ExceptionalValue`**: the ISO 21090 null flavor (`code`, `name`) and its `origin`: `source` when it stands in
@@ -145,3 +145,26 @@ def test_recovery_is_recognized_for_every_value_element(model, sdc_type, value_e
145
145
  comp = Component(ct_id="x", sdc_type=sdc_type, label="L", description="", data_type="", constraints={}, links={})
146
146
  v = _value(el, comp, [], 0, 0)
147
147
  assert v.exceptional.origin == "recovery" and v.rejected == {value_el: "bad"}
148
+
149
+
150
+ def test_cluster_instances_number_repeats_under_the_same_parent(model):
151
+ """4.4.0: a cluster repeated under the same parent instance is numbered, and every value in it carries the number,
152
+ so a mapper can write one target element per instance."""
153
+ tree = etree.parse(str(RECORD))
154
+ _, comp = _first_string(tree)
155
+ cluster = comp.getparent().getparent() # the cluster holding the first value (through its adapter)
156
+ twin = etree.fromstring(etree.tostring(cluster))
157
+ cluster.addnext(twin)
158
+ r = read_record(etree.tostring(tree), model)
159
+ ct = comp.tag.split("ms-", 1)[1]
160
+ first, second = [v for v in r.values if v.component.ct_id == ct][:2]
161
+ assert first.path == second.path
162
+ assert first.instances[-1] == 0 and second.instances[-1] == 1
163
+ assert first.instance_key != second.instance_key
164
+ assert first.occurrence == second.occurrence == 0, "occurrence counts within a cluster instance"
165
+
166
+
167
+ def test_every_value_has_an_instance_number_per_cluster_in_its_path(model):
168
+ r = read_record(RECORD, model)
169
+ assert all(len(v.instances) == len(v.path) for v in r.values)
170
+ assert all(v.instance_key[0] == (model.root.ct_id, 0) for v in r.values)
File without changes
File without changes
File without changes