morphir-python 0.0.1__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.
Files changed (99) hide show
  1. morphir/__init__.py +14 -0
  2. morphir/ir/__init__.py +110 -0
  3. morphir/ir/access_controlled.py +15 -0
  4. morphir/ir/decorations.py +19 -0
  5. morphir/ir/diagnostics.py +105 -0
  6. morphir/ir/distribution.py +21 -0
  7. morphir/ir/document.py +37 -0
  8. morphir/ir/documented.py +11 -0
  9. morphir/ir/format_version.py +483 -0
  10. morphir/ir/fqname.py +9 -0
  11. morphir/ir/json.py +11 -0
  12. morphir/ir/literal.py +25 -0
  13. morphir/ir/meta.py +11 -0
  14. morphir/ir/migrate.py +1267 -0
  15. morphir/ir/module.py +17 -0
  16. morphir/ir/name.py +27 -0
  17. morphir/ir/package.py +15 -0
  18. morphir/ir/path.py +25 -0
  19. morphir/ir/qname.py +9 -0
  20. morphir/ir/ref.py +17 -0
  21. morphir/ir/type.py +37 -0
  22. morphir/ir/type_constraints.py +33 -0
  23. morphir/ir/type_def.py +19 -0
  24. morphir/ir/type_spec.py +25 -0
  25. morphir/ir/v3/__init__.py +64 -0
  26. morphir/ir/v3/_legacy_value.py +201 -0
  27. morphir/ir/v3/access_controlled.py +31 -0
  28. morphir/ir/v3/codec.py +1275 -0
  29. morphir/ir/v3/codec_v1.py +491 -0
  30. morphir/ir/v3/codec_v2.py +187 -0
  31. morphir/ir/v3/distribution.py +42 -0
  32. morphir/ir/v3/documented.py +13 -0
  33. morphir/ir/v3/fqname.py +30 -0
  34. morphir/ir/v3/literal.py +46 -0
  35. morphir/ir/v3/module.py +32 -0
  36. morphir/ir/v3/name.py +42 -0
  37. morphir/ir/v3/package.py +23 -0
  38. morphir/ir/v3/path.py +29 -0
  39. morphir/ir/v3/qname.py +16 -0
  40. morphir/ir/v3/type.py +126 -0
  41. morphir/ir/v3/value.py +235 -0
  42. morphir/ir/v4/__init__.py +160 -0
  43. morphir/ir/v4/access_controlled.py +17 -0
  44. morphir/ir/v4/annotation.py +75 -0
  45. morphir/ir/v4/codec.py +2161 -0
  46. morphir/ir/v4/decorations.py +88 -0
  47. morphir/ir/v4/distribution.py +56 -0
  48. morphir/ir/v4/document.py +69 -0
  49. morphir/ir/v4/documented.py +10 -0
  50. morphir/ir/v4/fqname.py +69 -0
  51. morphir/ir/v4/incompleteness.py +78 -0
  52. morphir/ir/v4/json.py +103 -0
  53. morphir/ir/v4/literal.py +51 -0
  54. morphir/ir/v4/meta.py +27 -0
  55. morphir/ir/v4/module.py +30 -0
  56. morphir/ir/v4/name.py +69 -0
  57. morphir/ir/v4/package.py +19 -0
  58. morphir/ir/v4/path.py +50 -0
  59. morphir/ir/v4/qname.py +34 -0
  60. morphir/ir/v4/ref.py +35 -0
  61. morphir/ir/v4/type.py +140 -0
  62. morphir/ir/v4/type_constraints.py +91 -0
  63. morphir/ir/v4/type_def.py +38 -0
  64. morphir/ir/v4/type_spec.py +64 -0
  65. morphir/ir/v4/value.py +480 -0
  66. morphir/ir/value.py +81 -0
  67. morphir/py.typed +0 -0
  68. morphir/sdk/__init__.py +110 -0
  69. morphir/sdk/_compare.py +187 -0
  70. morphir/sdk/aggregate.py +421 -0
  71. morphir/sdk/basics.py +504 -0
  72. morphir/sdk/char.py +110 -0
  73. morphir/sdk/decimal.py +311 -0
  74. morphir/sdk/dict.py +336 -0
  75. morphir/sdk/instant.py +118 -0
  76. morphir/sdk/int.py +86 -0
  77. morphir/sdk/key.py +522 -0
  78. morphir/sdk/list.py +356 -0
  79. morphir/sdk/local_date.py +436 -0
  80. morphir/sdk/local_time.py +267 -0
  81. morphir/sdk/maybe.py +135 -0
  82. morphir/sdk/number.py +237 -0
  83. morphir/sdk/regex.py +287 -0
  84. morphir/sdk/result.py +162 -0
  85. morphir/sdk/result_list.py +169 -0
  86. morphir/sdk/rule.py +103 -0
  87. morphir/sdk/set.py +214 -0
  88. morphir/sdk/stateful_app.py +49 -0
  89. morphir/sdk/string.py +427 -0
  90. morphir/sdk/tuple.py +43 -0
  91. morphir/sdk/uuid.py +209 -0
  92. morphir_python-0.0.1.dist-info/METADATA +269 -0
  93. morphir_python-0.0.1.dist-info/RECORD +99 -0
  94. morphir_python-0.0.1.dist-info/WHEEL +4 -0
  95. morphir_python-0.0.1.dist-info/entry_points.txt +2 -0
  96. morphir_python-0.0.1.dist-info/licenses/LICENSE +201 -0
  97. morphir_tools/__init__.py +13 -0
  98. morphir_tools/cli.py +223 -0
  99. morphir_tools/py.typed +0 -0
morphir_tools/cli.py ADDED
@@ -0,0 +1,223 @@
1
+ """The ``morphir-python`` command line.
2
+
3
+ Every IR operation goes through the `morphir` library: the shared format-version
4
+ reader, the v3 (3.0.x and 3.1.x) and draft v4 codecs, and `morphir.ir.migrate`.
5
+ The CLI adds file handling and reporting only.
6
+
7
+ Exit codes: 0 for success (including success with warnings), 1 when the library
8
+ reports a diagnostic, 2 for a usage error.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import tempfile
15
+ from enum import Enum
16
+ from pathlib import Path
17
+ from typing import Annotated
18
+
19
+ import typer
20
+
21
+ from morphir.ir import format_version as fv
22
+ from morphir.ir.diagnostics import Diagnostic, DiagnosticError
23
+ from morphir.ir.migrate import Err, Ok, v3_to_v4, v4_to_v3
24
+ from morphir.ir.migrate import drop_input_attributes as drop_inputs
25
+ from morphir.ir.v3 import codec as v3_codec
26
+ from morphir.ir.v3 import codec_v1, codec_v2
27
+ from morphir.ir.v3 import distribution as v3_dist
28
+ from morphir.ir.v4 import codec as v4_codec
29
+ from morphir.ir.v4 import distribution as v4_dist
30
+ from morphir.ir.v4.name import Name as Name4
31
+ from morphir.ir.v4.path import Path as Path4
32
+
33
+ app = typer.Typer(
34
+ name="morphir-python",
35
+ help="Command-line tools for Morphir IR.",
36
+ no_args_is_help=True,
37
+ )
38
+ ir_app = typer.Typer(
39
+ help="Inspect and convert versioned morphir-ir.json files "
40
+ "(format 3.0.x and 3.1.x, and draft 4.0.x; legacy formats 1 and 2 are read "
41
+ "and upgraded).",
42
+ no_args_is_help=True,
43
+ )
44
+ app.add_typer(ir_app, name="ir")
45
+
46
+
47
+ class Target(str, Enum):
48
+ """The IR version to convert to."""
49
+
50
+ V3 = "v3"
51
+ V4 = "v4"
52
+
53
+
54
+ def _report(diagnostics: tuple[Diagnostic, ...], *, warning: bool = False) -> None:
55
+ prefix = "warning" if warning else "error"
56
+ for diagnostic in diagnostics:
57
+ typer.echo(f"{prefix}: {diagnostic.render()}", err=True)
58
+
59
+
60
+ def _read(path: Path) -> str:
61
+ try:
62
+ return path.read_text(encoding="utf-8")
63
+ except OSError as error:
64
+ typer.echo(f"error: cannot read {path}: {error.strerror}", err=True)
65
+ raise typer.Exit(1) from None
66
+
67
+
68
+ def _write_atomically(target: Path, text: str) -> None:
69
+ directory = target.parent if str(target.parent) else Path()
70
+ fd, temp_name = tempfile.mkstemp(
71
+ prefix=f".{target.name}.", suffix=".tmp", dir=directory
72
+ )
73
+ try:
74
+ with os.fdopen(fd, "w", encoding="utf-8") as handle:
75
+ handle.write(text)
76
+ Path(temp_name).replace(target)
77
+ except BaseException:
78
+ Path(temp_name).unlink(missing_ok=True)
79
+ raise
80
+
81
+
82
+ def _kind(dist: v4_dist.Distribution | v3_dist.Distribution) -> str:
83
+ match dist:
84
+ case v3_dist.Library():
85
+ return "Library"
86
+ case v3_dist.Specs():
87
+ return "Specs"
88
+ case v4_dist.LibraryDistribution():
89
+ return "Library"
90
+ case v4_dist.SpecsDistribution():
91
+ return "Specs"
92
+ case v4_dist.ApplicationDistribution():
93
+ return "Application"
94
+
95
+
96
+ def _read_v3_family(
97
+ text: str, version: fv.FormatVersion | fv.LegacyFormat
98
+ ) -> v3_dist.Distribution:
99
+ """Decode a format 1, 2, or 3.x document into the v3 model.
100
+
101
+ Legacy formats 1 and 2 always give a format 3.0.0 Library.
102
+ """
103
+ match version:
104
+ case fv.LegacyFormat.V1:
105
+ return codec_v1.decode_versioned_distribution(text)
106
+ case fv.LegacyFormat.V2:
107
+ return codec_v2.decode_versioned_distribution(text)
108
+ case _:
109
+ return v3_codec.decode_versioned_distribution(text)
110
+
111
+
112
+ def _is_v4(version: fv.FormatVersion | fv.LegacyFormat) -> bool:
113
+ return isinstance(version, fv.FormatVersion) and version.major == 4
114
+
115
+
116
+ @ir_app.command()
117
+ def inspect(
118
+ file: Annotated[Path, typer.Argument(help="A versioned morphir-ir.json file.")],
119
+ ) -> None:
120
+ """Report the format version, distribution kind, and package name."""
121
+ text = _read(file)
122
+ try:
123
+ version, _ = fv.read_document_with_legacy(text)
124
+ if _is_v4(version):
125
+ dist = v4_codec.decode_versioned_distribution(text)
126
+ kind = _kind(dist)
127
+ package = v4_codec.encode_path(dist.package.name)
128
+ else:
129
+ classic = _read_v3_family(text, version)
130
+ kind = _kind(classic)
131
+ package = v4_codec.encode_path(
132
+ Path4(tuple(Name4(tuple(n)) for n in classic.package_name))
133
+ )
134
+ except DiagnosticError as error:
135
+ _report(error.diagnostics)
136
+ raise typer.Exit(1) from None
137
+ legacy = isinstance(version, fv.LegacyFormat)
138
+ typer.echo(f"format: {version}{' (legacy)' if legacy else ''}")
139
+ typer.echo(f"kind: {kind}")
140
+ typer.echo(f"package: {package}")
141
+ if _is_v4(version):
142
+ typer.echo(f"note: {v4_codec.DRAFT_NOTICE}")
143
+ if legacy:
144
+ typer.echo(
145
+ f"note: legacy format {version} is read-only; "
146
+ "ir convert upgrades it to format 3.0.0 or draft v4"
147
+ )
148
+
149
+
150
+ @ir_app.command()
151
+ def convert(
152
+ file: Annotated[Path, typer.Argument(help="A versioned morphir-ir.json file.")],
153
+ to: Annotated[Target, typer.Option("--to", help="The IR version to write.")],
154
+ output: Annotated[
155
+ Path, typer.Option("--output", "-o", help="Where to write the result.")
156
+ ],
157
+ package_version: Annotated[
158
+ str | None,
159
+ typer.Option(help="Package release version to record when upgrading to v4."),
160
+ ] = None,
161
+ drop_source_locations: Annotated[
162
+ bool,
163
+ typer.Option(help="Drop value source locations when downgrading to v3."),
164
+ ] = False,
165
+ drop_input_attributes: Annotated[
166
+ bool,
167
+ typer.Option(
168
+ help="Keep only the declared type of each value-definition input when "
169
+ "writing v4, which spells an input as a bare type."
170
+ ),
171
+ ] = False,
172
+ ) -> None:
173
+ """Convert between format 3 (3.0.x and 3.1.x) and draft IR v4.
174
+
175
+ A v3 Library is written as format 3 and a v3 Specs as format 3.1.0, the lowest
176
+ version that holds each. Legacy formats 1 and 2 are read as a format 3.0.0
177
+ Library and never written.
178
+
179
+ The output is written to a temporary file and renamed only on success, so a
180
+ failed conversion leaves no output file.
181
+ """
182
+ text = _read(file)
183
+ try:
184
+ version, _ = fv.read_document_with_legacy(text)
185
+ if to is Target.V4:
186
+ upgraded: Ok[v4_dist.Distribution]
187
+ if _is_v4(version):
188
+ upgraded = Ok(v4_codec.decode_versioned_distribution(text))
189
+ else:
190
+ migrated = v3_to_v4(
191
+ _read_v3_family(text, version),
192
+ package_version=package_version,
193
+ )
194
+ upgraded = Ok(migrated.value, migrated.warnings)
195
+ if drop_input_attributes:
196
+ upgraded = drop_inputs(upgraded.value)
197
+ document = v4_codec.encode_versioned_distribution(upgraded.value)
198
+ warnings: tuple[Diagnostic, ...] = upgraded.warnings
199
+ else:
200
+ if not _is_v4(version):
201
+ classic = _read_v3_family(text, version)
202
+ warnings = ()
203
+ else:
204
+ result = v4_to_v3(
205
+ v4_codec.decode_versioned_distribution(text),
206
+ drop_source_locations=drop_source_locations,
207
+ )
208
+ if isinstance(result, Err):
209
+ _report(result.diagnostics)
210
+ raise typer.Exit(1)
211
+ classic, warnings = result.value, result.warnings
212
+ document = v3_codec.encode_versioned_distribution(classic)
213
+ except DiagnosticError as error:
214
+ _report(error.diagnostics)
215
+ raise typer.Exit(1) from None
216
+ _write_atomically(output, fv.dump_json(document))
217
+ _report(warnings, warning=True)
218
+ typer.echo(f"wrote {output} (format {to.value})")
219
+
220
+
221
+ def main() -> None:
222
+ """Run the ``morphir-python`` command line."""
223
+ app()
morphir_tools/py.typed ADDED
File without changes