pysdmx 1.6.0__py3-none-any.whl → 1.8.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 (44) hide show
  1. pysdmx/__init__.py +1 -1
  2. pysdmx/api/fmr/__init__.py +266 -0
  3. pysdmx/api/qb/refmeta.py +1 -1
  4. pysdmx/api/qb/schema.py +4 -10
  5. pysdmx/api/qb/service.py +26 -4
  6. pysdmx/api/qb/structure.py +2 -2
  7. pysdmx/io/input_processor.py +4 -4
  8. pysdmx/io/json/fusion/messages/__init__.py +14 -0
  9. pysdmx/io/json/fusion/messages/concept.py +2 -8
  10. pysdmx/io/json/fusion/messages/dsd.py +52 -1
  11. pysdmx/io/json/fusion/messages/metadataflow.py +44 -0
  12. pysdmx/io/json/fusion/messages/mpa.py +45 -0
  13. pysdmx/io/json/fusion/messages/msd.py +121 -0
  14. pysdmx/io/json/fusion/messages/org.py +90 -0
  15. pysdmx/io/json/fusion/reader/__init__.py +5 -0
  16. pysdmx/io/json/sdmxjson2/messages/__init__.py +15 -1
  17. pysdmx/io/json/sdmxjson2/messages/code.py +35 -13
  18. pysdmx/io/json/sdmxjson2/messages/concept.py +5 -8
  19. pysdmx/io/json/sdmxjson2/messages/dsd.py +9 -6
  20. pysdmx/io/json/sdmxjson2/messages/metadataflow.py +88 -0
  21. pysdmx/io/json/sdmxjson2/messages/mpa.py +88 -0
  22. pysdmx/io/json/sdmxjson2/messages/msd.py +241 -0
  23. pysdmx/io/json/sdmxjson2/messages/provider.py +117 -1
  24. pysdmx/io/json/sdmxjson2/messages/structure.py +25 -1
  25. pysdmx/io/json/sdmxjson2/reader/__init__.py +5 -0
  26. pysdmx/io/serde.py +5 -0
  27. pysdmx/io/writer.py +2 -4
  28. pysdmx/io/xml/__ss_aux_reader.py +1 -2
  29. pysdmx/io/xml/__structure_aux_reader.py +15 -10
  30. pysdmx/io/xml/__structure_aux_writer.py +6 -4
  31. pysdmx/io/xml/__write_data_aux.py +6 -5
  32. pysdmx/io/xml/__write_structure_specific_aux.py +6 -2
  33. pysdmx/io/xml/doc_validation.py +1 -3
  34. pysdmx/io/xml/sdmx21/writer/generic.py +5 -3
  35. pysdmx/model/__init__.py +13 -4
  36. pysdmx/model/dataflow.py +1 -0
  37. pysdmx/model/map.py +6 -6
  38. pysdmx/model/message.py +30 -6
  39. pysdmx/model/metadata.py +255 -4
  40. pysdmx/toolkit/pd/_data_utils.py +3 -4
  41. {pysdmx-1.6.0.dist-info → pysdmx-1.8.0.dist-info}/METADATA +2 -2
  42. {pysdmx-1.6.0.dist-info → pysdmx-1.8.0.dist-info}/RECORD +44 -38
  43. {pysdmx-1.6.0.dist-info → pysdmx-1.8.0.dist-info}/WHEEL +0 -0
  44. {pysdmx-1.6.0.dist-info → pysdmx-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,44 @@
1
+ """Collection of Fusion-JSON schemas for dataflow queries."""
2
+
3
+ from typing import Optional, Sequence
4
+
5
+ from msgspec import Struct
6
+
7
+ from pysdmx.io.json.fusion.messages.core import FusionString
8
+ from pysdmx.model import Metadataflow as MDF
9
+
10
+
11
+ class FusionMetadataflow(Struct, frozen=True, rename={"agency": "agencyId"}):
12
+ """Fusion-JSON payload for a metadataflow."""
13
+
14
+ id: str
15
+ agency: str
16
+ names: Sequence[FusionString]
17
+ metadataStructureRef: str
18
+ targets: Sequence[str]
19
+ descriptions: Optional[Sequence[FusionString]] = None
20
+ version: str = "1.0"
21
+
22
+ def to_model(self) -> MDF:
23
+ """Converts a FusionMetadataflow to a standard metadataflow."""
24
+ return MDF(
25
+ id=self.id,
26
+ agency=self.agency,
27
+ name=self.names[0].value if self.names else None,
28
+ description=(
29
+ self.descriptions[0].value if self.descriptions else None
30
+ ),
31
+ version=self.version,
32
+ structure=self.metadataStructureRef,
33
+ targets=self.targets,
34
+ )
35
+
36
+
37
+ class FusionMetadataflowsMessage(Struct, frozen=True):
38
+ """Fusion-JSON payload for /metadataflow queries."""
39
+
40
+ Metadataflow: Sequence[FusionMetadataflow]
41
+
42
+ def to_model(self) -> Sequence[MDF]:
43
+ """Returns the requested metadataflow details."""
44
+ return [df.to_model() for df in self.Metadataflow]
@@ -0,0 +1,45 @@
1
+ """Collection of Fusion-JSON schemas for provision agreements."""
2
+
3
+ from typing import Optional, Sequence
4
+
5
+ from msgspec import Struct
6
+
7
+ from pysdmx.io.json.fusion.messages.core import FusionString
8
+ from pysdmx.model import MetadataProvisionAgreement as MPA
9
+
10
+
11
+ class FusionMetadataProvisionAgreement(
12
+ Struct, frozen=True, rename={"agency": "agencyId"}
13
+ ):
14
+ """Fusion-JSON payload for a metadata provision agreement."""
15
+
16
+ id: str
17
+ names: Sequence[FusionString]
18
+ agency: str
19
+ metadataflowRef: str
20
+ metadataproviderRef: str
21
+ descriptions: Optional[Sequence[FusionString]] = None
22
+ version: str = "1.0"
23
+
24
+ def to_model(self) -> MPA:
25
+ """Converts a JsonPA to a standard provision agreement."""
26
+ description = self.descriptions[0].value if self.descriptions else None
27
+ return MPA(
28
+ id=self.id,
29
+ name=self.names[0].value,
30
+ agency=self.agency,
31
+ description=description,
32
+ version=self.version,
33
+ metadata_provider=self.metadataproviderRef,
34
+ metadataflow=self.metadataflowRef,
35
+ )
36
+
37
+
38
+ class FusionMetadataProvisionAgreementMessage(Struct, frozen=True):
39
+ """Fusion-JSON payload for /metadataprovisionagreement queries."""
40
+
41
+ MetadataProvisionAgreement: Sequence[FusionMetadataProvisionAgreement]
42
+
43
+ def to_model(self) -> Sequence[MPA]:
44
+ """Returns the requested metadata provision agreements."""
45
+ return [c.to_model() for c in self.MetadataProvisionAgreement]
@@ -0,0 +1,121 @@
1
+ """Collection of Fusion-JSON schemas for SDMX-REST schema queries."""
2
+
3
+ from typing import Literal, Optional, Sequence, Union
4
+
5
+ from msgspec import Struct
6
+
7
+ from pysdmx.io.json.fusion.messages.code import FusionCodelist
8
+ from pysdmx.io.json.fusion.messages.concept import FusionConceptScheme
9
+ from pysdmx.io.json.fusion.messages.core import (
10
+ FusionRepresentation,
11
+ FusionString,
12
+ )
13
+ from pysdmx.io.json.fusion.messages.dsd import (
14
+ _find_concept,
15
+ _get_representation,
16
+ )
17
+ from pysdmx.model import (
18
+ ArrayBoundaries,
19
+ MetadataComponent,
20
+ )
21
+ from pysdmx.model import (
22
+ MetadataStructure as MSD,
23
+ )
24
+
25
+
26
+ class FusionMetadataAttribute(Struct, frozen=True):
27
+ """Fusion-JSON payload for a metadata attribute."""
28
+
29
+ id: str
30
+ concept: str
31
+ minOccurs: int
32
+ maxOccurs: Union[int, Literal["unbounded"]]
33
+ presentational: Optional[bool] = False
34
+ representation: Optional[FusionRepresentation] = None
35
+ metadataAttributes: Sequence["FusionMetadataAttribute"] = ()
36
+
37
+ def to_model(
38
+ self,
39
+ cs: Sequence[FusionConceptScheme],
40
+ cls: Sequence[FusionCodelist],
41
+ ) -> MetadataComponent:
42
+ """Returns an attribute."""
43
+ c = _find_concept(cs, self.concept)
44
+ dt, facets, codes, _ = _get_representation(
45
+ self.id, self.representation, cls, {}
46
+ )
47
+
48
+ if self.representation and self.representation.representation:
49
+ local_enum_ref = self.representation.representation
50
+ else:
51
+ local_enum_ref = None
52
+
53
+ if self.maxOccurs == "unbounded":
54
+ ab = ArrayBoundaries(self.minOccurs)
55
+ elif self.maxOccurs > 1:
56
+ ab = ArrayBoundaries(self.minOccurs, self.maxOccurs)
57
+ else:
58
+ ab = None
59
+
60
+ return MetadataComponent(
61
+ self.id,
62
+ is_presentational=self.presentational, # type: ignore[arg-type]
63
+ concept=c.to_model(cls),
64
+ local_dtype=dt,
65
+ local_facets=facets,
66
+ local_codes=codes,
67
+ array_def=ab,
68
+ local_enum_ref=local_enum_ref,
69
+ components=[
70
+ ma.to_model(cs, cls) for ma in self.metadataAttributes
71
+ ],
72
+ )
73
+
74
+
75
+ class FusionMetadataStructure(
76
+ Struct, frozen=True, rename={"agency": "agencyId"}
77
+ ):
78
+ """Fusion-JSON payload for an MSD."""
79
+
80
+ id: str
81
+ names: Sequence[FusionString]
82
+ agency: str
83
+ descriptions: Optional[Sequence[FusionString]] = None
84
+ version: str = "1.0"
85
+ metadataAttributes: Sequence[FusionMetadataAttribute] = ()
86
+
87
+ def to_model(
88
+ self,
89
+ cs: Sequence[FusionConceptScheme],
90
+ cls: Sequence[FusionCodelist],
91
+ ) -> MSD:
92
+ """Returns the schema for this DSD."""
93
+ return MSD(
94
+ id=self.id,
95
+ agency=self.agency,
96
+ name=self.names[0].value if self.names else None,
97
+ description=(
98
+ self.descriptions[0].value if self.descriptions else None
99
+ ),
100
+ version=self.version,
101
+ components=[a.to_model(cs, cls) for a in self.metadataAttributes],
102
+ )
103
+
104
+
105
+ class FusionMetadataStructuresMessage(Struct, frozen=True):
106
+ """Fusion-JSON payload for /metadatastructure queries."""
107
+
108
+ ConceptScheme: Sequence[FusionConceptScheme]
109
+ MetadataStructure: Sequence[FusionMetadataStructure]
110
+ ValueList: Sequence[FusionCodelist] = ()
111
+ Codelist: Sequence[FusionCodelist] = ()
112
+
113
+ def to_model(self) -> Sequence[MSD]:
114
+ """Returns the requested metadata structures."""
115
+ all_mds = []
116
+ for msd in self.MetadataStructure:
117
+ cls: list[FusionCodelist] = []
118
+ cls.extend(self.Codelist)
119
+ cls.extend(self.ValueList)
120
+ all_mds.append(msd.to_model(self.ConceptScheme, cls))
121
+ return all_mds
@@ -6,12 +6,14 @@ from typing import Dict, Optional, Sequence, Set
6
6
  from msgspec import Struct
7
7
 
8
8
  from pysdmx.io.json.fusion.messages.core import FusionString
9
+ from pysdmx.io.json.fusion.messages.mpa import FusionMetadataProvisionAgreement
9
10
  from pysdmx.io.json.fusion.messages.pa import FusionProvisionAgreement
10
11
  from pysdmx.model import (
11
12
  Agency,
12
13
  Contact,
13
14
  DataflowRef,
14
15
  DataProvider,
16
+ MetadataProvider,
15
17
  )
16
18
  from pysdmx.model import (
17
19
  AgencyScheme as AS,
@@ -19,6 +21,9 @@ from pysdmx.model import (
19
21
  from pysdmx.model import (
20
22
  DataProviderScheme as DPS,
21
23
  )
24
+ from pysdmx.model import (
25
+ MetadataProviderScheme as MDPS,
26
+ )
22
27
  from pysdmx.util import parse_item_urn, parse_urn
23
28
 
24
29
 
@@ -188,3 +193,88 @@ class FusionProviderMessage(Struct, frozen=True):
188
193
  p.to_model(self.ProvisionAgreement)
189
194
  for p in self.DataProviderScheme
190
195
  ]
196
+
197
+
198
+ class FusionMetadataProvider(Struct, frozen=True):
199
+ """Fusion-JSON payload for a metadata provider."""
200
+
201
+ id: str
202
+ names: Sequence[FusionString]
203
+ descriptions: Optional[Sequence[FusionString]] = None
204
+ contacts: Sequence[FusionContact] = ()
205
+
206
+ def to_model(self, owner: Optional[str] = None) -> MetadataProvider:
207
+ """Converts a FusionMetadataProvider to a pysdmx one."""
208
+ d = self.descriptions[0].value if self.descriptions else None
209
+ c = tuple([c.to_model() for c in self.contacts])
210
+ oid = f"{owner}.{self.id}" if owner and owner != "SDMX" else self.id
211
+ return MetadataProvider(
212
+ id=oid, name=self.names[0].value, description=d, contacts=c
213
+ )
214
+
215
+
216
+ class FusionMetadataProviderScheme(Struct, frozen=True):
217
+ """Fusion-JSON payload for a metadata provider scheme."""
218
+
219
+ id: str
220
+ agencyId: str
221
+ names: Sequence[FusionString] = ()
222
+ descriptions: Sequence[FusionString] = ()
223
+ items: Sequence[FusionMetadataProvider] = ()
224
+
225
+ def __get_df_ref(self, ref: str) -> DataflowRef:
226
+ a = parse_urn(ref)
227
+ return DataflowRef(id=a.id, agency=a.agency, version=a.version)
228
+
229
+ def to_model(
230
+ self, pas: Sequence[FusionMetadataProvisionAgreement]
231
+ ) -> MDPS:
232
+ """Converts a FusionProviderScheme to a DataProviderScheme."""
233
+ if pas:
234
+ paprs: Dict[str, Set[DataflowRef]] = defaultdict(set)
235
+ for pa in pas:
236
+ df = self.__get_df_ref(pa.metadataflowRef)
237
+ ref = parse_item_urn(pa.metadataproviderRef)
238
+ paprs[f"{ref.agency}:{ref.item_id}"].add(df)
239
+ prvs = [o.to_model() for o in self.items]
240
+ prvs = [
241
+ MetadataProvider(
242
+ id=p.id,
243
+ name=p.name,
244
+ description=p.description,
245
+ contacts=p.contacts,
246
+ dataflows=list(paprs[f"{self.agencyId}:{p.id}"]),
247
+ )
248
+ for p in prvs
249
+ ]
250
+ return MDPS(
251
+ agency=self.agencyId,
252
+ name=self.names[0].value,
253
+ description=(
254
+ self.descriptions[0].value if self.descriptions else None
255
+ ),
256
+ items=prvs,
257
+ )
258
+ else:
259
+ return MDPS(
260
+ agency=self.agencyId,
261
+ name=self.names[0].value,
262
+ description=(
263
+ self.descriptions[0].value if self.descriptions else None
264
+ ),
265
+ items=[o.to_model() for o in self.items],
266
+ )
267
+
268
+
269
+ class FusionMetadataProviderMessage(Struct, frozen=True):
270
+ """Fusion-JSON payload for /metadataproviderscheme queries."""
271
+
272
+ MetadataProviderScheme: Sequence[FusionMetadataProviderScheme]
273
+ MetadataProvisionAgreement: Sequence[FusionMetadataProvisionAgreement] = ()
274
+
275
+ def to_model(self) -> Sequence[MDPS]:
276
+ """Returns the requested list of metadata providers."""
277
+ return [
278
+ p.to_model(self.MetadataProvisionAgreement)
279
+ for p in self.MetadataProviderScheme
280
+ ]
@@ -20,4 +20,9 @@ deserializers = Deserializers(
20
20
  mapping=msg.FusionMappingMessage, # type: ignore[arg-type]
21
21
  code_map=msg.FusionRepresentationMapMessage, # type: ignore[arg-type]
22
22
  transformation_scheme=msg.FusionTransfoMsg, # type: ignore[arg-type]
23
+ metadataflows=msg.FusionMetadataflowsMessage, # type: ignore[arg-type]
24
+ metadata_provision_agreement=msg.FusionMetadataProvisionAgreementMessage, # type: ignore[arg-type]
25
+ metadata_providers=msg.FusionMetadataProviderMessage, # type: ignore[arg-type]
26
+ msds=msg.FusionMetadataStructuresMessage, # type: ignore[arg-type]
27
+ data_structures=msg.FusionDataStructuresMessage, # type: ignore[arg-type]
23
28
  )
@@ -23,10 +23,20 @@ from pysdmx.io.json.sdmxjson2.messages.map import (
23
23
  JsonRepresentationMapsMessage,
24
24
  JsonStructureMapsMessage,
25
25
  )
26
+ from pysdmx.io.json.sdmxjson2.messages.metadataflow import (
27
+ JsonMetadataflowsMessage as JsonMdfsMsg,
28
+ )
29
+ from pysdmx.io.json.sdmxjson2.messages.mpa import (
30
+ JsonMetadataProvisionAgreementsMessage as JsonMPAMsg,
31
+ )
32
+ from pysdmx.io.json.sdmxjson2.messages.msd import JsonMetadataStructuresMessage
26
33
  from pysdmx.io.json.sdmxjson2.messages.pa import (
27
34
  JsonProvisionAgreementsMessage as JsonPAMessage,
28
35
  )
29
- from pysdmx.io.json.sdmxjson2.messages.provider import JsonProviderMessage
36
+ from pysdmx.io.json.sdmxjson2.messages.provider import (
37
+ JsonMetadataProviderMessage,
38
+ JsonProviderMessage,
39
+ )
30
40
  from pysdmx.io.json.sdmxjson2.messages.report import JsonMetadataMessage
31
41
  from pysdmx.io.json.sdmxjson2.messages.schema import JsonSchemaMessage
32
42
  from pysdmx.io.json.sdmxjson2.messages.structure import JsonStructureMessage
@@ -43,14 +53,18 @@ __all__ = [
43
53
  "JsonDataflowMessage",
44
54
  "JsonDataflowsMessage",
45
55
  "JsonDataStructuresMessage",
56
+ "JsonMetadataProviderMessage",
46
57
  "JsonProviderMessage",
47
58
  "JsonPAMessage",
48
59
  "JsonSchemaMessage",
49
60
  "JsonHierarchyAssociationMessage",
50
61
  "JsonHierarchiesMessage",
51
62
  "JsonHierarchyMessage",
63
+ "JsonMdfsMsg",
52
64
  "JsonMetadataMessage",
65
+ "JsonMPAMsg",
53
66
  "JsonMappingMessage",
67
+ "JsonMetadataStructuresMessage",
54
68
  "JsonRepresentationMapMessage",
55
69
  "JsonRepresentationMapsMessage",
56
70
  "JsonStructureMapsMessage",
@@ -34,7 +34,7 @@ class JsonCode(NameableType, frozen=True, omit_defaults=True):
34
34
  parent: Optional[str] = None
35
35
 
36
36
  def __handle_date(self, datestr: str) -> datetime:
37
- return datetime.strptime(datestr, _VAL_FMT)
37
+ return datetime.strptime(datestr, _VAL_FMT) # noqa
38
38
 
39
39
  def __get_val(
40
40
  self, a: JsonAnnotation
@@ -49,26 +49,38 @@ class JsonCode(NameableType, frozen=True, omit_defaults=True):
49
49
 
50
50
  def to_model(self) -> Code:
51
51
  """Converts a JsonCode to a standard code."""
52
+ # Pre-filter annotations once outside the tuple creation
53
+ vf, vt = None, None
54
+
52
55
  if self.annotations:
53
- vp = [
54
- a for a in self.annotations if a.type == "FR_VALIDITY_PERIOD"
56
+ # Get validity period info
57
+ vp = next(
58
+ (
59
+ a
60
+ for a in self.annotations
61
+ if a.type == "FR_VALIDITY_PERIOD"
62
+ ),
63
+ None,
64
+ )
65
+ if vp:
66
+ vf, vt = self.__get_val(vp)
67
+
68
+ # Pre-filter non-validity period annotations
69
+ filtered_annotations = [
70
+ a.to_model()
71
+ for a in self.annotations
72
+ if a.type != "FR_VALIDITY_PERIOD"
55
73
  ]
56
74
  else:
57
- vp = None
58
- vf, vt = self.__get_val(vp[0]) if vp else (None, None)
75
+ filtered_annotations = []
76
+
59
77
  return Code(
60
78
  id=self.id,
61
79
  name=self.name,
62
80
  description=self.description,
63
81
  valid_from=vf,
64
82
  valid_to=vt,
65
- annotations=tuple(
66
- [
67
- a.to_model()
68
- for a in self.annotations
69
- if a.type != "FR_VALIDITY_PERIOD"
70
- ]
71
- ),
83
+ annotations=tuple(filtered_annotations),
72
84
  )
73
85
 
74
86
  @classmethod
@@ -113,13 +125,23 @@ class JsonCodelist(ItemSchemeType, frozen=True, omit_defaults=True):
113
125
 
114
126
  def to_model(self) -> Codelist:
115
127
  """Converts a JsonCodelist to a standard codelist."""
128
+ # Process codes in batches to reduce memory pressure
129
+ batch_size = 10000
130
+ all_codes = []
131
+
132
+ # Process in batches
133
+ for i in range(0, len(self.codes), batch_size):
134
+ batch = self.codes[i : i + batch_size]
135
+ batch_models = [code.to_model() for code in batch]
136
+ all_codes.extend(batch_models)
137
+
116
138
  return Codelist(
117
139
  id=self.id,
118
140
  name=self.name,
119
141
  agency=self.agency,
120
142
  description=self.description,
121
143
  version=self.version,
122
- items=tuple([i.to_model() for i in self.codes]),
144
+ items=tuple(all_codes),
123
145
  annotations=tuple([a.to_model() for a in self.annotations]),
124
146
  is_external_reference=self.isExternalReference,
125
147
  is_partial=self.isPartial,
@@ -9,6 +9,7 @@ from pysdmx.io.json.sdmxjson2.messages.code import JsonCodelist
9
9
  from pysdmx.io.json.sdmxjson2.messages.core import (
10
10
  ItemSchemeType,
11
11
  JsonAnnotation,
12
+ JsonLink,
12
13
  JsonRepresentation,
13
14
  NameableType,
14
15
  )
@@ -29,6 +30,7 @@ class JsonConcept(NameableType, frozen=True, omit_defaults=True):
29
30
  coreRepresentation: Optional[JsonRepresentation] = None
30
31
  parent: Optional[str] = None
31
32
  isoConceptReference: Optional[IsoConceptReference] = None
33
+ links: Sequence[JsonLink] = ()
32
34
 
33
35
  def to_model(self, codelists: Sequence[Codelist]) -> Concept:
34
36
  """Converts a JsonConcept to a standard concept."""
@@ -48,6 +50,8 @@ class JsonConcept(NameableType, frozen=True, omit_defaults=True):
48
50
  facets = None
49
51
  codes = None
50
52
  cl_ref = None
53
+ urn_lnk = [lnk for lnk in self.links if lnk.rel == "self"]
54
+ c_urn = urn_lnk[0].urn if len(urn_lnk) > 0 else None
51
55
  return Concept(
52
56
  id=self.id,
53
57
  dtype=dt,
@@ -56,6 +60,7 @@ class JsonConcept(NameableType, frozen=True, omit_defaults=True):
56
60
  description=self.description,
57
61
  codes=codes,
58
62
  enum_ref=cl_ref,
63
+ urn=c_urn,
59
64
  )
60
65
 
61
66
  @classmethod
@@ -95,18 +100,10 @@ class JsonConceptScheme(ItemSchemeType, frozen=True, omit_defaults=True):
95
100
 
96
101
  concepts: Sequence[JsonConcept] = ()
97
102
 
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
-
105
103
  def to_model(self, codelists: Sequence[JsonCodelist]) -> ConceptScheme:
106
104
  """Converts a JsonConceptScheme to a standard concept scheme."""
107
105
  cls = [cl.to_model() for cl in codelists]
108
106
  concepts = [c.to_model(cls) for c in self.concepts]
109
- concepts = [self.__set_urn(c) for c in concepts]
110
107
  return ConceptScheme(
111
108
  id=self.id,
112
109
  name=self.name,
@@ -1,6 +1,6 @@
1
- """Collection of SDMX-JSON schemas for SDMX-REST schema queries."""
1
+ """Collection of SDMX-JSON schemas for SDMX-REST DSD queries."""
2
2
 
3
- from typing import Dict, List, Literal, Optional, Sequence, Tuple
3
+ from typing import Dict, List, Literal, Optional, Sequence, Tuple, Union
4
4
 
5
5
  from msgspec import Struct
6
6
 
@@ -27,6 +27,7 @@ from pysdmx.model import (
27
27
  DataType,
28
28
  Facets,
29
29
  ItemReference,
30
+ MetadataComponent,
30
31
  Role,
31
32
  )
32
33
  from pysdmx.model.dataflow import Group
@@ -43,7 +44,7 @@ def _find_concept(cs: Sequence[JsonConceptScheme], urn: str) -> JsonConcept:
43
44
  return [c for c in f[0].concepts if c.id == r.item_id][0]
44
45
 
45
46
 
46
- def __get_type(repr_: JsonRepresentation) -> Optional[str]:
47
+ def _get_type(repr_: JsonRepresentation) -> Optional[str]:
47
48
  t: Optional[str] = None
48
49
  if repr_.enumerationFormat:
49
50
  t = repr_.enumerationFormat.dataType
@@ -67,13 +68,15 @@ def _get_representation(
67
68
  ]:
68
69
  valid = cons.get(id_, [])
69
70
  codes = local.to_enumeration(cls, valid) if local else None
70
- dt = DataType(__get_type(local)) if local else None
71
+ dt = DataType(_get_type(local)) if local else None
71
72
  facets = local.to_facets() if local else None
72
73
  ab = local.to_array_def() if local else None
73
74
  return (dt, facets, codes, ab)
74
75
 
75
76
 
76
- def _get_concept_reference(component: Component) -> str:
77
+ def _get_concept_reference(
78
+ component: Union[Component, MetadataComponent],
79
+ ) -> str:
77
80
  if isinstance(component.concept, ItemReference):
78
81
  concept = (
79
82
  "urn:sdmx:org.sdmx.infomodel.conceptscheme."
@@ -95,7 +98,7 @@ def _get_concept_reference(component: Component) -> str:
95
98
 
96
99
 
97
100
  def _get_json_representation(
98
- comp: Component,
101
+ comp: Union[Component, MetadataComponent],
99
102
  ) -> Optional[JsonRepresentation]:
100
103
  enum = comp.local_enum_ref if comp.local_enum_ref else None
101
104
  return JsonRepresentation.from_model(
@@ -0,0 +1,88 @@
1
+ """Collection of SDMX-JSON schemas for dataflow queries."""
2
+
3
+ from typing import Sequence
4
+
5
+ from msgspec import Struct
6
+
7
+ from pysdmx import errors
8
+ from pysdmx.io.json.sdmxjson2.messages.core import (
9
+ JsonAnnotation,
10
+ MaintainableType,
11
+ )
12
+ from pysdmx.model import Agency, Metadataflow, MetadataStructure
13
+
14
+
15
+ class JsonMetadataflow(MaintainableType, frozen=True, omit_defaults=True):
16
+ """SDMX-JSON payload for a metadataflow."""
17
+
18
+ structure: str = ""
19
+ targets: Sequence[str] = ()
20
+
21
+ def to_model(self) -> Metadataflow:
22
+ """Converts a FusionMetadataflow to a standard one."""
23
+ return Metadataflow(
24
+ id=self.id,
25
+ agency=self.agency,
26
+ name=self.name,
27
+ description=self.description,
28
+ version=self.version,
29
+ structure=self.structure,
30
+ targets=self.targets,
31
+ annotations=[a.to_model() for a in self.annotations],
32
+ is_external_reference=self.isExternalReference,
33
+ valid_from=self.validFrom,
34
+ valid_to=self.validTo,
35
+ )
36
+
37
+ @classmethod
38
+ def from_model(self, df: Metadataflow) -> "JsonMetadataflow":
39
+ """Converts a pysdmx metadataflow to an SDMX-JSON one."""
40
+ if not df.name:
41
+ raise errors.Invalid(
42
+ "Invalid input",
43
+ "SDMX-JSON metadataflows must have a name",
44
+ {"metadataflow": df.id},
45
+ )
46
+ if isinstance(df.structure, MetadataStructure):
47
+ dsdref = (
48
+ "urn:sdmx:org.sdmx.infomodel.metadatastructure.MetadataStructure="
49
+ f"{df.structure.agency}:{df.structure.id}({df.structure.version})"
50
+ )
51
+ else:
52
+ dsdref = df.structure # type: ignore[assignment]
53
+ return JsonMetadataflow(
54
+ agency=(
55
+ df.agency.id if isinstance(df.agency, Agency) else df.agency
56
+ ),
57
+ id=df.id,
58
+ name=df.name,
59
+ version=df.version,
60
+ isExternalReference=df.is_external_reference,
61
+ validFrom=df.valid_from,
62
+ validTo=df.valid_to,
63
+ description=df.description,
64
+ annotations=tuple(
65
+ [JsonAnnotation.from_model(a) for a in df.annotations]
66
+ ),
67
+ structure=dsdref,
68
+ )
69
+
70
+
71
+ class JsonMetadataflows(Struct, frozen=True, omit_defaults=True):
72
+ """SDMX-JSON payload for the list of metadataflows."""
73
+
74
+ metadataflows: Sequence[JsonMetadataflow]
75
+
76
+ def to_model(self) -> Sequence[Metadataflow]:
77
+ """Returns the requested metadataflows."""
78
+ return [mdf.to_model() for mdf in self.metadataflows]
79
+
80
+
81
+ class JsonMetadataflowsMessage(Struct, frozen=True, omit_defaults=True):
82
+ """SDMX-JSON payload for /metadataflow queries."""
83
+
84
+ data: JsonMetadataflows
85
+
86
+ def to_model(self) -> Sequence[Metadataflow]:
87
+ """Returns the requested dataflows."""
88
+ return self.data.to_model()