onto-lang 0.1.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 (74) hide show
  1. onto_lang-0.1.0/PKG-INFO +94 -0
  2. onto_lang-0.1.0/README.md +87 -0
  3. onto_lang-0.1.0/pyproject.toml +31 -0
  4. onto_lang-0.1.0/setup.cfg +4 -0
  5. onto_lang-0.1.0/src/py/onto/__init__.py +215 -0
  6. onto_lang-0.1.0/src/py/onto/__main__.py +12 -0
  7. onto_lang-0.1.0/src/py/onto/binding/__init__.py +19 -0
  8. onto_lang-0.1.0/src/py/onto/binding/coverage.py +174 -0
  9. onto_lang-0.1.0/src/py/onto/binding/formatter.py +122 -0
  10. onto_lang-0.1.0/src/py/onto/binding/model.py +99 -0
  11. onto_lang-0.1.0/src/py/onto/binding/operations.py +364 -0
  12. onto_lang-0.1.0/src/py/onto/binding/parser.py +291 -0
  13. onto_lang-0.1.0/src/py/onto/cli/__init__.py +164 -0
  14. onto_lang-0.1.0/src/py/onto/cli/commands.py +436 -0
  15. onto_lang-0.1.0/src/py/onto/cli/ui.py +168 -0
  16. onto_lang-0.1.0/src/py/onto/cli/usage.py +382 -0
  17. onto_lang-0.1.0/src/py/onto/cli/workspace.py +555 -0
  18. onto_lang-0.1.0/src/py/onto/coverage.py +350 -0
  19. onto_lang-0.1.0/src/py/onto/expressions.py +264 -0
  20. onto_lang-0.1.0/src/py/onto/formats/__init__.py +12 -0
  21. onto_lang-0.1.0/src/py/onto/formats/formatter.py +25 -0
  22. onto_lang-0.1.0/src/py/onto/formats/model.py +53 -0
  23. onto_lang-0.1.0/src/py/onto/formats/operations.py +40 -0
  24. onto_lang-0.1.0/src/py/onto/formats/parser.py +80 -0
  25. onto_lang-0.1.0/src/py/onto/functions/__init__.py +19 -0
  26. onto_lang-0.1.0/src/py/onto/functions/formatter.py +100 -0
  27. onto_lang-0.1.0/src/py/onto/functions/model.py +110 -0
  28. onto_lang-0.1.0/src/py/onto/functions/operations.py +177 -0
  29. onto_lang-0.1.0/src/py/onto/functions/parser.py +221 -0
  30. onto_lang-0.1.0/src/py/onto/functions/runtime.py +320 -0
  31. onto_lang-0.1.0/src/py/onto/integrations/__init__.py +5 -0
  32. onto_lang-0.1.0/src/py/onto/integrations/hexa/__init__.py +44 -0
  33. onto_lang-0.1.0/src/py/onto/integrations/hexa/compiler.py +1014 -0
  34. onto_lang-0.1.0/src/py/onto/integrations/hexa/model.py +164 -0
  35. onto_lang-0.1.0/src/py/onto/integrations/hexa/operations.py +244 -0
  36. onto_lang-0.1.0/src/py/onto/integrations/hexa/runs.py +331 -0
  37. onto_lang-0.1.0/src/py/onto/integrations/hexa/stages.py +435 -0
  38. onto_lang-0.1.0/src/py/onto/integrations/hexa/unfold.py +545 -0
  39. onto_lang-0.1.0/src/py/onto/integrations/hexa/vocabulary.py +743 -0
  40. onto_lang-0.1.0/src/py/onto/integrations/hexa/writing.py +464 -0
  41. onto_lang-0.1.0/src/py/onto/inventory/__init__.py +15 -0
  42. onto_lang-0.1.0/src/py/onto/inventory/formatter.py +21 -0
  43. onto_lang-0.1.0/src/py/onto/inventory/model.py +43 -0
  44. onto_lang-0.1.0/src/py/onto/inventory/operations.py +166 -0
  45. onto_lang-0.1.0/src/py/onto/inventory/parser.py +60 -0
  46. onto_lang-0.1.0/src/py/onto/main.py +14 -0
  47. onto_lang-0.1.0/src/py/onto/mapping/__init__.py +20 -0
  48. onto_lang-0.1.0/src/py/onto/mapping/coverage.py +131 -0
  49. onto_lang-0.1.0/src/py/onto/mapping/formatter.py +133 -0
  50. onto_lang-0.1.0/src/py/onto/mapping/model.py +101 -0
  51. onto_lang-0.1.0/src/py/onto/mapping/operations.py +375 -0
  52. onto_lang-0.1.0/src/py/onto/mapping/parser.py +247 -0
  53. onto_lang-0.1.0/src/py/onto/mapping/reversal.py +234 -0
  54. onto_lang-0.1.0/src/py/onto/observations/__init__.py +84 -0
  55. onto_lang-0.1.0/src/py/onto/observations/formatter.py +322 -0
  56. onto_lang-0.1.0/src/py/onto/observations/model.py +237 -0
  57. onto_lang-0.1.0/src/py/onto/observations/operations.py +374 -0
  58. onto_lang-0.1.0/src/py/onto/observations/parser.py +717 -0
  59. onto_lang-0.1.0/src/py/onto/ontology/__init__.py +142 -0
  60. onto_lang-0.1.0/src/py/onto/ontology/formatter.py +400 -0
  61. onto_lang-0.1.0/src/py/onto/ontology/model.py +482 -0
  62. onto_lang-0.1.0/src/py/onto/ontology/operations.py +424 -0
  63. onto_lang-0.1.0/src/py/onto/ontology/parser.py +640 -0
  64. onto_lang-0.1.0/src/py/onto/referential/__init__.py +17 -0
  65. onto_lang-0.1.0/src/py/onto/referential/formatter.py +70 -0
  66. onto_lang-0.1.0/src/py/onto/referential/model.py +46 -0
  67. onto_lang-0.1.0/src/py/onto/referential/operations.py +205 -0
  68. onto_lang-0.1.0/src/py/onto/referential/parser.py +142 -0
  69. onto_lang-0.1.0/src/py/onto/text.py +279 -0
  70. onto_lang-0.1.0/src/py/onto_lang.egg-info/PKG-INFO +94 -0
  71. onto_lang-0.1.0/src/py/onto_lang.egg-info/SOURCES.txt +72 -0
  72. onto_lang-0.1.0/src/py/onto_lang.egg-info/dependency_links.txt +1 -0
  73. onto_lang-0.1.0/src/py/onto_lang.egg-info/entry_points.txt +2 -0
  74. onto_lang-0.1.0/src/py/onto_lang.egg-info/top_level.txt +1 -0
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: onto-lang
3
+ Version: 0.1.0
4
+ Summary: Compact text formats for ontologies, observations and mappings
5
+ Requires-Python: >=3.14
6
+ Description-Content-Type: text/markdown
7
+
8
+ ```
9
+ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄
10
+ ▄▀░▓░ ▀▄ █ ▓░ ▀▄ █ ░▓░ ▓ ▄▀░▓░ ▀▄
11
+ ▐▌ ▒ ▐▌ ▓ ▒ ▐▌ █ ▒ ▒ ▐▌ ▒ ▐▌
12
+ █ ░ ░ █ ▒ ░ ▓ █▄▄▄ ░ ▄▄▄▄░ █ ░ ░ █
13
+ █ ▓ █ ░ ▒ █ █ █ ▓ █
14
+ ▓ █ █ ░ ░ ░ ▓ █ ▓ █ █
15
+ ░ █ ▓ ░ ▒ ░▒ █ █ ░ █ ▓
16
+ ▒░ ▓ ▒ ▒░ ▓ ░▒░ ▒░ █ ▒░ ▓ ▒
17
+ ▐▓▒░ ▀ ▐▌ ▓▒░ ░█ ░▒▓▒ ░▓▒░ █ ▐▓▒░ ▀ ▐▌
18
+ ▀▓▒░ ▄▀ █▓▒░ ▒█ ░▒▓█▓ ██▓▒░ █ ▀▓▒░ ▄▀
19
+ ▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀
20
+ ```
21
+
22
+ *onto* is a format (JSON & text) to represent conceptual models (ontologies)
23
+ and instance data (observations), as well the mapping between systems and
24
+ the records they describe.
25
+
26
+ *onto*'s goal is to:
27
+ - Provide a format to describe data that is compact and expressive
28
+ - Can we read and edited by humans and machines/agents efficiently
29
+ - Facilitate data management and systems integration
30
+
31
+ Onto is implemented in Python.
32
+
33
+ Onto offers the following formats, each with a text and a JSON form:
34
+
35
+ - `.onto` for ontologies ([spec-01](docs/specs/spec-01-ontology.md))
36
+ - `.obs` for observations ([spec-02](docs/specs/spec-02-observations.md))
37
+ - `.bind`, `.map` and `.fn` for bindings, mappings and function libraries
38
+ ([spec-04](docs/specs/spec-04-mapping.md))
39
+ - `.ref` for identity referentials ([spec-05](docs/specs/spec-05-identities.md))
40
+
41
+ ## Get started
42
+
43
+ *onto* needs Python 3.14 or later and has no runtime dependencies. The
44
+ distribution is named `onto-lang`; the command and Python import are both
45
+ `onto`.
46
+
47
+ From the repository root, run the CLI without installing:
48
+
49
+ ```sh
50
+ ./bin/onto --help
51
+ ```
52
+
53
+ Or install the checkout into your Python environment:
54
+
55
+ ```sh
56
+ pip install .
57
+ onto --help
58
+ python -m onto --help
59
+ ```
60
+
61
+ The package has not been published to PyPI yet. Once released, you can
62
+ install it with `pip install onto-lang`.
63
+
64
+ For publishing, see the [release instructions](docs/releasing.md).
65
+
66
+ ## In a nutshell
67
+
68
+ Files name each other by namespace (`@use Finance as F`), so `onto` indexes
69
+ the files of your project by their header and finds what each one needs:
70
+
71
+ ```
72
+ $ onto ls ontology mapping
73
+ mapping Exchange->Finance finance/exchange-finance.map
74
+ ontology Exchange 2026-11-10 finance/exchange.onto
75
+ ontology Finance 2026-11-10 finance/finance.onto
76
+
77
+ $ onto check finance/exchange-finance.map # loads Exchange, Finance, Markets, MarketIds
78
+ no problems in 1 file (5 read)
79
+
80
+ $ onto fmt --check # lists files whose layout would change
81
+
82
+ $ onto coverage finance/exchange-finance.map
83
+ source target status note
84
+ Dividend.Amount Dividend.Amount direct
85
+ Dividend.Currency condition
86
+ Instrument.Class Instrument.AssetClass transformed
87
+
88
+ $ onto reverse finance/exchange-finance.map > finance-exchange.map
89
+ finance/exchange-finance.map:19: warning map.lossy: ... 'AssetClass' is not
90
+ reversed: @map 'AssetClassFromCode' is not exact and one-to-one
91
+ ```
92
+
93
+ `onto help` lists every command; [spec-07](docs/specs/spec-07-cli.md)
94
+ describes them.
@@ -0,0 +1,87 @@
1
+ ```
2
+ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄
3
+ ▄▀░▓░ ▀▄ █ ▓░ ▀▄ █ ░▓░ ▓ ▄▀░▓░ ▀▄
4
+ ▐▌ ▒ ▐▌ ▓ ▒ ▐▌ █ ▒ ▒ ▐▌ ▒ ▐▌
5
+ █ ░ ░ █ ▒ ░ ▓ █▄▄▄ ░ ▄▄▄▄░ █ ░ ░ █
6
+ █ ▓ █ ░ ▒ █ █ █ ▓ █
7
+ ▓ █ █ ░ ░ ░ ▓ █ ▓ █ █
8
+ ░ █ ▓ ░ ▒ ░▒ █ █ ░ █ ▓
9
+ ▒░ ▓ ▒ ▒░ ▓ ░▒░ ▒░ █ ▒░ ▓ ▒
10
+ ▐▓▒░ ▀ ▐▌ ▓▒░ ░█ ░▒▓▒ ░▓▒░ █ ▐▓▒░ ▀ ▐▌
11
+ ▀▓▒░ ▄▀ █▓▒░ ▒█ ░▒▓█▓ ██▓▒░ █ ▀▓▒░ ▄▀
12
+ ▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀
13
+ ```
14
+
15
+ *onto* is a format (JSON & text) to represent conceptual models (ontologies)
16
+ and instance data (observations), as well the mapping between systems and
17
+ the records they describe.
18
+
19
+ *onto*'s goal is to:
20
+ - Provide a format to describe data that is compact and expressive
21
+ - Can we read and edited by humans and machines/agents efficiently
22
+ - Facilitate data management and systems integration
23
+
24
+ Onto is implemented in Python.
25
+
26
+ Onto offers the following formats, each with a text and a JSON form:
27
+
28
+ - `.onto` for ontologies ([spec-01](docs/specs/spec-01-ontology.md))
29
+ - `.obs` for observations ([spec-02](docs/specs/spec-02-observations.md))
30
+ - `.bind`, `.map` and `.fn` for bindings, mappings and function libraries
31
+ ([spec-04](docs/specs/spec-04-mapping.md))
32
+ - `.ref` for identity referentials ([spec-05](docs/specs/spec-05-identities.md))
33
+
34
+ ## Get started
35
+
36
+ *onto* needs Python 3.14 or later and has no runtime dependencies. The
37
+ distribution is named `onto-lang`; the command and Python import are both
38
+ `onto`.
39
+
40
+ From the repository root, run the CLI without installing:
41
+
42
+ ```sh
43
+ ./bin/onto --help
44
+ ```
45
+
46
+ Or install the checkout into your Python environment:
47
+
48
+ ```sh
49
+ pip install .
50
+ onto --help
51
+ python -m onto --help
52
+ ```
53
+
54
+ The package has not been published to PyPI yet. Once released, you can
55
+ install it with `pip install onto-lang`.
56
+
57
+ For publishing, see the [release instructions](docs/releasing.md).
58
+
59
+ ## In a nutshell
60
+
61
+ Files name each other by namespace (`@use Finance as F`), so `onto` indexes
62
+ the files of your project by their header and finds what each one needs:
63
+
64
+ ```
65
+ $ onto ls ontology mapping
66
+ mapping Exchange->Finance finance/exchange-finance.map
67
+ ontology Exchange 2026-11-10 finance/exchange.onto
68
+ ontology Finance 2026-11-10 finance/finance.onto
69
+
70
+ $ onto check finance/exchange-finance.map # loads Exchange, Finance, Markets, MarketIds
71
+ no problems in 1 file (5 read)
72
+
73
+ $ onto fmt --check # lists files whose layout would change
74
+
75
+ $ onto coverage finance/exchange-finance.map
76
+ source target status note
77
+ Dividend.Amount Dividend.Amount direct
78
+ Dividend.Currency condition
79
+ Instrument.Class Instrument.AssetClass transformed
80
+
81
+ $ onto reverse finance/exchange-finance.map > finance-exchange.map
82
+ finance/exchange-finance.map:19: warning map.lossy: ... 'AssetClass' is not
83
+ reversed: @map 'AssetClassFromCode' is not exact and one-to-one
84
+ ```
85
+
86
+ `onto help` lists every command; [spec-07](docs/specs/spec-07-cli.md)
87
+ describes them.
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["setuptools>=75"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "onto-lang"
7
+ version = "0.1.0"
8
+ description = "Compact text formats for ontologies, observations and mappings"
9
+ readme = "README.md"
10
+ requires-python = ">=3.14"
11
+ dependencies = []
12
+
13
+ [project.scripts]
14
+ onto = "onto.main:main"
15
+
16
+ [dependency-groups]
17
+ dev = ["pytest>=8", "jsonschema>=4.18"]
18
+
19
+ [tool.setuptools]
20
+ package-dir = { "" = "src/py" }
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["src/py"]
24
+ include = ["onto*"]
25
+
26
+ [tool.pytest.ini_options]
27
+ python_files = ["T*.py"]
28
+ addopts = "--import-mode=importlib"
29
+ testpaths = ["tests"]
30
+ # NOTE: The optional hexa integration is tested against the sibling checkout when present.
31
+ pythonpath = ["src/py", "../hexa/src/py"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,215 @@
1
+ """Module: onto — ontology/observation formats, mapping declarations and identity policies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .text import Diagnostic, FormatError, Source
6
+ from .ontology import (
7
+ Operator as OntologyOperator,
8
+ Model,
9
+ Ontology,
10
+ formatOntology,
11
+ loadOntology,
12
+ normaliseOntology,
13
+ ontologyFromJson,
14
+ ontologyToJson,
15
+ parseOntology,
16
+ printOntology,
17
+ validateOntology,
18
+ )
19
+ from .observations import (
20
+ Operator as ObservationsOperator,
21
+ Gap,
22
+ Observations,
23
+ ObservationsDocument,
24
+ formatObservations,
25
+ loadObservations,
26
+ observationsFromJson,
27
+ observationsToJson,
28
+ parseObservations,
29
+ printObservationsLog,
30
+ printObservationsState,
31
+ reconcileObservations,
32
+ resolveObservations,
33
+ )
34
+ from .coverage import CoverageItem, CoverageReport, CoverageRow, observationsCoverage, printCoverageTable
35
+ from .expressions import Expression, expressionFromJson, parseExpression, printExpression
36
+ from .functions import (
37
+ Operator as FunctionsOperator,
38
+ Function,
39
+ FunctionDeclaration,
40
+ FunctionEvaluator,
41
+ FunctionExample,
42
+ FunctionLibrary,
43
+ FunctionResult,
44
+ FunctionsDocument,
45
+ SignatureType,
46
+ formatFunctions,
47
+ functionsFromJson,
48
+ functionsToJson,
49
+ loadFunctions,
50
+ parseFunctions,
51
+ printFunctions,
52
+ resolveFunctions,
53
+ )
54
+ from .binding import (
55
+ Operator as BindingOperator,
56
+ Binding,
57
+ BindingDocument,
58
+ BindingExclusion,
59
+ BindingLine,
60
+ BindingScope,
61
+ bindingFromJson,
62
+ bindingToJson,
63
+ formatBinding,
64
+ loadBinding,
65
+ parseBinding,
66
+ printBinding,
67
+ resolveBinding,
68
+ bindingCoverage,
69
+ )
70
+ from .mapping import (
71
+ Operator as MappingOperator,
72
+ Mapping,
73
+ MappingDocument,
74
+ MappingExclusion,
75
+ MappingLine,
76
+ MappingScope,
77
+ formatMapping,
78
+ loadMapping,
79
+ mappingFromJson,
80
+ mappingToJson,
81
+ parseMapping,
82
+ printMapping,
83
+ resolveMapping,
84
+ LossyLine,
85
+ mappingCoverage,
86
+ mappingReverse,
87
+ )
88
+ from .inventory import Inventory, InventoryField, defaultBinding, documentFields, inventoryFromDocuments, parseInventory, printInventory
89
+ from .formats import FormatChange, Formats, FormatsEntry, formatsUpdate, parseFormats, printFormats
90
+ from .referential import (
91
+ Operator as ReferentialOperator,
92
+ Policy as ReferentialPolicy,
93
+ Referential,
94
+ formatReferential,
95
+ loadReferential,
96
+ parseReferential,
97
+ printReferential,
98
+ referentialFromJson,
99
+ referentialImplicit,
100
+ referentialPolicy,
101
+ referentialToJson,
102
+ validateReferential,
103
+ )
104
+
105
+ __all__ = [
106
+ "Binding",
107
+ "BindingDocument",
108
+ "BindingExclusion",
109
+ "BindingLine",
110
+ "BindingScope",
111
+ "BindingOperator",
112
+ "CoverageItem",
113
+ "CoverageReport",
114
+ "CoverageRow",
115
+ "FormatChange",
116
+ "Formats",
117
+ "FormatsEntry",
118
+ "Inventory",
119
+ "InventoryField",
120
+ "LossyLine",
121
+ "Diagnostic",
122
+ "Expression",
123
+ "FormatError",
124
+ "Function",
125
+ "FunctionDeclaration",
126
+ "FunctionEvaluator",
127
+ "FunctionExample",
128
+ "FunctionLibrary",
129
+ "FunctionResult",
130
+ "FunctionsDocument",
131
+ "FunctionsOperator",
132
+ "Gap",
133
+ "Model",
134
+ "Mapping",
135
+ "MappingDocument",
136
+ "MappingExclusion",
137
+ "MappingLine",
138
+ "MappingScope",
139
+ "MappingOperator",
140
+ "Ontology",
141
+ "OntologyOperator",
142
+ "Observations",
143
+ "ObservationsDocument",
144
+ "ObservationsOperator",
145
+ "Referential",
146
+ "ReferentialOperator",
147
+ "ReferentialPolicy",
148
+ "Source",
149
+ "SignatureType",
150
+ "expressionFromJson",
151
+ "bindingFromJson",
152
+ "bindingToJson",
153
+ "formatBinding",
154
+ "formatOntology",
155
+ "formatObservations",
156
+ "formatFunctions",
157
+ "formatMapping",
158
+ "formatReferential",
159
+ "functionsFromJson",
160
+ "functionsToJson",
161
+ "loadOntology",
162
+ "loadObservations",
163
+ "loadFunctions",
164
+ "loadBinding",
165
+ "loadMapping",
166
+ "loadReferential",
167
+ "mappingFromJson",
168
+ "mappingToJson",
169
+ "normaliseOntology",
170
+ "ontologyFromJson",
171
+ "ontologyToJson",
172
+ "observationsFromJson",
173
+ "observationsCoverage",
174
+ "observationsToJson",
175
+ "parseOntology",
176
+ "parseExpression",
177
+ "parseFunctions",
178
+ "parseBinding",
179
+ "parseMapping",
180
+ "parseReferential",
181
+ "parseObservations",
182
+ "printOntology",
183
+ "printExpression",
184
+ "printFunctions",
185
+ "printBinding",
186
+ "printMapping",
187
+ "printReferential",
188
+ "printObservationsLog",
189
+ "printObservationsState",
190
+ "reconcileObservations",
191
+ "referentialFromJson",
192
+ "referentialImplicit",
193
+ "referentialPolicy",
194
+ "referentialToJson",
195
+ "resolveObservations",
196
+ "resolveFunctions",
197
+ "resolveBinding",
198
+ "resolveMapping",
199
+ "validateOntology",
200
+ "validateReferential",
201
+ "bindingCoverage",
202
+ "defaultBinding",
203
+ "documentFields",
204
+ "formatsUpdate",
205
+ "inventoryFromDocuments",
206
+ "mappingCoverage",
207
+ "mappingReverse",
208
+ "parseFormats",
209
+ "parseInventory",
210
+ "printCoverageTable",
211
+ "printFormats",
212
+ "printInventory",
213
+ ]
214
+
215
+ # EOF
@@ -0,0 +1,12 @@
1
+ """Module: __main__ — run the `onto` command line with `python -m onto`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ from .main import main
8
+
9
+ if __name__ == "__main__":
10
+ sys.exit(main())
11
+
12
+ # EOF
@@ -0,0 +1,19 @@
1
+ """Module: binding — spec-04 binding declarations and interchange, without execution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .model import Binding, BindingDocument, BindingExclusion, BindingLine, BindingScope, Condition
6
+ from .parser import parseBinding
7
+ from .formatter import bindingToJson, printBinding
8
+ from .operations import Operator, bindingFromJson, formatBinding, loadBinding, resolveBinding
9
+ from .coverage import bindingCoverage
10
+
11
+ BindingOperator = Operator
12
+
13
+ __all__ = [
14
+ "Binding", "BindingDocument", "BindingExclusion", "BindingLine", "BindingScope", "Condition", "Operator", "BindingOperator",
15
+ "parseBinding", "resolveBinding", "loadBinding", "formatBinding", "printBinding", "bindingToJson", "bindingFromJson",
16
+ "bindingCoverage",
17
+ ]
18
+
19
+ # EOF
@@ -0,0 +1,174 @@
1
+ """Module: binding.coverage — static coverage of a binding: ontology side and document side (spec-04)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from ..coverage import CoverageReport, CoverageRow, Elements, coverageRows
8
+ from ..expressions import Expression, printExpression
9
+ from ..inventory.model import CONTAINER_TYPES, Inventory
10
+ from ..ontology.model import Model, Ontology
11
+ from .model import Binding, BindingScope
12
+
13
+ # -----------------------------------------------------------------------------
14
+ #
15
+ # DEFINITIONS
16
+ #
17
+ # -----------------------------------------------------------------------------
18
+
19
+
20
+ type Steps = list[Any]
21
+
22
+ EACH = {"each": True}
23
+
24
+
25
+ # -----------------------------------------------------------------------------
26
+ #
27
+ # UTILITIES
28
+ #
29
+ # -----------------------------------------------------------------------------
30
+
31
+
32
+ def documentPaths(expr: Expression | None) -> list[Steps]:
33
+ """Document paths an expression reads; the type argument of `ref` is not one."""
34
+ if expr is None:
35
+ return []
36
+ if "path" in expr:
37
+ return [expr["path"]]
38
+ if "call" in expr:
39
+ args = expr["args"][1:] if expr["call"] == "ref" else expr["args"]
40
+ return [path for arg in args for path in documentPaths(arg)]
41
+ if "coalesce" in expr:
42
+ return [path for item in expr["coalesce"] for path in documentPaths(item)]
43
+ return []
44
+
45
+
46
+ def absolute(scope: Steps | None, steps: Steps) -> Steps:
47
+ """Steps of a path read from a record at `scope`, from the document root, indexes as `[*]`.
48
+
49
+ `^` climbs to the enclosing list element; `$` restarts at the root. A JSON root
50
+ object is implicit (`asOf`); a root list keeps its `$[*]`.
51
+ """
52
+ first = steps[0] if steps else None
53
+ if isinstance(first, dict) and "root" in first:
54
+ base, steps = [], steps[1:]
55
+ elif isinstance(first, dict) and "up" in first:
56
+ eaches = [index for index, step in enumerate(scope or []) if step == EACH]
57
+ count = first["up"]
58
+ base = list(scope[:eaches[-1 - count] + 1]) if len(eaches) > count else []
59
+ steps = steps[1:]
60
+ else:
61
+ base = list(scope or [])
62
+ result = base + [EACH if isinstance(step, dict) and "index" in step else step for step in steps]
63
+ if result and isinstance(result[0], dict) and "root" in result[0] and (len(result) < 2 or result[1] != EACH):
64
+ result = result[1:]
65
+ if not result:
66
+ return []
67
+ if result[0] == EACH:
68
+ result = [{"root": True}] + result
69
+ return result
70
+
71
+
72
+ def pathText(steps: Steps) -> str:
73
+ return printExpression({"path": steps}, binding=True) if steps else "$"
74
+
75
+
76
+ # -----------------------------------------------------------------------------
77
+ #
78
+ # OPERATIONS
79
+ #
80
+ # -----------------------------------------------------------------------------
81
+
82
+
83
+ class BindingCoverage:
84
+ """Collect the fields each scope reads and the elements its lines assign."""
85
+
86
+ def __init__(self, ontology: Ontology, binding: Binding):
87
+ self.binding = binding
88
+ self.elements = Elements(Model(ontology), binding.ontology)
89
+ self.reads: dict[str, list[tuple[str | None, str, str | None]]] = {}
90
+ self.assigned: set[str] = set()
91
+
92
+ def record(self, scope: Steps | None, paths: list[Steps], target: str | None, status: str, note: str | None = None) -> None:
93
+ for steps in paths:
94
+ path = absolute(scope, steps)
95
+ if path:
96
+ self.reads.setdefault(pathText(path), []).append((target, status, note))
97
+
98
+ def enclosing(self, scope: BindingScope, up: int) -> BindingScope | None:
99
+ """The scope line whose records enclose this scope's, `up` lists above."""
100
+ steps = absolute(None, scope.path["path"])
101
+ eaches = [index for index, step in enumerate(steps) if step == EACH]
102
+ if len(eaches) <= up:
103
+ return None
104
+ prefix = steps[:eaches[-1 - up] + 1]
105
+ return next((item for item in self.binding.scopes if absolute(None, item.path["path"]) == prefix), None)
106
+
107
+ def scope(self, scope: BindingScope) -> None:
108
+ steps = absolute(None, scope.path["path"])
109
+ typeName = self.elements.display(scope.type)
110
+ self.record(None, [scope.path["path"]], typeName, "direct", scope.doc)
111
+ conditions = documentPaths(scope.id) + documentPaths(scope.valid) + documentPaths(scope.ctx)
112
+ conditions += [scope.deleted["path"]] if scope.deleted else []
113
+ self.record(steps, conditions, None, "condition")
114
+ for line in scope.lines:
115
+ key = self.elements.assigned(scope.type, line.target, line.facet, line.verb)
116
+ if key:
117
+ self.assigned.add(key)
118
+ member = f"[{line.facet}]" if line.facet else f".{line.target or line.verb}"
119
+ expr = line.expr
120
+ if expr is not None and "path" in expr and len(expr["path"]) == 1 and isinstance(expr["path"][0], dict) and "up" in expr["path"][0]:
121
+ # `^` reads the enclosing record's key.
122
+ parent = self.enclosing(scope, expr["path"][0]["up"])
123
+ if parent is not None:
124
+ self.record(absolute(None, parent.path["path"]), documentPaths(parent.id), None, "condition")
125
+ continue
126
+ status = "direct" if expr is not None and "path" in expr else "transformed"
127
+ self.record(steps, documentPaths(expr), typeName + member, status, line.doc)
128
+ if line.when:
129
+ self.record(steps, [line.when["path"]], None, "condition")
130
+
131
+ def report(self, inventory: Inventory | None) -> CoverageReport:
132
+ if self.binding.at is not None:
133
+ self.record(None, documentPaths(self.binding.at), None, "condition")
134
+ for scope in self.binding.scopes:
135
+ self.scope(scope)
136
+ keys = [key for decl in self.elements.instanceTypes() for key in self.elements.declared(decl.path)]
137
+ keys += [f"{decl.path}.{decl.arrow.verb}" for decl in self.elements.model.ontology.decls if decl.kind == "relation" and decl.arrow and f"{decl.participants[0].type}.{decl.arrow.verb}" not in keys]
138
+ unassigned = sorted(self.elements.display(key) for key in dict.fromkeys(keys)
139
+ if key not in self.assigned and not any(key.startswith(item + ".") for item in self.assigned))
140
+ if inventory is None:
141
+ return CoverageReport([], unassigned, [])
142
+ types = inventory.types()
143
+ excluded: dict[str, tuple[str, str | None]] = {}
144
+ for exclusion in self.binding.exclusions:
145
+ prefix = pathText(absolute(None, exclusion.path["path"]))
146
+ for path in types:
147
+ if path == prefix or path.startswith(prefix + ".") or path.startswith(prefix + "["):
148
+ excluded.setdefault(path, (exclusion.reason, exclusion.doc))
149
+ # NOTE: Containers (objects, lists) are reported only when read or excluded themselves; their fields carry the rest.
150
+ fields = [path for path, kind in types.items() if kind not in CONTAINER_TYPES or path in self.reads or path in excluded]
151
+ rows: list[CoverageRow] = coverageRows(fields, self.reads, excluded)
152
+ return CoverageReport(rows, unassigned, sorted(path for path in self.reads if path not in types and path != "$"))
153
+
154
+
155
+ # -----------------------------------------------------------------------------
156
+ #
157
+ # HIGH-LEVEL API
158
+ #
159
+ # -----------------------------------------------------------------------------
160
+
161
+
162
+ def bindingCoverage(ontology: Ontology, binding: Binding, inventory: Inventory | None = None) -> CoverageReport:
163
+ """Both sides of a binding's coverage.
164
+
165
+ `unassigned` is the ontology side: elements no line assigns, needing no data. With an
166
+ `inventory`, `rows` give each document field's status and `uninventoried` the paths
167
+ the binding reads that the inventory does not list.
168
+ """
169
+ return BindingCoverage(ontology, binding).report(inventory)
170
+
171
+
172
+ __all__ = ["bindingCoverage", "absolute", "documentPaths"]
173
+
174
+ # EOF