cognite-neat 0.119.1__py3-none-any.whl → 0.119.3__py3-none-any.whl

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.

Potentially problematic release.


This version of cognite-neat might be problematic. Click here for more details.

Files changed (32) hide show
  1. cognite/neat/_constants.py +34 -70
  2. cognite/neat/_graph/extractors/__init__.py +0 -6
  3. cognite/neat/_graph/loaders/_rdf2dms.py +5 -5
  4. cognite/neat/_graph/queries/__init__.py +1 -1
  5. cognite/neat/_graph/queries/_base.py +2 -456
  6. cognite/neat/_graph/queries/_queries.py +16 -0
  7. cognite/neat/_graph/queries/_select.py +440 -0
  8. cognite/neat/_graph/queries/_update.py +37 -0
  9. cognite/neat/_issues/errors/_external.py +4 -2
  10. cognite/neat/_rules/exporters/_rules2excel.py +240 -107
  11. cognite/neat/_rules/importers/_yaml2rules.py +7 -1
  12. cognite/neat/_rules/models/_base_rules.py +16 -1
  13. cognite/neat/_rules/models/dms/_validation.py +11 -2
  14. cognite/neat/_rules/transformers/_converters.py +16 -6
  15. cognite/neat/_session/_drop.py +2 -2
  16. cognite/neat/_session/_explore.py +4 -4
  17. cognite/neat/_session/_prepare.py +5 -5
  18. cognite/neat/_session/_read.py +6 -0
  19. cognite/neat/_session/_set.py +3 -3
  20. cognite/neat/_session/_show.py +1 -1
  21. cognite/neat/_session/_template.py +21 -2
  22. cognite/neat/_state/README.md +23 -0
  23. cognite/neat/_store/_graph_store.py +5 -5
  24. cognite/neat/_version.py +1 -1
  25. {cognite_neat-0.119.1.dist-info → cognite_neat-0.119.3.dist-info}/METADATA +37 -2
  26. {cognite_neat-0.119.1.dist-info → cognite_neat-0.119.3.dist-info}/RECORD +29 -28
  27. cognite/neat/_graph/extractors/_dexpi.py +0 -234
  28. cognite/neat/_graph/extractors/_iodd.py +0 -403
  29. cognite/neat/_graph/transformers/_iodd.py +0 -30
  30. {cognite_neat-0.119.1.dist-info → cognite_neat-0.119.3.dist-info}/LICENSE +0 -0
  31. {cognite_neat-0.119.1.dist-info → cognite_neat-0.119.3.dist-info}/WHEEL +0 -0
  32. {cognite_neat-0.119.1.dist-info → cognite_neat-0.119.3.dist-info}/entry_points.txt +0 -0
@@ -511,6 +511,8 @@ class CSVReadAPI(BaseReadAPI):
511
511
  column_with_identifier = "UNIQUE_TAG_NAME"
512
512
  neat.read.csv("url_or_path_to_csv_file", type=type_described_in_table, primary_key=column_with_identifier)
513
513
  ```
514
+
515
+ !!! note "Method read.csv requires NEATEngine plug-in"
514
516
  """
515
517
 
516
518
  def __call__(self, io: Any, type: str, primary_key: str) -> None:
@@ -565,6 +567,8 @@ class XMLReadAPI(BaseReadAPI):
565
567
  neat.read.xml.dexpi("url_or_path_to_dexpi_file")
566
568
  ```
567
569
 
570
+ !!! note "Method read.xml.dexpi requires NEATEngine plug-in"
571
+
568
572
  !!! note "This method bundles several graph transformers which"
569
573
  - attach values of generic attributes to nodes
570
574
  - create associations between nodes
@@ -627,6 +631,8 @@ class XMLReadAPI(BaseReadAPI):
627
631
  neat.read.xml.aml("url_or_path_to_aml_file")
628
632
  ```
629
633
 
634
+ !!! note "Method read.xml.aml requires NEATEngine plug-in"
635
+
630
636
  !!! note "This method bundles several graph transformers which"
631
637
  - attach values of attributes to nodes
632
638
  - remove unused attributes
@@ -76,8 +76,8 @@ class SetInstances:
76
76
  neat.set.instances.replace_type("Asset", "assetCategory")
77
77
  ```
78
78
  """
79
- type_uri = self._state.instances.store.queries.type_uri(current_type)
80
- property_uri = self._state.instances.store.queries.property_uri(property_type)
79
+ type_uri = self._state.instances.store.queries.select.type_uri(current_type)
80
+ property_uri = self._state.instances.store.queries.select.property_uri(property_type)
81
81
 
82
82
  if not type_uri:
83
83
  raise NeatValueError(f"Type {current_type} does not exist in the graph.")
@@ -93,7 +93,7 @@ class SetInstances:
93
93
  f"{property_type} has multiple ids found in the graph: {humanize_collection(property_uri)}."
94
94
  )
95
95
 
96
- if not self._state.instances.store.queries.type_with_property(type_uri[0], property_uri[0]):
96
+ if not self._state.instances.store.queries.select.type_with_property(type_uri[0], property_uri[0]):
97
97
  raise NeatValueError(f"Property {property_type} is not defined for type {current_type}.")
98
98
 
99
99
  self._state.instances.store.transform(SetType(type_uri[0], property_uri[0], drop_property))
@@ -247,7 +247,7 @@ class ShowInstanceAPI(ShowBaseAPI):
247
247
 
248
248
  di_graph = nx.DiGraph()
249
249
 
250
- types = [type_ for type_, _ in self._state.instances.store.queries.summarize_instances()]
250
+ types = [type_ for type_, _ in self._state.instances.store.queries.select.summarize_instances()]
251
251
  hex_colored_types = _generate_hex_color_per_type(types)
252
252
 
253
253
  for ( # type: ignore
@@ -9,6 +9,7 @@ from cognite.neat._rules._shared import ReadRules
9
9
  from cognite.neat._rules.exporters import ExcelExporter
10
10
  from cognite.neat._rules.importers import ExcelImporter
11
11
  from cognite.neat._rules.models import InformationInputRules
12
+ from cognite.neat._rules.models._base_rules import RoleTypes
12
13
  from cognite.neat._rules.models.dms import DMSValidation
13
14
  from cognite.neat._rules.transformers import (
14
15
  AddCogniteProperties,
@@ -174,7 +175,21 @@ class TemplateAPI:
174
175
  self._state.last_reference = last_rules
175
176
  return issues
176
177
 
177
- def extension(self, io: Any, output: str | Path | None = None) -> IssueList:
178
+ def conceptual_model(self, io: Any) -> None:
179
+ """This method will create a template for a conceptual data modeling
180
+
181
+ Args:
182
+ io: file path to the Excel sheet
183
+
184
+ """
185
+ reader = NeatReader.create(io)
186
+ path = reader.materialize_path()
187
+
188
+ ExcelExporter().template(RoleTypes.information, path)
189
+
190
+ return None
191
+
192
+ def extension(self, io: Any, output: str | Path | None = None, dummy_property: str = "GUID") -> IssueList:
178
193
  """Creates a template for an extension of a Cognite model.
179
194
 
180
195
  The input is a spreadsheet of a conceptual model in which the concepts are defined
@@ -193,6 +208,10 @@ class TemplateAPI:
193
208
  io: The input spreadsheet.
194
209
  output: The output spreadsheet. If None, the output will be the same
195
210
  as the input with `_extension` added to the name.
211
+ dummy_property: The dummy property to use as placeholder for user-defined properties
212
+ for each user-defined concept, and to alleviate need for usage of filters in
213
+ physical data model. When converting a data model, it is recommended to have at least
214
+ one property for each concept. This ensures that you follow that recommendation.
196
215
  """
197
216
  ExperimentalFlags.extension.warn()
198
217
  reader = NeatReader.create(io)
@@ -214,7 +233,7 @@ class TemplateAPI:
214
233
  raise NeatSessionError(f"The input {reader.name} must contain an InformationInputRules object. ")
215
234
  if self._state.client is None:
216
235
  raise NeatSessionError("Client must be set in the session to run the extension.")
217
- modified = AddCogniteProperties(self._state.client).transform(read)
236
+ modified = AddCogniteProperties(self._state.client, dummy_property).transform(read)
218
237
  if modified.rules is not None:
219
238
  # If rules are None there will be issues that are already caught.
220
239
  info = modified.rules.as_verified_rules()
@@ -0,0 +1,23 @@
1
+ # NeatState
2
+
3
+ **IN DEVELOPMENT**
4
+ The neat state controls the `NeatGraphStore` and `NeatRuleStore`. It is implementing a state machine pattern
5
+ to ensure valid state transitions. The diagram below shows the state machine:
6
+
7
+ ```mermaid
8
+ stateDiagram-v2
9
+ state excel_importer <<fork>>
10
+ [*] --> EmptyState
11
+ EmptyState --> Instances: extractor
12
+ Instances --> Instances: graph transformer
13
+ EmptyState --> Conceptual: importer
14
+ EmptyState --> Physical: DMS importer/extractor
15
+ Instances --> Conceptual: infer/importer
16
+ Conceptual --> Physical: convert
17
+ Conceptual --> Conceptual: conceptual transformer/infer
18
+ Physical --> Physical: physical transformer/infer
19
+ EmptyState --> excel_importer: Excel/YAML importer
20
+ state excel_importer <<join>>
21
+ excel_importer --> Conceptual
22
+ excel_importer --> Physical
23
+ ```
@@ -243,10 +243,10 @@ class NeatGraphStore:
243
243
  ) -> Iterable[tuple[URIRef, dict[str | InstanceType, list[Any]]]]:
244
244
  named_graph = named_graph or self.default_named_graph
245
245
 
246
- instance_ids = self.queries.list_instances_ids(class_uri, named_graph=named_graph)
246
+ instance_ids = self.queries.select.list_instances_ids(class_uri, named_graph=named_graph)
247
247
 
248
248
  for instance_id in instance_ids:
249
- if res := self.queries.describe(
249
+ if res := self.queries.select.describe(
250
250
  instance_id=instance_id,
251
251
  instance_type=class_uri,
252
252
  property_renaming_config=property_renaming_config,
@@ -363,7 +363,7 @@ class NeatGraphStore:
363
363
  def summary(self) -> dict[URIRef, pd.DataFrame]:
364
364
  return {
365
365
  named_graph: pd.DataFrame(
366
- self.queries.summarize_instances(named_graph),
366
+ self.queries.select.summarize_instances(named_graph),
367
367
  columns=["Type", "Occurrence"],
368
368
  )
369
369
  for named_graph in self.named_graphs
@@ -371,7 +371,7 @@ class NeatGraphStore:
371
371
 
372
372
  @property
373
373
  def multi_type_instances(self) -> dict[URIRef, dict[str, list[str]]]:
374
- return {named_graph: self.queries.multi_type_instances(named_graph) for named_graph in self.named_graphs}
374
+ return {named_graph: self.queries.select.multi_type_instances(named_graph) for named_graph in self.named_graphs}
375
375
 
376
376
  def _repr_html_(self) -> str:
377
377
  provenance = self.provenance._repr_html_()
@@ -448,4 +448,4 @@ class NeatGraphStore:
448
448
  @property
449
449
  def empty(self) -> bool:
450
450
  """Cheap way to check if the graph store is empty."""
451
- return not self.queries.has_data()
451
+ return not self.queries.select.has_data()
cognite/neat/_version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.119.1"
1
+ __version__ = "0.119.3"
2
2
  __engine__ = "^2.0.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: cognite-neat
3
- Version: 0.119.1
3
+ Version: 0.119.3
4
4
  Summary: Knowledge graph transformation
5
5
  License: Apache-2.0
6
6
  Author: Nikola Vasiljevic
@@ -80,7 +80,42 @@ NEAT is a domain expert centric and developer friendly solution for rapid:
80
80
  NEAT is using open and globally recognized standards maintained by the [World Wide Web Consortium (W3C)](https://www.w3.org/RDF/).
81
81
  NEAT represents an essential tool for creation of standardized, machine-actionable, linked and semantic (meta)data.
82
82
 
83
- > NEAT is an acronym derived from k**N**owl**Ed**ge gr**A**ph **T**ransformer hallucinated by GenAI.
83
+ > NEAT is a funny acronym derived from k**N**owl**Ed**ge gr**A**ph **T**ransformer produced using [ACRONIMIFY](https://acronymify.com/NEAT/?q=knowledge+graph+transformer).
84
+
85
+
86
+ ## History
87
+
88
+ NEAT emerged from years of experience in semantic tooling and knowledge graph development. The foundation was laid in 2020 with [sheet2rdf](https://github.com/nikokaoja/sheet2rdf), an Excel-based tool that trained data stewards to build domain-specific knowledge graphs and supported CI/CD processes in the Dutch Covid program and european wind energy community.
89
+
90
+ By mid of 2022, sheet2rdf was used in several POCs in Cognite. As Cognite's Data Modeling Service (DMS) development progressed, the need for simplified data modeling experience led to demonstration of proto-NEAT, known as [sheet2fdm](https://github.com/cognitedata/sheet2fdm), an extension of sheet2rdf, enabling semantic data model definitions in OWL, SHACL, Python and GraphQL (see e.g., [wind energy data model](https://cognitedata.github.io/wind-energy-data-model/)) using a simplified version of sheet2rdf Excel template.
91
+
92
+ Presented in various forums in 2022, this approach paved the way for NEAT’s formal development in November 2022 to enable cost-saving and empowerment of Cognite customers to self-sufficiently maintain and onboard knowledge graphs to Cognite Data Fusion.
93
+
94
+
95
+ ## Authorship
96
+
97
+ ### Authors
98
+ The plot below shows the NEAT authorship from the start until present day.
99
+
100
+ ![NEAT authorship](./docs/artifacts/figs/authorship.png)
101
+
102
+ #### Current authors
103
+ - [Nikola Vasiljević](www.linkedin.com/in/thisisnikola)
104
+ - [Anders Albert](https://www.linkedin.com/in/anders-albert-00790483/)
105
+ - [Rogerio Júnior](https://www.linkedin.com/in/rogerio-saboia-j%C3%BAnior-087118a7/)
106
+
107
+ #### Former authors
108
+ - [Aleksandrs Livincovs](https://www.linkedin.com/in/aleksandrslivincovs/)
109
+ - [Julia Graham](https://www.linkedin.com/in/julia-graham-959a78a7/)
110
+
111
+ ### Contributors
112
+ We are very grateful for the contributions made by:
113
+
114
+
115
+ - [Marie Solvik Lepoutre](https://www.linkedin.com/in/mslepoutre/), who improved RDF triples projections to Cognite Data Fusion
116
+ - [Bård Henning Tvedt](https://www.linkedin.com/in/bhtvedt/), who implemented IMF importer
117
+ - [Kristina Tomičić](https://www.linkedin.com/in/kristina-tomicic-6bb443108/), who implemented Data Model and Instances visualization
118
+
84
119
 
85
120
  ## Installation
86
121
 
@@ -12,7 +12,7 @@ cognite/neat/_client/data_classes/neat_sequence.py,sha256=QZWSfWnwk6KlYJvsInco4W
12
12
  cognite/neat/_client/data_classes/schema.py,sha256=LFRj6-noSO23bxA2I-RdRXMHHc9AHptOXlSVHj1tVHg,25047
13
13
  cognite/neat/_client/testing.py,sha256=JnzLQegw2f6SATWRNDQ8Fui6qBYyz7vFgA5myjioohY,1175
14
14
  cognite/neat/_config.py,sha256=WT1BS8uADcFvGoUYOOfwFOVq_VBl472TisdoA3wLick,280
15
- cognite/neat/_constants.py,sha256=WcrInu37p56BP7gPRIvTVScu6D9Di4RQG-OiYg5NAek,8250
15
+ cognite/neat/_constants.py,sha256=WO_kl74JrUWfuapEah4vC8e3SaRm4XQaTcCcws_n8AU,7382
16
16
  cognite/neat/_graph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  cognite/neat/_graph/_shared.py,sha256=6avH6mtjxjHI7JDLkXwICxGvZwooGBr6APs1_w1To-A,940
18
18
  cognite/neat/_graph/_tracking/__init__.py,sha256=WOwsYieZtCW-iW15YkxUFrfKVVdLWdXHOGGStTwvE8A,91
@@ -22,7 +22,7 @@ cognite/neat/_graph/examples/Knowledge-Graph-Nordic44-dirty.xml,sha256=ujJip6XBs
22
22
  cognite/neat/_graph/examples/Knowledge-Graph-Nordic44.xml,sha256=U2Ns-M4LRjT1fBkhmRj63ur7jDzlRtHK9yOLf_npZ_g,1437996
23
23
  cognite/neat/_graph/examples/__init__.py,sha256=yAjHVY3b5jOjmbW-iLbhvu7BG014TpGi3K4igkDqW5I,368
24
24
  cognite/neat/_graph/examples/skos-capturing-sheet-wind-topics.xlsx,sha256=CV_yK5ZSbYS_ktfIZUPD8Sevs47zpswLXQUDFkGE4Gw,45798
25
- cognite/neat/_graph/extractors/__init__.py,sha256=v7hPDaRzI4koBTesbCgcxTb2W0Eoqydir7AVikT1_wk,2362
25
+ cognite/neat/_graph/extractors/__init__.py,sha256=OlPnJc55utagsvRcBOt1kIiP68yeK6e2I_TX5S-do1w,2210
26
26
  cognite/neat/_graph/extractors/_base.py,sha256=qQE-fl3f1hfqZg5KLF3zLHybP0u8ofRKf4jk7pEHnl4,1907
27
27
  cognite/neat/_graph/extractors/_classic_cdf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  cognite/neat/_graph/extractors/_classic_cdf/_assets.py,sha256=9WVFrAtUFAp_AAlb26Rtt2Axz9xsPQYetg7SKVrNCr4,1474
@@ -35,23 +35,23 @@ cognite/neat/_graph/extractors/_classic_cdf/_labels.py,sha256=7guTZdGFT1r7ItE2VN
35
35
  cognite/neat/_graph/extractors/_classic_cdf/_relationships.py,sha256=wQDR0DFmGqXFiRHxQtQWNTVk4zVKhZzjrCfCHihXvlU,5293
36
36
  cognite/neat/_graph/extractors/_classic_cdf/_sequences.py,sha256=pkCtjH7I1Z6naWDtItHGDirqKSdCTbX_DmXwz_23VLQ,11306
37
37
  cognite/neat/_graph/extractors/_classic_cdf/_timeseries.py,sha256=6CmmxWWG2IErfNKOPhsjQ5wSOTUZZMjulpaRbHj0Q-g,1560
38
- cognite/neat/_graph/extractors/_dexpi.py,sha256=pX1G8nISQ0CP60yXLzsjDPEZ8niuFcm2eb_GTd34ZRA,9489
39
38
  cognite/neat/_graph/extractors/_dict.py,sha256=CmXvYkzhRjWDPEaXZAb_YyWCwJDeNzdWBkmFyc2K90s,4321
40
39
  cognite/neat/_graph/extractors/_dms.py,sha256=fpr9jmJlnB9IS9sdDA7RV50e_RedF7AEVE25qLlgtp4,13810
41
40
  cognite/neat/_graph/extractors/_dms_graph.py,sha256=b_OpXsLZ1xCGEWAqsF5Qk6EknZzxDbcfaZ5NOZGaYrE,9441
42
- cognite/neat/_graph/extractors/_iodd.py,sha256=iZABOBqLIKyqIQ-RG6Z-FMTRcV5Ul8Ch1gAZygwN1Nk,18540
43
41
  cognite/neat/_graph/extractors/_mock_graph_generator.py,sha256=uAGyLMxaeWBlcSFQx0nArr1ChlDF8ZR3EcLM1akwAII,15443
44
42
  cognite/neat/_graph/extractors/_raw.py,sha256=xU3SmeLBCeqbs1WBdGCge8ZMnlOU6wgkKX5GRNXc0D0,2499
45
43
  cognite/neat/_graph/extractors/_rdf_file.py,sha256=8BWDk1oQPzo2w2HVmGah4rppIGqihX98SBbTB1SB_6o,2900
46
44
  cognite/neat/_graph/loaders/__init__.py,sha256=XS6vwmxgBzntg7UuG_ct_1hfhShVnFH5u0gGrdA8WfA,699
47
45
  cognite/neat/_graph/loaders/_base.py,sha256=Xq91-4GeQF2XN90-QgEFCU4aJabBXkeFeFXS2k4mWU4,4472
48
- cognite/neat/_graph/loaders/_rdf2dms.py,sha256=8XrrKfwQXIE7qRnU4Fz-7QlhUQfn-eXIP8AneJVm_rM,33873
49
- cognite/neat/_graph/queries/__init__.py,sha256=BgDd-037kvtWwAoGAy8eORVNMiZ5-E9sIV0txIpeaN4,50
50
- cognite/neat/_graph/queries/_base.py,sha256=xs_kCiqQFJfaPyYrKhpyPIAvyDOf19RgYcdg3WxjB6s,19344
46
+ cognite/neat/_graph/loaders/_rdf2dms.py,sha256=GD9EuZGs7ybFgTRF4Gm-VOvulny4slSsIOiRPvx7cm8,33908
47
+ cognite/neat/_graph/queries/__init__.py,sha256=W477LMyB4l6HIRbQhJoFgA_MUBwVCh2GBvtFeZu0AUA,53
48
+ cognite/neat/_graph/queries/_base.py,sha256=APevHeeWQDEoOQ0MlBtVlPf9hbZukVkI5fOvt5oPJCE,543
49
+ cognite/neat/_graph/queries/_queries.py,sha256=4BidSQXhdZYZ6_kyG7jMJ2iG0UtSrbQxfmwPM7V167A,466
50
+ cognite/neat/_graph/queries/_select.py,sha256=Bm16EnE-gg0fnzw0dxfhnUgeWRprVBTljdWyqG07QrI,18518
51
+ cognite/neat/_graph/queries/_update.py,sha256=RBY_QyRu1HNAErGxnzpZKiaajb739fC1pd_RHWeeSp0,1285
51
52
  cognite/neat/_graph/transformers/__init__.py,sha256=YzC1Z8BuT77NwagWX4Z-F9R9BARLSS7zM4bCdxBbqKg,1761
52
53
  cognite/neat/_graph/transformers/_base.py,sha256=8W_PSlsp-jQuKNb9z-lN0Khdq1tKsnWxXAMj2XnXcTc,4561
53
54
  cognite/neat/_graph/transformers/_classic_cdf.py,sha256=XjsMj5J0cmZyNzuGnGdlWec_drjaUNSgWwqIIa-YE7Y,25294
54
- cognite/neat/_graph/transformers/_iodd.py,sha256=oHBS-7wr8n80QfSliRaEzo6jpwRrpJczQg90T-KvD3o,882
55
55
  cognite/neat/_graph/transformers/_prune_graph.py,sha256=AJuDqpeaSzk7kY6PlO3JiqGmsfAkEq7s3ubZ5P2r1uI,12615
56
56
  cognite/neat/_graph/transformers/_rdfpath.py,sha256=9KCiq8UCeNWcV9VAmyV5gbqwmozUaHRJuMyzVmL3ji4,3156
57
57
  cognite/neat/_graph/transformers/_value_type.py,sha256=9Ihk-NMra91c_KOKonKUNgZXwylSrE7pDluzd31NHfM,15797
@@ -60,7 +60,7 @@ cognite/neat/_issues/_base.py,sha256=UDcx0s9_PqLD4z1NnOxI4wgsk2z6IiVCuJoG-V8aPyQ
60
60
  cognite/neat/_issues/_contextmanagers.py,sha256=lfASjmq0wHHCJQtgChvOMFZGp_rL513baHSRXbW0ugU,1577
61
61
  cognite/neat/_issues/_factory.py,sha256=-22ptgqVeqkaL7RqaIEexhXh3Z27dFvj7Q-9D0tu-4E,2811
62
62
  cognite/neat/_issues/errors/__init__.py,sha256=1MK8nHFrN5c2Uiq9r4PkjL8GnBY7TGhcqGNkgQzVmKs,2359
63
- cognite/neat/_issues/errors/_external.py,sha256=WISG6rA0GH7lD23hN7WDhDIfOh9QrsXUDSxMCkTcBj4,2210
63
+ cognite/neat/_issues/errors/_external.py,sha256=LIze-eY3gEWEA_UmCGmknoazhKa73-ZGaHDz3xsR9DM,2349
64
64
  cognite/neat/_issues/errors/_general.py,sha256=MnBm9V1S-Agr9kUfTMvviBB4_NkuOXamlgJCu2UojEM,849
65
65
  cognite/neat/_issues/errors/_properties.py,sha256=T_nquQeEQS3DQY--DQ13acxhGX_-gpUMjWGTBKCh6r8,2536
66
66
  cognite/neat/_issues/errors/_resources.py,sha256=qndhGGPdulWpU7G4PkFHsF4Bjflf3lehGV4A0l-BeNI,3966
@@ -85,7 +85,7 @@ cognite/neat/_rules/catalog/info-rules-imf.xlsx,sha256=vrE5g8vBtsGpwJqygxG3t9I3x
85
85
  cognite/neat/_rules/exporters/__init__.py,sha256=IYBa0DIYlx8cFItgYRw9W4FY_LmVEjuaqMz3JORZZX0,1204
86
86
  cognite/neat/_rules/exporters/_base.py,sha256=VkNMy8wsH-x4tAjS44cXgzzNH0CM2k_4RhkMwK50J7g,2284
87
87
  cognite/neat/_rules/exporters/_rules2dms.py,sha256=ddlRbEgKBDLiO-DBM4A5fGpiF5nDgVpzdT3YGchAHNM,19625
88
- cognite/neat/_rules/exporters/_rules2excel.py,sha256=rCxEyiCu9QQ05GPFVT0MQwTZaZNh5hXpGLUeMeZcYlg,18754
88
+ cognite/neat/_rules/exporters/_rules2excel.py,sha256=xh8oDkDAvnLMCYU2iuuDSnUw1G3erxV62KwPSnYs4ok,24264
89
89
  cognite/neat/_rules/exporters/_rules2instance_template.py,sha256=6hvuqb5cdO5IqbFKPaoZxIF-fG9gxo0c0pLWLJmKioA,5989
90
90
  cognite/neat/_rules/exporters/_rules2ontology.py,sha256=4Z8Qf8H3Ksz0xAtyj5A3oyIKFJNljfMKKqYLxtiQfKs,23057
91
91
  cognite/neat/_rules/exporters/_rules2yaml.py,sha256=ggaPR8FO8PwZk1_nhwb5wVHk_C4s6qh1RrlbPkNcbBo,3160
@@ -105,17 +105,17 @@ cognite/neat/_rules/importers/_rdf/_inference2rules.py,sha256=NSPFEe6ZLHbFTUGTpQ
105
105
  cognite/neat/_rules/importers/_rdf/_owl2rules.py,sha256=gTMe94DC9xe8NR9KNVHTMTshg_NzVuN1v8Lz95BUshI,3392
106
106
  cognite/neat/_rules/importers/_rdf/_shared.py,sha256=nLHEXOwEs9n-4t6kIGyPJIlFMGw4VKCpU8_NBC0XLr0,5876
107
107
  cognite/neat/_rules/importers/_spreadsheet2rules.py,sha256=wjB7RVevjvXL3gaL1SGEW411ie76ZrFbe9fngzO-ncQ,10977
108
- cognite/neat/_rules/importers/_yaml2rules.py,sha256=k2oDgz--mxFVeyqQG3uvyYcb0BkFHwKHdBYHVaxtAEc,3721
108
+ cognite/neat/_rules/importers/_yaml2rules.py,sha256=tqfHhLnlcu4iFrF5ol26lUquBoXHW95mfpmtjdTQx-Q,3896
109
109
  cognite/neat/_rules/models/__init__.py,sha256=tf6tyWiYO0eC2PHCcpy208dwOHjEi9hg0sEaQLcB3uA,1024
110
110
  cognite/neat/_rules/models/_base_input.py,sha256=ZcSheXMz72w907RAcuMMupTk35zApQ8lS43As7dFVgA,6661
111
- cognite/neat/_rules/models/_base_rules.py,sha256=5Z2iyyumJn0oR-1XZ2-O696UTSpsjQ-6cBAdW3dvnE0,14996
111
+ cognite/neat/_rules/models/_base_rules.py,sha256=ONwBYq7YVUL4R9KL4iLhgbC2ZhGkl6HZtC9Dgx3pI5A,15460
112
112
  cognite/neat/_rules/models/_types.py,sha256=OjEV-eDIYTzoMbuazWtNTZgWhthntknqMPS_E9ts22s,5430
113
113
  cognite/neat/_rules/models/data_types.py,sha256=OyiAiszU59wQssv05XHX8SihLkYmtfgmgmdVypJ7cwc,10095
114
114
  cognite/neat/_rules/models/dms/__init__.py,sha256=fRaUH0IwG0YwWd_DNKM6j-jHHFyiIVz4_8DPiS1KR0Y,695
115
115
  cognite/neat/_rules/models/dms/_exporter.py,sha256=2QiTLX4UF89jGd_YRz9EczDFprBphdO_oyaQWMBmSiM,28576
116
116
  cognite/neat/_rules/models/dms/_rules.py,sha256=QEKNw31nkTwzjyNEnWYUdDFUdJvDJ5_14iQzWsFizxs,23537
117
117
  cognite/neat/_rules/models/dms/_rules_input.py,sha256=HfC4Z9UfDSBlKhoBdk57NqyPic1pVNymZYYEeV8Ow-k,16410
118
- cognite/neat/_rules/models/dms/_validation.py,sha256=Uh6DaCywdpTljy3trmVGaHrRI1wqZSAHRBpSi1hemsU,32165
118
+ cognite/neat/_rules/models/dms/_validation.py,sha256=qImtBMWCKC_C2zrfeTxQrjhTTL1MJt8cFqKMYQwNiEs,32871
119
119
  cognite/neat/_rules/models/entities/__init__.py,sha256=Hlucp3LyV6ncLl79aqRTbSy2qgiGzoyN8x54D_zaJCY,1469
120
120
  cognite/neat/_rules/models/entities/_constants.py,sha256=GXRzVfArwxF3C67VCkzy0JWTZRkRJUYXBQaaecrqcWc,351
121
121
  cognite/neat/_rules/models/entities/_loaders.py,sha256=OQDbz5ANMQ_7ZcdMIBdTR94BoCpWrBA2KBH3fCW0JQo,2728
@@ -132,24 +132,24 @@ cognite/neat/_rules/models/mapping/_classic2core.py,sha256=AhLWoXk4PlBVA6SgBtY9d
132
132
  cognite/neat/_rules/models/mapping/_classic2core.yaml,sha256=ei-nuivNWVW9HmvzDBKIPF6ZdgaMq64XHw_rKm0CMxg,22584
133
133
  cognite/neat/_rules/transformers/__init__.py,sha256=icjtZq6Hc122fEj9AGEcDB8QuF2JL6dWe4utne73naQ,1617
134
134
  cognite/neat/_rules/transformers/_base.py,sha256=9LnjKbYIf9238PQXedkTZsMXAyEND6glUD187iEaHfc,2783
135
- cognite/neat/_rules/transformers/_converters.py,sha256=pel46GyIBVKeYb_GoflHc0uq5pVPayc2ePrIBwNb0fk,104666
135
+ cognite/neat/_rules/transformers/_converters.py,sha256=-veWPRxJJGYBtExSZGlTm3obeG_AE6lSKyaaWNjlHGY,105167
136
136
  cognite/neat/_rules/transformers/_mapping.py,sha256=QVd96vMF9sGailN5hB22KGdmOU8GzFwZ7IPMD0aVCYY,18237
137
137
  cognite/neat/_rules/transformers/_verification.py,sha256=coZjoLqdfS8yrPP62T_xEKrDZc9ETbPWrLuwEVBSLZQ,4370
138
138
  cognite/neat/_session/__init__.py,sha256=fxQ5URVlUnmEGYyB8Baw7IDq-uYacqkigbc4b-Pr9Fw,58
139
139
  cognite/neat/_session/_base.py,sha256=WrX4g-pf4GnnYpusSoNkmKyjH_IfB9ZWfXw34THMqy0,12344
140
140
  cognite/neat/_session/_collector.py,sha256=ipYlwBBgjF53adjDW_D1U39mLEviB3UOVSGgeqHkEuc,4228
141
- cognite/neat/_session/_drop.py,sha256=gOkDAnddASpFxYxkPjlTyhkpNfnmDEj94GRI8tnHFR0,4167
142
- cognite/neat/_session/_explore.py,sha256=hrL0ASLtEXLlZn0dgDsKNySO10qEMBT8cE8mti2lOws,1561
141
+ cognite/neat/_session/_drop.py,sha256=yXCZVL2PdVHffu5x630etuYEa3hBNhsZqhlryZVUkOQ,4181
142
+ cognite/neat/_session/_explore.py,sha256=KX0_iNRospxhcgC5O2grvbVbRqaDszeZ3AG5SYqjzcM,1589
143
143
  cognite/neat/_session/_fix.py,sha256=wYXIIHKmWTNmOLr9RvDSkBJllKoomP2mCnMdB9x2ojw,898
144
144
  cognite/neat/_session/_inspect.py,sha256=qoBAfCQnzC40ef91gxJmhonWo1Kr_VEjBb2KhbCOO_s,10084
145
145
  cognite/neat/_session/_mapping.py,sha256=AkQwmqYH-0EgqoXHqCFwJY92hNSGzfojOelhVFlqH4c,2655
146
- cognite/neat/_session/_prepare.py,sha256=0lWENdavp3ut-5WUqwTaeKTbM3fZ1C2PTfFoFuoLx_8,12657
147
- cognite/neat/_session/_read.py,sha256=Md0k5746t7N7R0pacz-c0HfQlov28jkofv9Vh2ifKWQ,29995
148
- cognite/neat/_session/_set.py,sha256=dCZ5zEmNAw8tiqOGT7-EigSXOIGlfVP2ldA7nmC8LJ8,4451
149
- cognite/neat/_session/_show.py,sha256=2lnkud996ouwf6-aKGvU0cU0ttfMeQ3vcb__g_7Yko4,10539
146
+ cognite/neat/_session/_prepare.py,sha256=js4R1ssvWZRUDTDas228HVPGLuRABLHrliTTDNCFLOg,12692
147
+ cognite/neat/_session/_read.py,sha256=rjQqFU9VzyPSdNmaKaMo-E5em6LZHa3iDAjdvRLX9rI,30193
148
+ cognite/neat/_session/_set.py,sha256=a16aKeip7Y3qAhU0AzmJlMaEXe5ifDBXFYq4tXZNanI,4472
149
+ cognite/neat/_session/_show.py,sha256=9c0eUGtvN6pIX594P6hVofxDwO7lDE-N3A52htJjcv0,10546
150
150
  cognite/neat/_session/_state.py,sha256=6b0BSXm7m5-OcsF_O0A_7ec9tt8oPqH8zHImyEhVQQA,6313
151
151
  cognite/neat/_session/_subset.py,sha256=4RFGC8apNLnRSKcoDh95ksXA7zLy8vGLxEV5U0H3Hoc,2687
152
- cognite/neat/_session/_template.py,sha256=oR11KXyEGUGKjKthsehK7-3xI2n3_3ZAsRQ66wHXs8o,10458
152
+ cognite/neat/_session/_template.py,sha256=UEKvSAZJg08Vfb0TTUq7K7X3gcjP_pJEazyugPXFTq0,11318
153
153
  cognite/neat/_session/_to.py,sha256=S7209afgOlLWbsKOA0Z4lAwT57gzLmSDaUWpUaWjJJY,19118
154
154
  cognite/neat/_session/_wizard.py,sha256=9idlzhZy54h2Iwupe9iXKX3RDb5jJQuBZFEouni50L0,1476
155
155
  cognite/neat/_session/engine/__init__.py,sha256=D3MxUorEs6-NtgoICqtZ8PISQrjrr4dvca6n48bu_bI,120
@@ -158,8 +158,9 @@ cognite/neat/_session/engine/_interface.py,sha256=3W-cYr493c_mW3P5O6MKN1xEQg3cA7
158
158
  cognite/neat/_session/engine/_load.py,sha256=LcoYVthQyCzLWKzRE_75_nElS-n_eMWSPAgNJBnh5dA,5193
159
159
  cognite/neat/_session/exceptions.py,sha256=kRNKnhvOEPnF8TdcKP-lWbHvAwn-MOtGxKZdUpnJ_Q8,3239
160
160
  cognite/neat/_shared.py,sha256=Ov59SWYboRRsncB_5V1ZC_BAoACfNLjo80vqE5Ru6wo,2325
161
+ cognite/neat/_state/README.md,sha256=G6my1CtTDNyIR7G3H1Is_92dHsH_dXjmJmYQb9xh_dk,898
161
162
  cognite/neat/_store/__init__.py,sha256=RrvuZrMdezqun5dOrwHWSk26kampZcvqiHBqSFumkEE,130
162
- cognite/neat/_store/_graph_store.py,sha256=ruIn1mSnJoj597M-ZgXDH2bzy3uF11NLomel0OkuR4Q,17142
163
+ cognite/neat/_store/_graph_store.py,sha256=jEAGNhMhzOdnBESDfWt6yF6qyJZ61gWCrEQDsMfMhdI,17177
163
164
  cognite/neat/_store/_provenance.py,sha256=V2rBK3L4wneoiTBvir3AbmrcPsl6XVmefovkkjHYhNE,7301
164
165
  cognite/neat/_store/_rules_store.py,sha256=dh2GfDRRtQ2F00I5hdqYveULQKx2_kt630Mac_ZFF2k,18562
165
166
  cognite/neat/_store/exceptions.py,sha256=XznWE3X8iBpnTKcC2SpInwesQVpfinGBzQT6xX9GANg,1614
@@ -177,10 +178,10 @@ cognite/neat/_utils/text.py,sha256=9T0mzcNn6J9X8DriNntLN5ThCXOWbU1BYareukbLT7A,8
177
178
  cognite/neat/_utils/time_.py,sha256=7ayUm0OWZm1JDmy32E4ip8WRr2o0GLwrHwJA8sJ43Z4,357
178
179
  cognite/neat/_utils/upload.py,sha256=xWtM6mFuD2QYQHaZ7zCAuGptbEpPIxcH-raWQu93-Ug,5845
179
180
  cognite/neat/_utils/xml_.py,sha256=FQkq84u35MUsnKcL6nTMJ9ajtG9D5i1u4VBnhGqP2DQ,1710
180
- cognite/neat/_version.py,sha256=PmwQMm2xpu25bGSQZn5n0s1kkl4epWM68bMMNxSbKEs,46
181
+ cognite/neat/_version.py,sha256=d27As8_j5YQxh9_ZmNwoy0v_fx27xmBROZeiFkXTUNE,46
181
182
  cognite/neat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
182
- cognite_neat-0.119.1.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
183
- cognite_neat-0.119.1.dist-info/METADATA,sha256=uTVeDmlTuWj0Br_wMxYzmBrT8aO2ZINIypUKtBqmc78,5421
184
- cognite_neat-0.119.1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
185
- cognite_neat-0.119.1.dist-info/entry_points.txt,sha256=SsQlnl8SNMSSjE3acBI835JYFtsIinLSbVmHmMEXv6E,51
186
- cognite_neat-0.119.1.dist-info/RECORD,,
183
+ cognite_neat-0.119.3.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
184
+ cognite_neat-0.119.3.dist-info/METADATA,sha256=L1WOkKQhfbfheXIZ3UaLUI2JPAsF8qbx-dUfGxpbh84,7646
185
+ cognite_neat-0.119.3.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
186
+ cognite_neat-0.119.3.dist-info/entry_points.txt,sha256=SsQlnl8SNMSSjE3acBI835JYFtsIinLSbVmHmMEXv6E,51
187
+ cognite_neat-0.119.3.dist-info/RECORD,,
@@ -1,234 +0,0 @@
1
- import xml.etree.ElementTree as ET
2
- from collections import defaultdict
3
- from collections.abc import Iterable
4
- from pathlib import Path
5
- from xml.etree.ElementTree import Element
6
-
7
- from rdflib import RDF, RDFS, XSD, Literal, Namespace, URIRef
8
- from typing_extensions import Self
9
-
10
- from cognite.neat._constants import DEFAULT_NAMESPACE
11
- from cognite.neat._graph.extractors._base import BaseExtractor
12
- from cognite.neat._shared import Triple
13
- from cognite.neat._utils.rdf_ import as_neat_compliant_uri
14
- from cognite.neat._utils.xml_ import get_children, iterate_tree
15
-
16
- DEXPI = Namespace("http://sandbox.dexpi.org/rdl/")
17
- QUDT = Namespace("https://qudt.org/vocab/unit/")
18
-
19
-
20
- class DexpiExtractor(BaseExtractor):
21
- """
22
- DEXPI-XML extractor of RDF triples
23
-
24
- Args:
25
- root: XML root element of DEXPI file.
26
- namespace: Optional custom namespace to use for extracted triples that define data
27
- model instances. Defaults to DEFAULT_NAMESPACE.
28
- """
29
-
30
- def __init__(
31
- self,
32
- root: Element,
33
- namespace: Namespace | None = None,
34
- ):
35
- self.root = root
36
- self.namespace = namespace or DEFAULT_NAMESPACE
37
-
38
- @classmethod
39
- def from_file(cls, filepath: str | Path, namespace: Namespace | None = None) -> Self:
40
- return cls(ET.parse(filepath).getroot(), namespace)
41
-
42
- @classmethod
43
- def from_url(cls, url: str, namespace: Namespace | None = None) -> Self:
44
- from io import BytesIO
45
-
46
- import requests
47
-
48
- response = requests.get(url)
49
- response.raise_for_status()
50
- return cls(ET.parse(BytesIO(response.content)).getroot(), namespace)
51
-
52
- def extract(self) -> Iterable[Triple]:
53
- """Extracts RDF triples from DEXPI XML file."""
54
-
55
- for element in iterate_tree(self.root):
56
- yield from self._element2triples(element, self.namespace)
57
-
58
- @classmethod
59
- def _element2triples(cls, element: Element, namespace: Namespace) -> list[Triple]:
60
- """Converts an element to triples."""
61
- triples: list[Triple] = []
62
-
63
- if (
64
- "ComponentClass" in element.attrib
65
- and element.attrib["ComponentClass"] != "Label"
66
- and "ID" in element.attrib
67
- ):
68
- id_ = namespace[element.attrib["ID"]]
69
-
70
- if node_triples := cls._element2node_triples(id_, element):
71
- triples.extend(node_triples)
72
-
73
- if edge_triples := cls._element2edge_triples(id_, element, namespace):
74
- triples.extend(edge_triples)
75
-
76
- return triples
77
-
78
- @classmethod
79
- def _element2edge_triples(cls, id_: URIRef, element: Element, namespace: Namespace) -> list[Triple]:
80
- triples: list[Triple] = []
81
-
82
- # connection triples
83
- if connections := get_children(element, "Connection"):
84
- for connection in connections:
85
- if "FromID" in connection.attrib and "ToID" in connection.attrib:
86
- triples.append(
87
- (
88
- namespace[connection.attrib["FromID"]],
89
- DEXPI.connection,
90
- namespace[connection.attrib["ToID"]],
91
- )
92
- )
93
-
94
- # association triples
95
- if associations := get_children(element, "Association"):
96
- for association in associations:
97
- if "Type" in association.attrib and "ItemID" in association.attrib:
98
- association_type = cls._to_uri_friendly_association_type(association)
99
-
100
- triples.append(
101
- (
102
- id_,
103
- DEXPI[f"association/{association_type}"],
104
- namespace[association.attrib["ItemID"]],
105
- )
106
- )
107
-
108
- # children-parent triples
109
- for child in element:
110
- if "ID" in child.attrib and child.tag != "Label":
111
- camel_case_property = child.tag[0].lower() + child.tag[1:]
112
- triples.append(
113
- (
114
- id_,
115
- DEXPI[f"children/{camel_case_property}"],
116
- namespace[child.attrib["ID"]],
117
- )
118
- )
119
-
120
- return triples
121
-
122
- @classmethod
123
- def _to_uri_friendly_association_type(cls, association: Element) -> str:
124
- association_type = "".join(
125
- [word.capitalize() if i != 0 else word for i, word in enumerate(association.attrib["Type"].split(" "))]
126
- )
127
-
128
- return association_type
129
-
130
- @classmethod
131
- def _element2node_triples(cls, id_: URIRef, element: Element) -> list[Triple]:
132
- """Converts an XML element to triples."""
133
- triples: list[Triple] = []
134
-
135
- # setting these to None this is the order of getting the type
136
- component_class: str | None = None
137
- component_name: str | None = None
138
- tag: str | None = None
139
-
140
- # adding tag triple if exists
141
- if tag := element.tag:
142
- triples.append((id_, DEXPI.tag, Literal(str(tag))))
143
-
144
- # adding attributes triples
145
- if attributes := element.attrib:
146
- if component_class := attributes.get("ComponentClass", None):
147
- triples.append((id_, DEXPI.ComponentClass, Literal(component_class)))
148
- if component_name := attributes.get("ComponentName", None):
149
- triples.append((id_, DEXPI.ComponentName, Literal(component_name)))
150
- if component_class_uri := attributes.get("ComponentClassURI", None):
151
- triples.append((id_, DEXPI.ComponentClassURI, URIRef(component_class_uri)))
152
-
153
- triples.append(
154
- (
155
- id_,
156
- RDF.type,
157
- as_neat_compliant_uri(DEFAULT_NAMESPACE[component_class or component_name or tag or "Unknown"]),
158
- )
159
- )
160
-
161
- # add label triple
162
- if label := cls._get_element_label(element):
163
- triples.append((id_, RDFS.label, Literal(label)))
164
-
165
- # add generic attributes triples
166
- if generic_attributes := cls._get_element_generic_attributes(element):
167
- for attribute, value_definitions in generic_attributes.items():
168
- predicate = as_neat_compliant_uri(attribute)
169
- for value_definition in value_definitions:
170
- if literal := cls._value_definition2literal(value_definition):
171
- triples.append((id_, predicate, literal))
172
-
173
- return triples
174
-
175
- @classmethod
176
- def _value_definition2literal(cls, definition: dict, make_unit_datatype: bool = False) -> Literal | None:
177
- if "Value" not in definition or "Format" not in definition:
178
- return None
179
-
180
- if "Units" in definition and "Value" in definition:
181
- if make_unit_datatype and "UnitsURI" in definition:
182
- return Literal(definition["Value"], datatype=URIRef(definition["UnitsURI"]))
183
-
184
- else:
185
- return Literal(definition["Value"], datatype=XSD.float)
186
-
187
- # case: when language is present we create add language tag to the literal
188
- elif "Language" in definition and "Value" in definition:
189
- return Literal(definition["Value"], lang=definition["Language"])
190
-
191
- # case: when ValueURI is present we use it instead of Value
192
- # this would be candidate for ENUMs in CDF
193
- elif "ValueURI" in definition:
194
- return Literal(definition["ValueURI"], datatype=XSD[definition["Format"]])
195
-
196
- # case: when Format is not string we make sure to add the datatype
197
- elif definition["Format"].lower() != "string":
198
- return Literal(definition["Value"], datatype=XSD[definition["Format"]])
199
-
200
- # case: when Format is string we add the literal without datatype (easier to read triples, less noise)
201
- else:
202
- return Literal(definition["Value"])
203
-
204
- @classmethod
205
- def _get_element_label(cls, element: Element) -> str | None:
206
- if children := get_children(element, "Label", no_children=1):
207
- if grandchildren := get_children(children[0], "Text", no_children=1):
208
- if "String" in grandchildren[0].attrib:
209
- return grandchildren[0].attrib["String"]
210
-
211
- # extension for schema version 3.3, where text is used to "label" without a <label> parent
212
- elif children := get_children(element, "Text", no_children=1):
213
- if "String" in children[0].attrib:
214
- return children[0].attrib["String"]
215
-
216
- return None
217
-
218
- @classmethod
219
- def _get_element_generic_attributes(cls, element: Element) -> dict:
220
- # TODO: This requires more work as there are multiple groupings of GenericAttributes
221
-
222
- attributes = defaultdict(list)
223
- if children := get_children(element, "GenericAttributes", no_children=1):
224
- if grandchildren := get_children(children[0], "GenericAttribute"):
225
- for generic_attribute in grandchildren:
226
- # extension for schema version 3.3, where "AttributeURI" is not included
227
- if name := generic_attribute.attrib.get("Name", None):
228
- attribute_uri = as_neat_compliant_uri(DEFAULT_NAMESPACE[name])
229
- if attribute_uri not in attributes:
230
- attributes[attribute_uri] = [generic_attribute.attrib]
231
- else:
232
- attributes[attribute_uri].append(generic_attribute.attrib)
233
-
234
- return attributes