pysdmx 1.5.2__py3-none-any.whl → 1.6.0__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 (58) hide show
  1. pysdmx/__init__.py +1 -1
  2. pysdmx/api/fmr/__init__.py +8 -3
  3. pysdmx/api/fmr/maintenance.py +158 -0
  4. pysdmx/api/qb/structure.py +1 -0
  5. pysdmx/api/qb/util.py +1 -0
  6. pysdmx/io/csv/__csv_aux_reader.py +99 -0
  7. pysdmx/io/csv/__csv_aux_writer.py +118 -0
  8. pysdmx/io/csv/sdmx10/reader/__init__.py +9 -14
  9. pysdmx/io/csv/sdmx10/writer/__init__.py +28 -2
  10. pysdmx/io/csv/sdmx20/__init__.py +0 -9
  11. pysdmx/io/csv/sdmx20/reader/__init__.py +8 -61
  12. pysdmx/io/csv/sdmx20/writer/__init__.py +32 -25
  13. pysdmx/io/csv/sdmx21/__init__.py +1 -0
  14. pysdmx/io/csv/sdmx21/reader/__init__.py +86 -0
  15. pysdmx/io/csv/sdmx21/writer/__init__.py +70 -0
  16. pysdmx/io/format.py +8 -0
  17. pysdmx/io/input_processor.py +16 -2
  18. pysdmx/io/json/fusion/messages/code.py +21 -4
  19. pysdmx/io/json/fusion/messages/concept.py +16 -8
  20. pysdmx/io/json/fusion/messages/dataflow.py +8 -1
  21. pysdmx/io/json/fusion/messages/dsd.py +15 -0
  22. pysdmx/io/json/fusion/messages/schema.py +8 -1
  23. pysdmx/io/json/sdmxjson2/messages/agency.py +43 -7
  24. pysdmx/io/json/sdmxjson2/messages/category.py +92 -7
  25. pysdmx/io/json/sdmxjson2/messages/code.py +239 -18
  26. pysdmx/io/json/sdmxjson2/messages/concept.py +78 -13
  27. pysdmx/io/json/sdmxjson2/messages/constraint.py +5 -5
  28. pysdmx/io/json/sdmxjson2/messages/core.py +121 -14
  29. pysdmx/io/json/sdmxjson2/messages/dataflow.py +63 -8
  30. pysdmx/io/json/sdmxjson2/messages/dsd.py +215 -20
  31. pysdmx/io/json/sdmxjson2/messages/map.py +200 -24
  32. pysdmx/io/json/sdmxjson2/messages/pa.py +36 -5
  33. pysdmx/io/json/sdmxjson2/messages/provider.py +35 -7
  34. pysdmx/io/json/sdmxjson2/messages/report.py +85 -7
  35. pysdmx/io/json/sdmxjson2/messages/schema.py +11 -12
  36. pysdmx/io/json/sdmxjson2/messages/structure.py +150 -2
  37. pysdmx/io/json/sdmxjson2/messages/vtl.py +547 -17
  38. pysdmx/io/json/sdmxjson2/reader/metadata.py +32 -0
  39. pysdmx/io/json/sdmxjson2/reader/structure.py +32 -0
  40. pysdmx/io/json/sdmxjson2/writer/__init__.py +9 -0
  41. pysdmx/io/json/sdmxjson2/writer/metadata.py +60 -0
  42. pysdmx/io/json/sdmxjson2/writer/structure.py +61 -0
  43. pysdmx/io/reader.py +28 -9
  44. pysdmx/io/serde.py +17 -0
  45. pysdmx/io/writer.py +45 -9
  46. pysdmx/io/xml/__write_data_aux.py +1 -54
  47. pysdmx/io/xml/__write_structure_specific_aux.py +1 -1
  48. pysdmx/io/xml/sdmx21/writer/generic.py +1 -1
  49. pysdmx/model/code.py +11 -1
  50. pysdmx/model/dataflow.py +23 -0
  51. pysdmx/model/map.py +12 -4
  52. pysdmx/model/message.py +9 -1
  53. pysdmx/toolkit/pd/_data_utils.py +100 -0
  54. pysdmx/toolkit/vtl/_validations.py +2 -3
  55. {pysdmx-1.5.2.dist-info → pysdmx-1.6.0.dist-info}/METADATA +3 -2
  56. {pysdmx-1.5.2.dist-info → pysdmx-1.6.0.dist-info}/RECORD +58 -46
  57. {pysdmx-1.5.2.dist-info → pysdmx-1.6.0.dist-info}/WHEEL +1 -1
  58. {pysdmx-1.5.2.dist-info → pysdmx-1.6.0.dist-info/licenses}/LICENSE +0 -0
@@ -2,18 +2,20 @@
2
2
 
3
3
  from typing import Optional, Sequence
4
4
 
5
- from msgspec import Struct
5
+ import msgspec
6
6
 
7
+ from pysdmx import errors
7
8
  from pysdmx.io.json.sdmxjson2.messages.code import JsonCodelist
8
9
  from pysdmx.io.json.sdmxjson2.messages.core import (
9
10
  ItemSchemeType,
11
+ JsonAnnotation,
10
12
  JsonRepresentation,
11
13
  NameableType,
12
14
  )
13
- from pysdmx.model import Codelist, Concept, ConceptScheme, DataType
15
+ from pysdmx.model import Agency, Codelist, Concept, ConceptScheme, DataType
14
16
 
15
17
 
16
- class IsoConceptReference(Struct, frozen=True):
18
+ class IsoConceptReference(msgspec.Struct, frozen=True, omit_defaults=True):
17
19
  """Payload for a reference to an ISO 11179 concept."""
18
20
 
19
21
  conceptAgency: str
@@ -21,7 +23,7 @@ class IsoConceptReference(Struct, frozen=True):
21
23
  conceptID: str
22
24
 
23
25
 
24
- class JsonConcept(NameableType, frozen=True):
26
+ class JsonConcept(NameableType, frozen=True, omit_defaults=True):
25
27
  """SDMX-JSON payload for concepts."""
26
28
 
27
29
  coreRepresentation: Optional[JsonRepresentation] = None
@@ -56,22 +58,62 @@ class JsonConcept(NameableType, frozen=True):
56
58
  enum_ref=cl_ref,
57
59
  )
58
60
 
61
+ @classmethod
62
+ def from_model(self, concept: Concept) -> "JsonConcept":
63
+ """Converts a pysdmx concept to an SDMX-JSON one."""
64
+ if not concept.name:
65
+ raise errors.Invalid(
66
+ "Invalid input",
67
+ "SDMX-JSON concepts must have a name",
68
+ {"codelist": concept.id},
69
+ )
70
+ if concept.codes:
71
+ enum_ref = (
72
+ "urn:sdmx:org.sdmx.infomodel.codelist."
73
+ f"{concept.codes.short_urn}"
74
+ )
75
+ elif concept.enum_ref:
76
+ enum_ref = concept.enum_ref
77
+ else:
78
+ enum_ref = None
79
+
80
+ return JsonConcept(
81
+ id=concept.id,
82
+ name=concept.name,
83
+ description=concept.description,
84
+ annotations=tuple(
85
+ [JsonAnnotation.from_model(a) for a in concept.annotations]
86
+ ),
87
+ coreRepresentation=JsonRepresentation.from_model(
88
+ concept.dtype, enum_ref, concept.facets, None
89
+ ),
90
+ )
59
91
 
60
- class JsonConceptScheme(ItemSchemeType, frozen=True):
92
+
93
+ class JsonConceptScheme(ItemSchemeType, frozen=True, omit_defaults=True):
61
94
  """SDMX-JSON payload for a concept scheme."""
62
95
 
63
96
  concepts: Sequence[JsonConcept] = ()
64
97
 
98
+ def __set_urn(self, concept: Concept) -> Concept:
99
+ urn = (
100
+ "urn:sdmx:org.sdmx.infomodel.conceptscheme.Concept="
101
+ f"{self.agency}:{self.id}({self.version}).{concept.id}"
102
+ )
103
+ return msgspec.structs.replace(concept, urn=urn)
104
+
65
105
  def to_model(self, codelists: Sequence[JsonCodelist]) -> ConceptScheme:
66
106
  """Converts a JsonConceptScheme to a standard concept scheme."""
67
- cls = [c.to_model() for c in codelists]
107
+ cls = [cl.to_model() for cl in codelists]
108
+ concepts = [c.to_model(cls) for c in self.concepts]
109
+ concepts = [self.__set_urn(c) for c in concepts]
68
110
  return ConceptScheme(
69
111
  id=self.id,
70
112
  name=self.name,
71
113
  agency=self.agency,
72
114
  description=self.description,
73
115
  version=self.version,
74
- items=[c.to_model(cls) for c in self.concepts],
116
+ items=concepts,
75
117
  annotations=[a.to_model() for a in self.annotations],
76
118
  is_external_reference=self.isExternalReference,
77
119
  is_partial=self.isPartial,
@@ -79,11 +121,35 @@ class JsonConceptScheme(ItemSchemeType, frozen=True):
79
121
  valid_to=self.validTo,
80
122
  )
81
123
 
124
+ @classmethod
125
+ def from_model(self, cs: ConceptScheme) -> "JsonConceptScheme":
126
+ """Converts a pysdmx concept scheme to an SDMX-JSON one."""
127
+ if not cs.name:
128
+ raise errors.Invalid(
129
+ "Invalid input",
130
+ "SDMX-JSON concept schemes must have a name",
131
+ {"concept_scheme": cs.id},
132
+ )
133
+ return JsonConceptScheme(
134
+ agency=(
135
+ cs.agency.id if isinstance(cs.agency, Agency) else cs.agency
136
+ ),
137
+ id=cs.id,
138
+ name=cs.name,
139
+ version=cs.version,
140
+ isExternalReference=cs.is_external_reference,
141
+ validFrom=cs.valid_from,
142
+ validTo=cs.valid_to,
143
+ description=cs.description,
144
+ annotations=tuple(
145
+ [JsonAnnotation.from_model(a) for a in cs.annotations]
146
+ ),
147
+ isPartial=cs.is_partial,
148
+ concepts=tuple([JsonConcept.from_model(c) for c in cs.concepts]),
149
+ )
150
+
82
151
 
83
- class JsonConceptSchemes(
84
- Struct,
85
- frozen=True,
86
- ):
152
+ class JsonConceptSchemes(msgspec.Struct, frozen=True, omit_defaults=True):
87
153
  """SDMX-JSON payload for the list of concept schemes."""
88
154
 
89
155
  conceptSchemes: Sequence[JsonConceptScheme]
@@ -91,8 +157,7 @@ class JsonConceptSchemes(
91
157
 
92
158
 
93
159
  class JsonConceptSchemeMessage(
94
- Struct,
95
- frozen=True,
160
+ msgspec.Struct, frozen=True, omit_defaults=True
96
161
  ):
97
162
  """SDMX-JSON payload for /conceptscheme queries."""
98
163
 
@@ -7,13 +7,13 @@ from msgspec import Struct
7
7
  from pysdmx.io.json.sdmxjson2.messages.core import MaintainableType
8
8
 
9
9
 
10
- class JsonValue(Struct, frozen=True):
10
+ class JsonValue(Struct, frozen=True, omit_defaults=True):
11
11
  """SDMX-JSON payload for an allowed value."""
12
12
 
13
13
  value: str
14
14
 
15
15
 
16
- class JsonKeyValue(Struct, frozen=True):
16
+ class JsonKeyValue(Struct, frozen=True, omit_defaults=True):
17
17
  """SDMX-JSON payload for the list of allowed values per component."""
18
18
 
19
19
  id: str
@@ -24,7 +24,7 @@ class JsonKeyValue(Struct, frozen=True):
24
24
  return [v.value for v in self.values]
25
25
 
26
26
 
27
- class JsonCubeRegion(Struct, frozen=True):
27
+ class JsonCubeRegion(Struct, frozen=True, omit_defaults=True):
28
28
  """SDMX-JSON payload for a cube region."""
29
29
 
30
30
  keyValues: Sequence[JsonKeyValue]
@@ -34,7 +34,7 @@ class JsonCubeRegion(Struct, frozen=True):
34
34
  return {kv.id: kv.to_model() for kv in self.keyValues}
35
35
 
36
36
 
37
- class JsonConstraintAttachment(Struct, frozen=True):
37
+ class JsonConstraintAttachment(Struct, frozen=True, omit_defaults=True):
38
38
  """SDMX-JSON payload for a constraint attachment."""
39
39
 
40
40
  dataProvider: Optional[str]
@@ -45,7 +45,7 @@ class JsonConstraintAttachment(Struct, frozen=True):
45
45
  queryableDataSources: Optional[Sequence[str]] = None
46
46
 
47
47
 
48
- class JsonDataConstraint(MaintainableType, frozen=True):
48
+ class JsonDataConstraint(MaintainableType, frozen=True, omit_defaults=True):
49
49
  """SDMX-JSON payload for a content constraint."""
50
50
 
51
51
  role: Optional[Literal["Allowed", "Actual"]] = None
@@ -1,7 +1,7 @@
1
1
  """Collection of SDMX-JSON schemas for common artefacts."""
2
2
 
3
3
  from datetime import datetime
4
- from typing import Optional, Sequence, Union
4
+ from typing import Literal, Optional, Sequence, Union
5
5
 
6
6
  import msgspec
7
7
 
@@ -10,6 +10,7 @@ from pysdmx.model import (
10
10
  Annotation,
11
11
  ArrayBoundaries,
12
12
  Codelist,
13
+ DataType,
13
14
  Facets,
14
15
  Organisation,
15
16
  )
@@ -17,7 +18,7 @@ from pysdmx.model.message import Header
17
18
  from pysdmx.util import find_by_urn
18
19
 
19
20
 
20
- class JsonLink(msgspec.Struct, frozen=True):
21
+ class JsonLink(msgspec.Struct, frozen=True, omit_defaults=True):
21
22
  """SDMX-JSON payload for link objects."""
22
23
 
23
24
  href: Optional[str] = None
@@ -29,7 +30,7 @@ class JsonLink(msgspec.Struct, frozen=True):
29
30
  hreflang: Optional[str] = None
30
31
 
31
32
 
32
- class JsonAnnotation(msgspec.Struct, frozen=True):
33
+ class JsonAnnotation(msgspec.Struct, frozen=True, omit_defaults=True):
33
34
  """SDMX-JSON payload for annotations."""
34
35
 
35
36
  id: Optional[str] = None
@@ -57,15 +58,31 @@ class JsonAnnotation(msgspec.Struct, frozen=True):
57
58
  text=self.value if self.value else self.text,
58
59
  )
59
60
 
61
+ @classmethod
62
+ def from_model(self, annotation: Annotation) -> "JsonAnnotation":
63
+ """Converts a pysdmx annotation to an SDMX-JSON one."""
64
+ if annotation.url:
65
+ links = (JsonLink(rel="self", href=annotation.url),)
66
+ else:
67
+ links = () # type: ignore[assignment]
68
+
69
+ return JsonAnnotation(
70
+ id=annotation.id,
71
+ title=annotation.title,
72
+ type=annotation.type,
73
+ text=annotation.text,
74
+ links=links,
75
+ )
60
76
 
61
- class IdentifiableType(msgspec.Struct, frozen=True):
77
+
78
+ class IdentifiableType(msgspec.Struct, frozen=True, omit_defaults=True):
62
79
  """An abstract base type used for all nameable artefacts."""
63
80
 
64
81
  id: str
65
82
  annotations: Sequence[JsonAnnotation] = ()
66
83
 
67
84
 
68
- class NameableType(msgspec.Struct, frozen=True):
85
+ class NameableType(msgspec.Struct, frozen=True, omit_defaults=True):
69
86
  """An abstract base type used for all nameable artefacts."""
70
87
 
71
88
  id: str
@@ -75,7 +92,10 @@ class NameableType(msgspec.Struct, frozen=True):
75
92
 
76
93
 
77
94
  class MaintainableType(
78
- msgspec.Struct, frozen=True, rename={"agency": "agencyID"}
95
+ msgspec.Struct,
96
+ frozen=True,
97
+ rename={"agency": "agencyID"},
98
+ omit_defaults=True,
79
99
  ):
80
100
  """An abstract base type used for all maintainable artefacts."""
81
101
 
@@ -90,16 +110,16 @@ class MaintainableType(
90
110
  annotations: Sequence[JsonAnnotation] = ()
91
111
 
92
112
 
93
- class ItemSchemeType(MaintainableType, frozen=True):
113
+ class ItemSchemeType(MaintainableType, frozen=True, omit_defaults=True):
94
114
  """An abstract base type used for all item schemes."""
95
115
 
96
116
  isPartial: bool = False
97
117
 
98
118
 
99
- class JsonTextFormat(msgspec.Struct, frozen=True):
119
+ class JsonTextFormat(msgspec.Struct, frozen=True, omit_defaults=True):
100
120
  """SDMX-JSON payload for TextFormat."""
101
121
 
102
- dataType: str
122
+ dataType: Optional[str] = None
103
123
  minLength: Optional[int] = None
104
124
  maxLength: Optional[int] = None
105
125
  minValue: Optional[Union[int, float]] = None
@@ -114,7 +134,37 @@ class JsonTextFormat(msgspec.Struct, frozen=True):
114
134
  isMultilingual: bool = False
115
135
  sentinelValues: Optional[Sequence[str]] = None
116
136
  timeInterval: Optional[str] = None
117
- interval: Optional[int] = None
137
+ interval: Optional[Union[int, float]] = None
138
+
139
+ @classmethod
140
+ def from_model(
141
+ self, dtype: Optional[DataType], facets: Optional[Facets]
142
+ ) -> Optional["JsonTextFormat"]:
143
+ """Converts pysdmx format details to an SDMX-JSON JsonTextFormat."""
144
+ if dtype is None and facets is None:
145
+ return None
146
+ else:
147
+ typ = dtype.value if dtype else None
148
+ if facets is None:
149
+ return JsonTextFormat(typ)
150
+ else:
151
+ return JsonTextFormat(
152
+ typ,
153
+ facets.min_length,
154
+ facets.max_length,
155
+ facets.min_value,
156
+ facets.max_value,
157
+ facets.start_value,
158
+ facets.end_value,
159
+ facets.decimals,
160
+ facets.pattern,
161
+ facets.start_time,
162
+ facets.end_time,
163
+ facets.is_sequence,
164
+ facets.is_multilingual,
165
+ timeInterval=facets.time_interval,
166
+ interval=facets.interval,
167
+ )
118
168
 
119
169
 
120
170
  def get_facets(input: JsonTextFormat) -> Facets:
@@ -135,7 +185,7 @@ def get_facets(input: JsonTextFormat) -> Facets:
135
185
  )
136
186
 
137
187
 
138
- class JsonRepresentation(msgspec.Struct, frozen=True):
188
+ class JsonRepresentation(msgspec.Struct, frozen=True, omit_defaults=True):
139
189
  """SDMX-JSON payload for core representation."""
140
190
 
141
191
  enumerationFormat: Optional[JsonTextFormat] = None
@@ -192,8 +242,44 @@ class JsonRepresentation(msgspec.Struct, frozen=True):
192
242
  else:
193
243
  return None
194
244
 
195
-
196
- class JsonHeader(msgspec.Struct, frozen=True):
245
+ @classmethod
246
+ def from_model(
247
+ self,
248
+ dtype: Optional[DataType],
249
+ enumeration: Optional[str],
250
+ facets: Optional[Facets],
251
+ array_def: Optional[ArrayBoundaries],
252
+ ) -> Optional["JsonRepresentation"]:
253
+ """Converts pysdmx representation details to an SDMX-JSON one."""
254
+ if (
255
+ dtype is None
256
+ and enumeration is None
257
+ and facets is None
258
+ and array_def is None
259
+ ):
260
+ return None
261
+ else:
262
+ fmt = JsonTextFormat.from_model(dtype, facets)
263
+ if array_def:
264
+ mino = array_def.min_size
265
+ maxo = array_def.max_size
266
+ else:
267
+ mino = None
268
+ maxo = None
269
+ if enumeration:
270
+ return JsonRepresentation(
271
+ enumerationFormat=fmt,
272
+ enumeration=enumeration,
273
+ minOccurs=mino,
274
+ maxOccurs=maxo,
275
+ )
276
+ else:
277
+ return JsonRepresentation(
278
+ format=fmt, minOccurs=mino, maxOccurs=maxo
279
+ )
280
+
281
+
282
+ class JsonHeader(msgspec.Struct, frozen=True, omit_defaults=True):
197
283
  """SDMX-JSON payload for message header."""
198
284
 
199
285
  id: str
@@ -202,8 +288,9 @@ class JsonHeader(msgspec.Struct, frozen=True):
202
288
  test: bool = False
203
289
  contentLanguages: Sequence[str] = ()
204
290
  name: Optional[str] = None
205
- receivers: Optional[Organisation] = None
291
+ receivers: Optional[Sequence[Organisation]] = None
206
292
  links: Sequence[JsonLink] = ()
293
+ schema: Optional[str] = None
207
294
 
208
295
  def to_model(self) -> Header:
209
296
  """Map to pysdmx header class."""
@@ -213,3 +300,23 @@ class JsonHeader(msgspec.Struct, frozen=True):
213
300
  prepared=self.prepared,
214
301
  sender=self.sender,
215
302
  )
303
+
304
+ @classmethod
305
+ def from_model(
306
+ self,
307
+ header: Header,
308
+ msg_type: Literal["structure", "metadata"] = "structure",
309
+ ) -> "JsonHeader":
310
+ """Create an SDMX-JSON header from a pysdmx Header."""
311
+ return JsonHeader(
312
+ header.id,
313
+ header.prepared,
314
+ header.sender,
315
+ header.test,
316
+ receivers=(header.receiver,) if header.receiver else None,
317
+ schema=(
318
+ "https://raw.githubusercontent.com/sdmx-twg/sdmx-json/"
319
+ "develop/structure-message/tools/schemas/2.0.0/"
320
+ f"sdmx-json-{msg_type}-schema.json"
321
+ ),
322
+ )
@@ -4,9 +4,13 @@ from typing import List, Optional, Sequence, Union
4
4
 
5
5
  from msgspec import Struct
6
6
 
7
+ from pysdmx import errors
7
8
  from pysdmx.io.json.sdmxjson2.messages.code import JsonCodelist, JsonValuelist
8
9
  from pysdmx.io.json.sdmxjson2.messages.concept import JsonConceptScheme
9
- from pysdmx.io.json.sdmxjson2.messages.core import MaintainableType
10
+ from pysdmx.io.json.sdmxjson2.messages.core import (
11
+ JsonAnnotation,
12
+ MaintainableType,
13
+ )
10
14
  from pysdmx.io.json.sdmxjson2.messages.dsd import JsonDataStructure
11
15
  from pysdmx.io.json.sdmxjson2.messages.provider import JsonDataProviderScheme
12
16
  from pysdmx.model import (
@@ -17,10 +21,11 @@ from pysdmx.model import (
17
21
  DataProvider,
18
22
  DataStructureDefinition,
19
23
  )
24
+ from pysdmx.model.dataflow import Group
20
25
  from pysdmx.util import parse_urn
21
26
 
22
27
 
23
- class JsonDataflow(MaintainableType, frozen=True):
28
+ class JsonDataflow(MaintainableType, frozen=True, omit_defaults=True):
24
29
  """SDMX-JSON payload for a dataflow."""
25
30
 
26
31
  structure: str = ""
@@ -59,8 +64,47 @@ class JsonDataflow(MaintainableType, frozen=True):
59
64
  valid_to=self.validTo,
60
65
  )
61
66
 
67
+ @classmethod
68
+ def from_model(self, df: Dataflow) -> "JsonDataflow":
69
+ """Converts a pysdmx dataflow to an SDMX-JSON one."""
70
+ if not df.name:
71
+ raise errors.Invalid(
72
+ "Invalid input",
73
+ "SDMX-JSON dataflows must have a name",
74
+ {"dataflow": df.id},
75
+ )
76
+ if not df.structure:
77
+ raise errors.Invalid(
78
+ "Invalid input",
79
+ "SDMX-JSON dataflows must reference a DSD.",
80
+ {"dataflow": df.id},
81
+ )
82
+ if isinstance(df.structure, DataStructureDefinition):
83
+ dsdref = (
84
+ "urn:sdmx:org.sdmx.infomodel.datastructure.DataStructure="
85
+ f"{df.structure.agency}:{df.structure.id}({df.structure.version})"
86
+ )
87
+ else:
88
+ dsdref = df.structure
89
+ return JsonDataflow(
90
+ agency=(
91
+ df.agency.id if isinstance(df.agency, Agency) else df.agency
92
+ ),
93
+ id=df.id,
94
+ name=df.name,
95
+ version=df.version,
96
+ isExternalReference=df.is_external_reference,
97
+ validFrom=df.valid_from,
98
+ validTo=df.valid_to,
99
+ description=df.description,
100
+ annotations=tuple(
101
+ [JsonAnnotation.from_model(a) for a in df.annotations]
102
+ ),
103
+ structure=dsdref,
104
+ )
62
105
 
63
- class JsonDataflows(Struct, frozen=True):
106
+
107
+ class JsonDataflows(Struct, frozen=True, omit_defaults=True):
64
108
  """SDMX-JSON payload for the list of dataflows."""
65
109
 
66
110
  dataflows: Sequence[JsonDataflow]
@@ -81,7 +125,12 @@ class JsonDataflows(Struct, frozen=True):
81
125
  return df.agency == agency and df.id == id_
82
126
 
83
127
  def to_model(
84
- self, components: Components, agency: str, id_: str, version: str
128
+ self,
129
+ components: Components,
130
+ grps: Optional[Sequence[Group]],
131
+ agency: str,
132
+ id_: str,
133
+ version: str,
85
134
  ) -> DataflowInfo:
86
135
  """Returns the requested dataflow details."""
87
136
  prvs: List[DataProvider] = []
@@ -102,6 +151,7 @@ class JsonDataflows(Struct, frozen=True):
102
151
  version=df.version,
103
152
  providers=prvs,
104
153
  dsd_ref=df.structure,
154
+ groups=grps,
105
155
  )
106
156
 
107
157
  def to_generic_model(self) -> Sequence[Dataflow]:
@@ -117,19 +167,24 @@ class JsonDataflows(Struct, frozen=True):
117
167
  ]
118
168
 
119
169
 
120
- class JsonDataflowMessage(Struct, frozen=True):
170
+ class JsonDataflowMessage(Struct, frozen=True, omit_defaults=True):
121
171
  """SDMX-JSON payload for /dataflow queries (with details)."""
122
172
 
123
173
  data: JsonDataflows
124
174
 
125
175
  def to_model(
126
- self, components: Components, agency: str, id_: str, version: str
176
+ self,
177
+ components: Components,
178
+ grps: Optional[Sequence[Group]],
179
+ agency: str,
180
+ id_: str,
181
+ version: str,
127
182
  ) -> DataflowInfo:
128
183
  """Returns the requested dataflow details."""
129
- return self.data.to_model(components, agency, id_, version)
184
+ return self.data.to_model(components, grps, agency, id_, version)
130
185
 
131
186
 
132
- class JsonDataflowsMessage(Struct, frozen=True):
187
+ class JsonDataflowsMessage(Struct, frozen=True, omit_defaults=True):
133
188
  """SDMX-JSON payload for /dataflow queries."""
134
189
 
135
190
  data: JsonDataflows