morphir 0.0.2__tar.gz → 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 (96) hide show
  1. {morphir-0.0.2 → morphir-0.1.0}/PKG-INFO +8 -1
  2. {morphir-0.0.2 → morphir-0.1.0}/README.md +7 -0
  3. {morphir-0.0.2 → morphir-0.1.0}/pyproject.toml +1 -1
  4. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/__init__.py +2 -2
  5. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/codec.py +228 -26
  6. {morphir-0.0.2 → morphir-0.1.0}/.gitignore +0 -0
  7. {morphir-0.0.2 → morphir-0.1.0}/LICENSE +0 -0
  8. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/__init__.py +0 -0
  9. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/access_controlled.py +0 -0
  10. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/decorations.py +0 -0
  11. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/diagnostics.py +0 -0
  12. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/distribution.py +0 -0
  13. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/document.py +0 -0
  14. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/documented.py +0 -0
  15. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/format_version.py +0 -0
  16. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/fqname.py +0 -0
  17. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/json.py +0 -0
  18. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/literal.py +0 -0
  19. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/meta.py +0 -0
  20. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/migrate.py +0 -0
  21. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/module.py +0 -0
  22. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/name.py +0 -0
  23. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/package.py +0 -0
  24. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/path.py +0 -0
  25. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/qname.py +0 -0
  26. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/ref.py +0 -0
  27. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/type.py +0 -0
  28. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/type_constraints.py +0 -0
  29. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/type_def.py +0 -0
  30. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/type_spec.py +0 -0
  31. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/__init__.py +0 -0
  32. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/_legacy_value.py +0 -0
  33. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/access_controlled.py +0 -0
  34. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/codec.py +0 -0
  35. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/codec_v1.py +0 -0
  36. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/codec_v2.py +0 -0
  37. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/distribution.py +0 -0
  38. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/documented.py +0 -0
  39. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/fqname.py +0 -0
  40. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/literal.py +0 -0
  41. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/module.py +0 -0
  42. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/name.py +0 -0
  43. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/package.py +0 -0
  44. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/path.py +0 -0
  45. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/qname.py +0 -0
  46. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/type.py +0 -0
  47. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v3/value.py +0 -0
  48. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/__init__.py +0 -0
  49. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/access_controlled.py +0 -0
  50. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/annotation.py +0 -0
  51. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/decorations.py +0 -0
  52. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/distribution.py +0 -0
  53. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/document.py +0 -0
  54. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/documented.py +0 -0
  55. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/fqname.py +0 -0
  56. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/incompleteness.py +0 -0
  57. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/json.py +0 -0
  58. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/literal.py +0 -0
  59. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/meta.py +0 -0
  60. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/module.py +0 -0
  61. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/name.py +0 -0
  62. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/package.py +0 -0
  63. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/path.py +0 -0
  64. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/qname.py +0 -0
  65. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/ref.py +0 -0
  66. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/type.py +0 -0
  67. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/type_constraints.py +0 -0
  68. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/type_def.py +0 -0
  69. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/type_spec.py +0 -0
  70. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/v4/value.py +0 -0
  71. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/ir/value.py +0 -0
  72. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/py.typed +0 -0
  73. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/__init__.py +0 -0
  74. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/_compare.py +0 -0
  75. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/aggregate.py +0 -0
  76. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/basics.py +0 -0
  77. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/char.py +0 -0
  78. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/decimal.py +0 -0
  79. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/dict.py +0 -0
  80. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/instant.py +0 -0
  81. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/int.py +0 -0
  82. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/key.py +0 -0
  83. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/list.py +0 -0
  84. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/local_date.py +0 -0
  85. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/local_time.py +0 -0
  86. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/maybe.py +0 -0
  87. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/number.py +0 -0
  88. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/regex.py +0 -0
  89. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/result.py +0 -0
  90. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/result_list.py +0 -0
  91. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/rule.py +0 -0
  92. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/set.py +0 -0
  93. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/stateful_app.py +0 -0
  94. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/string.py +0 -0
  95. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/tuple.py +0 -0
  96. {morphir-0.0.2 → morphir-0.1.0}/src/morphir/sdk/uuid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: morphir
3
- Version: 0.0.2
3
+ Version: 0.1.0
4
4
  Summary: Python port of Morphir - functional domain modeling for the enterprise
5
5
  Project-URL: Homepage, https://github.com/finos/morphir-python
6
6
  Project-URL: Documentation, https://morphir.finos.org
@@ -200,6 +200,13 @@ the distribution wrapped by its kind, never a format-3 shape. The module
200
200
  docstring lists the gaps between the Python v4 model and upstream v4, each of
201
201
  which fails with a diagnostic instead of guessing or dropping data.
202
202
 
203
+ The reader accepts every spelling the pinned schema allows. The writer emits the
204
+ compact canonical spelling that the profile prefers and Rust `morphir-core`
205
+ writes, so `{"Reference": {"fqname": "morphir/SDK:basics#int"}}` is written as
206
+ `"morphir/SDK:basics#int"`. Pass `spelling=v4_codec.Spelling.EXPANDED`, or use
207
+ `with v4_codec.use_spelling(v4_codec.Spelling.EXPANDED):`, to write every node in
208
+ the expanded form. Both spellings decode to the same model.
209
+
203
210
  The v4 model follows the upstream reference model at that commit:
204
211
 
205
212
  - A value definition is one of four bodies (`ValueDefinitionBody`): the
@@ -177,6 +177,13 @@ the distribution wrapped by its kind, never a format-3 shape. The module
177
177
  docstring lists the gaps between the Python v4 model and upstream v4, each of
178
178
  which fails with a diagnostic instead of guessing or dropping data.
179
179
 
180
+ The reader accepts every spelling the pinned schema allows. The writer emits the
181
+ compact canonical spelling that the profile prefers and Rust `morphir-core`
182
+ writes, so `{"Reference": {"fqname": "morphir/SDK:basics#int"}}` is written as
183
+ `"morphir/SDK:basics#int"`. Pass `spelling=v4_codec.Spelling.EXPANDED`, or use
184
+ `with v4_codec.use_spelling(v4_codec.Spelling.EXPANDED):`, to write every node in
185
+ the expanded form. Both spellings decode to the same model.
186
+
180
187
  The v4 model follows the upstream reference model at that commit:
181
188
 
182
189
  - A value definition is one of four bodies (`ValueDefinitionBody`): the
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "morphir"
3
- version = "0.0.2"
3
+ version = "0.1.0"
4
4
  description = "Python port of Morphir - functional domain modeling for the enterprise"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -7,8 +7,8 @@ representation (IR) that captures the semantics of your domain model.
7
7
  Example:
8
8
  >>> import morphir
9
9
  >>> morphir.__version__
10
- '0.0.2'
10
+ '0.1.0'
11
11
  """
12
12
 
13
- __version__ = "0.0.2"
13
+ __version__ = "0.1.0"
14
14
  __all__ = ["__version__"]
@@ -9,8 +9,16 @@ The envelope is ``{"formatVersion": 4, "distribution": {"Library": {...}}}``, wi
9
9
  the distribution wrapped by its kind (``Library``, ``Specs``, or ``Application``).
10
10
  The codec never writes a format-3 tagged-array shape.
11
11
 
12
- The writer emits every node in the schema's expanded form, ``{"Kind": {members}}``,
13
- with ``attributes`` omitted when empty. The profile says that readers MUST accept
12
+ The writer emits the compact canonical spelling by default, as the profile asks
13
+ and as Rust ``morphir-core`` writes it: a node without attributes takes its
14
+ shortest form (a type variable or argument-free reference as a string, a literal
15
+ as ``{"Kind": payload}``, a variable as ``{"Variable": "x"}``, a tuple as
16
+ ``{"Tuple": [...]}``), and a node with attributes keeps the expanded form
17
+ ``{"Kind": {"attributes": ..., members}}``. `Spelling.EXPANDED`, passed as
18
+ ``spelling=`` or selected with `use_spelling`, writes every node expanded.
19
+ Documentation is written flattened, ``doc`` first beside the documented type or
20
+ value (decision 0010); a module definition writes its ``doc`` after ``types``
21
+ and ``values``. The profile says that readers MUST accept
14
22
  every spelling the v4 JSON Schema marks as valid, so the reader also accepts the
15
23
  compact canonical vocabulary that Rust ``morphir-core`` writes, and the legacy
16
24
  spellings that the schema itself lists. Each spelling of a node gives the same
@@ -93,8 +101,6 @@ Known gaps between the Python v4 model and upstream v4, each handled explicitly:
93
101
  (upstream makes them definition bodies); encoding fails with ``no_v4_spelling``.
94
102
  - Structured ``TypeConstraints`` (deprecated) have no spelling; encoding fails
95
103
  with ``no_v4_spelling``.
96
- - The writer emits only the expanded spelling, not the compact canonical one the
97
- profile prefers.
98
104
  - The reader refuses the decision-0006 legacy-window spellings that the pinned
99
105
  schema marks invalid (``attrs``, ``thenBranch``, ``argumentType``, a record's
100
106
  field map directly under ``Record``, the nested ``{"value": ...}`` wrapper with
@@ -129,8 +135,11 @@ from __future__ import annotations
129
135
 
130
136
  import math
131
137
  import re
132
- from collections.abc import Callable, Collection
138
+ from collections.abc import Callable, Collection, Iterator
139
+ from contextlib import contextmanager
140
+ from contextvars import ContextVar
133
141
  from decimal import Decimal, InvalidOperation
142
+ from enum import Enum
134
143
  from typing import Any
135
144
 
136
145
  from .. import diagnostics as d
@@ -809,14 +818,94 @@ def _with_attributes(
809
818
  return {"attributes": attrs, **members} if attrs else members
810
819
 
811
820
 
821
+ class Spelling(Enum):
822
+ """Which v4 JSON spelling the encoders write.
823
+
824
+ ``COMPACT``, the default, is the canonical spelling the v4 JSON profile asks
825
+ writers to emit, as morphir-core writes it: a node without attributes takes
826
+ its shortest schema-valid form. ``EXPANDED`` writes every node as
827
+ ``{"Kind": {members}}``. Both decode to the same model, and both write
828
+ documentation flattened beside the node it documents (decision 0010).
829
+ """
830
+
831
+ COMPACT = "compact"
832
+ EXPANDED = "expanded"
833
+
834
+
835
+ _SPELLING: ContextVar[Spelling] = ContextVar(
836
+ "morphir_ir_v4_spelling", default=Spelling.COMPACT
837
+ )
838
+
839
+
840
+ @contextmanager
841
+ def use_spelling(spelling: Spelling | None) -> Iterator[None]:
842
+ """Use ``spelling`` for the v4 encoders called inside the block.
843
+
844
+ The selection is local to the current thread or task. ``None`` keeps the
845
+ selection already in effect.
846
+
847
+ Example:
848
+ >>> from morphir.ir.v4 import codec
849
+ >>> with codec.use_spelling(codec.Spelling.EXPANDED):
850
+ ... codec.encode_type(codec.decode_type("a"))
851
+ {'Variable': {'name': 'a'}}
852
+ """
853
+ if spelling is None:
854
+ yield
855
+ return
856
+ token = _SPELLING.set(spelling)
857
+ try:
858
+ yield
859
+ finally:
860
+ _SPELLING.reset(token)
861
+
862
+
863
+ def _compact(attrs: dict[str, Json]) -> bool:
864
+ return not attrs and _SPELLING.get() is Spelling.COMPACT
865
+
866
+
867
+ # The member a compact value or pattern writes directly under its tag.
868
+ _PAYLOAD = {
869
+ "Literal": "literal",
870
+ "Constructor": "fqname",
871
+ "Reference": "fqname",
872
+ "Variable": "name",
873
+ "FieldFunction": "name",
874
+ "Tuple": "elements",
875
+ "List": "items",
876
+ "TuplePattern": "patterns",
877
+ "LiteralPattern": "literal",
878
+ }
879
+
880
+
812
881
  # --- Types ---
813
882
 
814
883
 
815
- def encode_type(tpe: t.Type, loc: Location = ()) -> Json:
816
- """Encode a v4 type in the expanded draft spelling."""
884
+ def encode_type(
885
+ tpe: t.Type, loc: Location = (), *, spelling: Spelling | None = None
886
+ ) -> Json:
887
+ """Encode a v4 type, compact unless ``spelling`` or the caller selects expanded."""
888
+ with use_spelling(spelling):
889
+ return _encode_type(tpe, loc)
890
+
891
+
892
+ def _encode_type(tpe: t.Type, loc: Location) -> Json:
817
893
  attrs = _encode_type_attributes(tpe.attributes, loc)
818
894
 
819
895
  def node(tag: str, members: dict[str, Json]) -> Json:
896
+ if _compact(attrs):
897
+ match tag:
898
+ case "Variable" if _TYPE_VARIABLE_STRING.fullmatch(members["name"]):
899
+ return members["name"]
900
+ case "Variable":
901
+ # The bare string grammar is lowercase only; keep the tag.
902
+ return {"Variable": members["name"]}
903
+ case "Reference" if "args" in members:
904
+ return {"Reference": [members["fqname"], *members["args"]]}
905
+ case "Reference":
906
+ return members["fqname"]
907
+ case "Tuple":
908
+ return {"Tuple": members["elements"]}
820
909
  return _wrap(tag, _with_attributes(attrs, members))
821
910
 
822
911
  here = (*loc, type(tpe).__name__)
@@ -1231,12 +1320,21 @@ def _decode_literal_spelling(
1231
1320
  # --- Patterns ---
1232
1321
 
1233
1322
 
1234
- def encode_pattern(p: v.Pattern, loc: Location = ()) -> Json:
1235
- """Encode a v4 pattern in the expanded draft spelling."""
1323
+ def encode_pattern(
1324
+ p: v.Pattern, loc: Location = (), *, spelling: Spelling | None = None
1325
+ ) -> Json:
1326
+ """Encode a v4 pattern, compact unless ``spelling`` or the caller selects expanded."""
1327
+ with use_spelling(spelling):
1328
+ return _encode_pattern(p, loc)
1329
+
1330
+
1331
+ def _encode_pattern(p: v.Pattern, loc: Location) -> Json:
1236
1332
  attrs = _encode_value_attributes(p.attributes, loc)
1237
1333
  here = (*loc, type(p).__name__)
1238
1334
 
1239
1335
  def node(tag: str, members: dict[str, Json]) -> Json:
1336
+ if _compact(attrs) and tag in _PAYLOAD:
1337
+ return {tag: members[_PAYLOAD[tag]]}
1240
1338
  return _wrap(tag, _with_attributes(attrs, members))
1241
1339
 
1242
1340
  def many(items: list[v.Pattern], key: str) -> list[Json]:
@@ -1336,13 +1434,20 @@ def decode_pattern(j: object, loc: Location = ()) -> v.Pattern:
1336
1434
  # --- Values ---
1337
1435
 
1338
1436
 
1339
- def encode_value(val: v.Value, loc: Location = ()) -> Json:
1340
- """Encode a v4 value in the expanded draft spelling.
1437
+ def encode_value(
1438
+ val: v.Value, loc: Location = (), *, spelling: Spelling | None = None
1439
+ ) -> Json:
1440
+ """Encode a v4 value, compact unless ``spelling`` or the caller selects expanded.
1341
1441
 
1342
1442
  Raises:
1343
1443
  DiagnosticError: With ``no_v4_spelling`` for ``Native`` and ``External``
1344
1444
  nodes, or ``duplicate_member`` for repeated record fields.
1345
1445
  """
1446
+ with use_spelling(spelling):
1447
+ return _encode_value(val, loc)
1448
+
1449
+
1450
+ def _encode_value(val: v.Value, loc: Location) -> Json:
1346
1451
  construct = type(val).__name__
1347
1452
  here = (*loc, construct)
1348
1453
  if isinstance(val, (v.Native, v.External)):
@@ -1356,6 +1461,8 @@ def encode_value(val: v.Value, loc: Location = ()) -> Json:
1356
1461
  attrs = _encode_value_attributes(val.attributes, loc)
1357
1462
 
1358
1463
  def node(tag: str, members: dict[str, Json]) -> Json:
1464
+ if _compact(attrs) and tag in _PAYLOAD:
1465
+ return {tag: members[_PAYLOAD[tag]]}
1359
1466
  return _wrap(tag, _with_attributes(attrs, members))
1360
1467
 
1361
1468
  def sub(x: v.Value, key: str) -> Json:
@@ -1833,7 +1940,17 @@ def _decode_externals(j: object, loc: Location) -> list[v.ExternalBinding]:
1833
1940
  return bindings
1834
1941
 
1835
1942
 
1836
- def encode_value_definition(defn: v.ValueDefinitionBody, loc: Location = ()) -> Json:
1943
+ def encode_value_definition(
1944
+ defn: v.ValueDefinitionBody, loc: Location = (), *, spelling: Spelling | None = None
1945
+ ) -> Json:
1946
+ """Encode with `Spelling.COMPACT` unless `spelling` or the caller selects otherwise."""
1947
+ with use_spelling(spelling):
1948
+ return _encode_value_definition_body(defn, loc)
1949
+
1950
+
1951
+ def _encode_value_definition_body(
1952
+ defn: v.ValueDefinitionBody, loc: Location = ()
1953
+ ) -> Json:
1837
1954
  """Encode a value definition as ``{"<Kind>Body": {...}}``.
1838
1955
 
1839
1956
  Raises:
@@ -1950,7 +2067,15 @@ def decode_value_definition(j: object, loc: Location = ()) -> v.ValueDefinitionB
1950
2067
  )
1951
2068
 
1952
2069
 
1953
- def encode_value_specification(spec: v.Specification, loc: Location = ()) -> Json:
2070
+ def encode_value_specification(
2071
+ spec: v.Specification, loc: Location = (), *, spelling: Spelling | None = None
2072
+ ) -> Json:
2073
+ """Encode with `Spelling.COMPACT` unless `spelling` or the caller selects otherwise."""
2074
+ with use_spelling(spelling):
2075
+ return _encode_value_specification_body(spec, loc)
2076
+
2077
+
2078
+ def _encode_value_specification_body(spec: v.Specification, loc: Location = ()) -> Json:
1954
2079
  return _with_annotations(
1955
2080
  spec.annotations,
1956
2081
  loc,
@@ -2151,7 +2276,15 @@ def _decode_ctor_args(j: object, loc: Location) -> list[tuple[Name, t.Type]]:
2151
2276
  return _decode_list(_decode_name_type_pair, j, loc, "Constructor")
2152
2277
 
2153
2278
 
2154
- def encode_type_specification(spec: ts.Specification, loc: Location = ()) -> Json:
2279
+ def encode_type_specification(
2280
+ spec: ts.Specification, loc: Location = (), *, spelling: Spelling | None = None
2281
+ ) -> Json:
2282
+ """Encode with `Spelling.COMPACT` unless `spelling` or the caller selects otherwise."""
2283
+ with use_spelling(spelling):
2284
+ return _encode_type_specification_body(spec, loc)
2285
+
2286
+
2287
+ def _encode_type_specification_body(spec: ts.Specification, loc: Location = ()) -> Json:
2155
2288
  tag = type(spec).__name__
2156
2289
  encoded = _encode_type_specification(spec, loc)
2157
2290
  return _wrap(tag, _with_annotations(spec.annotations, (*loc, tag), encoded[tag]))
@@ -2256,7 +2389,15 @@ def _decode_legacy_type_specification(
2256
2389
  )
2257
2390
 
2258
2391
 
2259
- def encode_type_definition(defn: td.Definition, loc: Location = ()) -> Json:
2392
+ def encode_type_definition(
2393
+ defn: td.Definition, loc: Location = (), *, spelling: Spelling | None = None
2394
+ ) -> Json:
2395
+ """Encode with `Spelling.COMPACT` unless `spelling` or the caller selects otherwise."""
2396
+ with use_spelling(spelling):
2397
+ return _encode_type_definition_body(defn, loc)
2398
+
2399
+
2400
+ def _encode_type_definition_body(defn: td.Definition, loc: Location = ()) -> Json:
2260
2401
  params = _encode_params(defn.type_params, loc)
2261
2402
  match defn:
2262
2403
  case td.TypeAliasDefinition(_, tpe):
@@ -2335,9 +2476,19 @@ def _encode_documented[A](
2335
2476
  encode: Callable[[A, Location], Json],
2336
2477
  ) -> Callable[[Documented[A], Location], Json]:
2337
2478
  def run(item: Documented[A], loc: Location) -> Json:
2479
+ encoded = encode(item.value, loc)
2338
2480
  if item.doc is None:
2339
- return encode(item.value, loc)
2340
- return {"doc": item.doc, "value": encode(item.value, (*loc, "value"))}
2481
+ return encoded
2482
+ if not isinstance(encoded, dict):
2483
+ raise fail(
2484
+ d.NO_V4_SPELLING,
2485
+ "doc",
2486
+ loc,
2487
+ "documentation is flattened beside the node it documents, "
2488
+ "so the node must be a JSON object",
2489
+ )
2490
+ # Decision 0010: `doc` is a flattened member placed first.
2491
+ return {"doc": item.doc, **encoded}
2341
2492
 
2342
2493
  return run
2343
2494
 
@@ -2471,7 +2622,17 @@ def _decode_doc(obj: dict[str, object], loc: Location) -> str | None:
2471
2622
  return _expect_str(obj["doc"], (*loc, "doc"), "doc")
2472
2623
 
2473
2624
 
2474
- def encode_module_specification(spec: module.Specification, loc: Location = ()) -> Json:
2625
+ def encode_module_specification(
2626
+ spec: module.Specification, loc: Location = (), *, spelling: Spelling | None = None
2627
+ ) -> Json:
2628
+ """Encode with `Spelling.COMPACT` unless `spelling` or the caller selects otherwise."""
2629
+ with use_spelling(spelling):
2630
+ return _encode_module_specification_body(spec, loc)
2631
+
2632
+
2633
+ def _encode_module_specification_body(
2634
+ spec: module.Specification, loc: Location = ()
2635
+ ) -> Json:
2475
2636
  return _with_annotations(
2476
2637
  spec.annotations,
2477
2638
  loc,
@@ -2518,7 +2679,15 @@ def decode_module_specification(j: object, loc: Location = ()) -> module.Specifi
2518
2679
  )
2519
2680
 
2520
2681
 
2521
- def encode_module_definition(defn: module.Definition, loc: Location = ()) -> Json:
2682
+ def encode_module_definition(
2683
+ defn: module.Definition, loc: Location = (), *, spelling: Spelling | None = None
2684
+ ) -> Json:
2685
+ """Encode with `Spelling.COMPACT` unless `spelling` or the caller selects otherwise."""
2686
+ with use_spelling(spelling):
2687
+ return _encode_module_definition_body(defn, loc)
2688
+
2689
+
2690
+ def _encode_module_definition_body(defn: module.Definition, loc: Location = ()) -> Json:
2522
2691
  return _with_doc(
2523
2692
  {
2524
2693
  "types": _keyed(
@@ -2563,6 +2732,14 @@ _dec_module_def = _decode_access(decode_module_definition)
2563
2732
 
2564
2733
 
2565
2734
  def encode_package_specification(
2735
+ spec: package.Specification, loc: Location = (), *, spelling: Spelling | None = None
2736
+ ) -> Json:
2737
+ """Encode with `Spelling.COMPACT` unless `spelling` or the caller selects otherwise."""
2738
+ with use_spelling(spelling):
2739
+ return _encode_package_specification_body(spec, loc)
2740
+
2741
+
2742
+ def _encode_package_specification_body(
2566
2743
  spec: package.Specification, loc: Location = ()
2567
2744
  ) -> Json:
2568
2745
  return {
@@ -2594,7 +2771,17 @@ def decode_package_specification(
2594
2771
  )
2595
2772
 
2596
2773
 
2597
- def encode_package_definition(defn: package.Definition, loc: Location = ()) -> Json:
2774
+ def encode_package_definition(
2775
+ defn: package.Definition, loc: Location = (), *, spelling: Spelling | None = None
2776
+ ) -> Json:
2777
+ """Encode with `Spelling.COMPACT` unless `spelling` or the caller selects otherwise."""
2778
+ with use_spelling(spelling):
2779
+ return _encode_package_definition_body(defn, loc)
2780
+
2781
+
2782
+ def _encode_package_definition_body(
2783
+ defn: package.Definition, loc: Location = ()
2784
+ ) -> Json:
2598
2785
  return {
2599
2786
  "modules": _keyed(
2600
2787
  defn.modules, _path_key, _enc_module_def, (*loc, "modules"), "modules"
@@ -2651,7 +2838,15 @@ def _decode_entry_point(j: object, loc: Location) -> EntryPoint:
2651
2838
  )
2652
2839
 
2653
2840
 
2654
- def encode_distribution(dist: Distribution, loc: Location = ()) -> Json:
2841
+ def encode_distribution(
2842
+ dist: Distribution, loc: Location = (), *, spelling: Spelling | None = None
2843
+ ) -> Json:
2844
+ """Encode with `Spelling.COMPACT` unless `spelling` or the caller selects otherwise."""
2845
+ with use_spelling(spelling):
2846
+ return _encode_distribution_body(dist, loc)
2847
+
2848
+
2849
+ def _encode_distribution_body(dist: Distribution, loc: Location = ()) -> Json:
2655
2850
  """Encode a distribution wrapped by its kind.
2656
2851
 
2657
2852
  The package release version is not written; the v4 profile has no member for it.
@@ -2772,17 +2967,24 @@ def decode_distribution(j: object, loc: Location = ()) -> Distribution:
2772
2967
  )
2773
2968
 
2774
2969
 
2775
- def encode_versioned_distribution(dist: Distribution) -> dict[str, Json]:
2970
+ def encode_versioned_distribution(
2971
+ dist: Distribution, *, spelling: Spelling | None = None
2972
+ ) -> dict[str, Json]:
2776
2973
  """Encode ``{"formatVersion": 4, "distribution": ...}``, version first.
2777
2974
 
2975
+ Args:
2976
+ dist: The distribution to encode.
2977
+ spelling: `Spelling.COMPACT`, the default, or `Spelling.EXPANDED`.
2978
+
2778
2979
  Raises:
2779
2980
  DiagnosticError: When the distribution uses a construct with no draft
2780
2981
  spelling.
2781
2982
  """
2782
- return {
2783
- "formatVersion": write_format_version(V4),
2784
- "distribution": encode_distribution(dist, ("distribution",)),
2785
- }
2983
+ with use_spelling(spelling):
2984
+ return {
2985
+ "formatVersion": write_format_version(V4),
2986
+ "distribution": encode_distribution(dist, ("distribution",)),
2987
+ }
2786
2988
 
2787
2989
 
2788
2990
  def decode_versioned_distribution(document: str | object) -> Distribution:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes