pysdmx 1.10.0rc1__py3-none-any.whl → 1.10.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.
pysdmx/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Your opinionated Python SDMX library."""
2
2
 
3
- __version__ = "1.10.0rc1"
3
+ __version__ = "1.10.1"
@@ -39,11 +39,13 @@ from pysdmx.model import (
39
39
  Dataflow,
40
40
  DataflowInfo,
41
41
  DataProvider,
42
+ DataStructureDefinition,
42
43
  Hierarchy,
43
44
  HierarchyAssociation,
44
45
  Metadataflow,
45
46
  MetadataProvisionAgreement,
46
47
  MetadataReport,
48
+ MetadataStructure,
47
49
  MultiRepresentationMap,
48
50
  ProvisionAgreement,
49
51
  RepresentationMap,
@@ -678,7 +680,7 @@ class RegistryClient(__BaseRegistryClient):
678
680
  agency: str = "*",
679
681
  id: str = "*",
680
682
  version: str = "+",
681
- ) -> Sequence[Dataflow]:
683
+ ) -> Sequence[DataStructureDefinition]:
682
684
  """Get the data structures(s) matching the supplied parameters.
683
685
 
684
686
  Args:
@@ -767,7 +769,7 @@ class RegistryClient(__BaseRegistryClient):
767
769
  agency: str = "*",
768
770
  id: str = "*",
769
771
  version: str = "+",
770
- ) -> Sequence[Dataflow]:
772
+ ) -> Sequence[MetadataStructure]:
771
773
  """Get the metadata structures (MSD) matching the supplied parameters.
772
774
 
773
775
  Args:
@@ -1252,7 +1254,7 @@ class AsyncRegistryClient(__BaseRegistryClient):
1252
1254
  agency: str = "*",
1253
1255
  id: str = "*",
1254
1256
  version: str = "+",
1255
- ) -> Sequence[Dataflow]:
1257
+ ) -> Sequence[DataStructureDefinition]:
1256
1258
  """Get the data structures(s) matching the supplied parameters.
1257
1259
 
1258
1260
  Args:
@@ -1341,7 +1343,7 @@ class AsyncRegistryClient(__BaseRegistryClient):
1341
1343
  agency: str = "*",
1342
1344
  id: str = "*",
1343
1345
  version: str = "+",
1344
- ) -> Sequence[Dataflow]:
1346
+ ) -> Sequence[MetadataStructure]:
1345
1347
  """Get the metadata structures (MSD) matching the supplied parameters.
1346
1348
 
1347
1349
  Args:
pysdmx/api/qb/data.py CHANGED
@@ -329,6 +329,8 @@ class DataQuery(_CoreDataQuery, frozen=True, omit_defaults=True):
329
329
 
330
330
  def __get_short_v2_qs(self, api_version: ApiVersion) -> str:
331
331
  qs = ""
332
+ if self.components:
333
+ qs += self._create_component_filters(self.components)
332
334
  if self.updated_after:
333
335
  qs = super()._append_qs_param(
334
336
  qs,
@@ -80,8 +80,14 @@ class FusionAttribute(Struct, frozen=True):
80
80
  measureReferences: Optional[Sequence[str]] = None
81
81
 
82
82
  def __derive_level(self, groups: Sequence[FusionGroup]) -> str:
83
- if self.attachmentLevel == "OBSERVATION":
84
- return "O"
83
+ if self.measureReferences:
84
+ if (
85
+ len(self.measureReferences) == 1
86
+ and self.measureReferences[0] == "OBS_VALUE"
87
+ ):
88
+ return "O"
89
+ else:
90
+ return ",".join(self.measureReferences)
85
91
  elif self.attachmentLevel == "DATA_SET":
86
92
  return "D"
87
93
  elif self.attachmentLevel == "GROUP":
@@ -12,6 +12,9 @@ from pysdmx.io.json.sdmxjson2.messages.code import (
12
12
  JsonHierarchyMessage,
13
13
  )
14
14
  from pysdmx.io.json.sdmxjson2.messages.concept import JsonConceptSchemeMessage
15
+ from pysdmx.io.json.sdmxjson2.messages.constraint import (
16
+ JsonDataConstraintMessage,
17
+ )
15
18
  from pysdmx.io.json.sdmxjson2.messages.dataflow import (
16
19
  JsonDataflowMessage,
17
20
  JsonDataflowsMessage,
@@ -50,6 +53,7 @@ __all__ = [
50
53
  "JsonCategorySchemeMessage",
51
54
  "JsonCodelistMessage",
52
55
  "JsonConceptSchemeMessage",
56
+ "JsonDataConstraintMessage",
53
57
  "JsonDataflowMessage",
54
58
  "JsonDataflowsMessage",
55
59
  "JsonDataStructuresMessage",
@@ -231,7 +231,7 @@ class JsonCodelists(Struct, frozen=True, omit_defaults=True):
231
231
  """SDMX-JSON payload for lists of codes."""
232
232
 
233
233
  codelists: Sequence[JsonCodelist] = ()
234
- valuelists: Sequence[JsonValuelist] = ()
234
+ valueLists: Sequence[JsonValuelist] = ()
235
235
 
236
236
 
237
237
  class JsonCodelistMessage(Struct, frozen=True, omit_defaults=True):
@@ -244,7 +244,7 @@ class JsonCodelistMessage(Struct, frozen=True, omit_defaults=True):
244
244
  if self.data.codelists:
245
245
  return self.data.codelists[0].to_model()
246
246
  else:
247
- return self.data.valuelists[0].to_model()
247
+ return self.data.valueLists[0].to_model()
248
248
 
249
249
 
250
250
  class JsonHierarchicalCode(Struct, frozen=True, omit_defaults=True):
@@ -329,10 +329,10 @@ class JsonHierarchicalCode(Struct, frozen=True, omit_defaults=True):
329
329
  code=code.urn,
330
330
  validFrom=code.rel_valid_from,
331
331
  validTo=code.rel_valid_to,
332
- annotations=tuple(annotations),
333
- hierarchicalCodes=[
334
- JsonHierarchicalCode.from_model(c) for c in code.codes
335
- ],
332
+ annotations=tuple(annotations) if annotations else None,
333
+ hierarchicalCodes=tuple(
334
+ [JsonHierarchicalCode.from_model(c) for c in code.codes]
335
+ ),
336
336
  )
337
337
 
338
338
 
@@ -475,6 +475,15 @@ class JsonHierarchyAssociation(
475
475
  "SDMX-JSON hierarchy associations must reference a context",
476
476
  {"hierarchy_association": ha.id},
477
477
  )
478
+ lnk = (
479
+ JsonLink(
480
+ rel="UserDefinedOperator",
481
+ type="sdmx_artefact",
482
+ urn=ha.operator,
483
+ )
484
+ if ha.operator
485
+ else None
486
+ )
478
487
  return JsonHierarchyAssociation(
479
488
  agency=(
480
489
  ha.agency.id if isinstance(ha.agency, Agency) else ha.agency
@@ -492,6 +501,7 @@ class JsonHierarchyAssociation(
492
501
  linkedHierarchy=href,
493
502
  linkedObject=ha.component_ref,
494
503
  contextObject=ha.context_ref,
504
+ links=[lnk] if lnk else (),
495
505
  )
496
506
 
497
507
 
@@ -1,16 +1,43 @@
1
1
  """Collection of SDMX-JSON schemas for content constraints."""
2
2
 
3
- from typing import Dict, Literal, Optional, Sequence
3
+ from datetime import datetime
4
+ from typing import Optional, Sequence
4
5
 
5
6
  from msgspec import Struct
6
7
 
7
- from pysdmx.io.json.sdmxjson2.messages.core import MaintainableType
8
+ from pysdmx import errors
9
+ from pysdmx.io.json.sdmxjson2.messages.core import (
10
+ JsonAnnotation,
11
+ MaintainableType,
12
+ )
13
+ from pysdmx.model import (
14
+ Agency,
15
+ ConstraintAttachment,
16
+ CubeKeyValue,
17
+ CubeRegion,
18
+ CubeValue,
19
+ DataConstraint,
20
+ DataKey,
21
+ DataKeyValue,
22
+ KeySet,
23
+ )
8
24
 
9
25
 
10
26
  class JsonValue(Struct, frozen=True, omit_defaults=True):
11
- """SDMX-JSON payload for an allowed value."""
27
+ """SDMX-JSON payload for a cube value."""
12
28
 
13
29
  value: str
30
+ validFrom: Optional[datetime] = None
31
+ validTo: Optional[datetime] = None
32
+
33
+ def to_model(self) -> CubeValue:
34
+ """Converts a JsonValue to a CubeValue."""
35
+ return CubeValue(self.value, self.validFrom, self.validTo)
36
+
37
+ @classmethod
38
+ def from_model(self, cv: CubeValue) -> "JsonValue":
39
+ """Converts a pysdmx cube value to an SDMX-JSON one."""
40
+ return JsonValue(cv.value, cv.valid_from, cv.valid_to)
14
41
 
15
42
 
16
43
  class JsonKeyValue(Struct, frozen=True, omit_defaults=True):
@@ -18,36 +45,228 @@ class JsonKeyValue(Struct, frozen=True, omit_defaults=True):
18
45
 
19
46
  id: str
20
47
  values: Sequence[JsonValue]
48
+ # Additional properties are supported in the model (include,
49
+ # removePrefix, validFrom, validTo, timeRange) but not by the FMR.
50
+ # Therefore, they are ignored for now.
51
+
52
+ def to_model(self) -> CubeKeyValue:
53
+ """Converts a JsonKeyValue to a CubeKeyValue."""
54
+ return CubeKeyValue(self.id, [v.to_model() for v in self.values])
21
55
 
22
- def to_model(self) -> Sequence[str]:
23
- """Returns the requested list of values."""
24
- return [v.value for v in self.values]
56
+ @classmethod
57
+ def from_model(self, key_value: CubeKeyValue) -> "JsonKeyValue":
58
+ """Converts a pysdmx cube key value to an SDMX-JSON one."""
59
+ return JsonKeyValue(
60
+ key_value.id, [JsonValue.from_model(v) for v in key_value.values]
61
+ )
25
62
 
26
63
 
27
64
  class JsonCubeRegion(Struct, frozen=True, omit_defaults=True):
28
65
  """SDMX-JSON payload for a cube region."""
29
66
 
67
+ # The property `components` is ignored as it's not used in the FMR`
30
68
  keyValues: Sequence[JsonKeyValue]
69
+ include: bool = True
31
70
 
32
- def to_map(self) -> Dict[str, Sequence[str]]:
33
- """Gets the list of allowed values for a component."""
34
- return {kv.id: kv.to_model() for kv in self.keyValues}
71
+ def to_model(self) -> CubeRegion:
72
+ """Converts a JsonCubeRegion to a CubeRegion."""
73
+ return CubeRegion(
74
+ [kv.to_model() for kv in self.keyValues], self.include
75
+ )
76
+
77
+ @classmethod
78
+ def from_model(self, region: CubeRegion) -> "JsonCubeRegion":
79
+ """Converts a pysdmx cube region to an SDMX-JSON one."""
80
+ return JsonCubeRegion(
81
+ [JsonKeyValue.from_model(kv) for kv in region.key_values],
82
+ region.is_included,
83
+ )
35
84
 
36
85
 
37
86
  class JsonConstraintAttachment(Struct, frozen=True, omit_defaults=True):
38
87
  """SDMX-JSON payload for a constraint attachment."""
39
88
 
40
- dataProvider: Optional[str]
41
- simpleDataSources: Optional[Sequence[str]] = None
42
- dataStructures: Optional[Sequence[str]] = None
43
- dataflows: Optional[Sequence[str]] = None
44
- provisionAgreements: Optional[Sequence[str]] = None
45
- queryableDataSources: Optional[Sequence[str]] = None
89
+ dataProvider: Optional[str] = None
90
+ dataStructures: Sequence[str] = ()
91
+ dataflows: Sequence[str] = ()
92
+ provisionAgreements: Sequence[str] = ()
93
+
94
+ def to_model(self) -> ConstraintAttachment:
95
+ """Converts a JsonConstraintAttachment to a ConstraintAttachment."""
96
+ return ConstraintAttachment(
97
+ self.dataProvider,
98
+ self.dataStructures,
99
+ self.dataflows,
100
+ self.provisionAgreements,
101
+ )
102
+
103
+ @classmethod
104
+ def from_model(
105
+ self, attachment: ConstraintAttachment
106
+ ) -> "JsonConstraintAttachment":
107
+ """Converts a pysdmx constraint attachment to an SDMX-JSON one."""
108
+ ds = attachment.data_structures if attachment.data_structures else ()
109
+ df = attachment.dataflows if attachment.dataflows else ()
110
+ pa = (
111
+ attachment.provision_agreements
112
+ if attachment.provision_agreements
113
+ else ()
114
+ )
115
+ return JsonConstraintAttachment(attachment.data_provider, ds, df, pa)
116
+
117
+
118
+ class JsonDataKeyValue(Struct, frozen=True, omit_defaults=True):
119
+ """SDMX-JSON payload for a data key value."""
120
+
121
+ id: str
122
+ value: str
123
+
124
+ def to_model(self) -> DataKeyValue:
125
+ """Converts a JsonDataKeyValue to a DataKeyValue."""
126
+ return DataKeyValue(self.id, self.value)
127
+
128
+ @classmethod
129
+ def from_model(self, kv: DataKeyValue) -> "JsonDataKeyValue":
130
+ """Converts a pysdmx key value to an SDMX-JSON one."""
131
+ return JsonDataKeyValue(kv.id, kv.value)
132
+
133
+
134
+ class JsonDataKey(Struct, frozen=True, omit_defaults=True):
135
+ """SDMX-JSON payload for a data key."""
136
+
137
+ keyValues: Sequence[JsonDataKeyValue]
138
+ validFrom: Optional[datetime] = None
139
+ validTo: Optional[datetime] = None
140
+
141
+ def to_model(self) -> DataKey:
142
+ """Converts a JsonDataKey to a DataKey."""
143
+ return DataKey(
144
+ [kv.to_model() for kv in self.keyValues],
145
+ self.validFrom,
146
+ self.validTo,
147
+ )
148
+
149
+ @classmethod
150
+ def from_model(self, kv: DataKey) -> "JsonDataKey":
151
+ """Converts a pysdmx key constraint to an SDMX-JSON one."""
152
+ return JsonDataKey(
153
+ [JsonDataKeyValue.from_model(val) for val in kv.keys_values],
154
+ kv.valid_from,
155
+ kv.valid_to,
156
+ )
157
+
158
+
159
+ class JsonKeySet(Struct, frozen=True, omit_defaults=True):
160
+ """SDMX-JSON payload for a keyset."""
161
+
162
+ keys: Sequence[JsonDataKey]
163
+ isIncluded: bool
164
+
165
+ def to_model(self) -> KeySet:
166
+ """Converts a JsonKeySet to a KeySet."""
167
+ return KeySet([k.to_model() for k in self.keys], self.isIncluded)
168
+
169
+ @classmethod
170
+ def from_model(self, ks: KeySet) -> "JsonKeySet":
171
+ """Converts a pysdmx key set constraint to an SDMX-JSON one."""
172
+ return JsonKeySet(
173
+ [JsonDataKey.from_model(k) for k in ks.keys], ks.is_included
174
+ )
46
175
 
47
176
 
48
177
  class JsonDataConstraint(MaintainableType, frozen=True, omit_defaults=True):
49
178
  """SDMX-JSON payload for a content constraint."""
50
179
 
51
- role: Optional[Literal["Allowed", "Actual"]] = None
52
180
  constraintAttachment: Optional[JsonConstraintAttachment] = None
53
181
  cubeRegions: Optional[Sequence[JsonCubeRegion]] = None
182
+ dataKeySets: Optional[Sequence[JsonKeySet]] = None
183
+
184
+ def to_model(self) -> DataConstraint:
185
+ """Converts a JsonDataConstraint to a pysdmx Data Constraint."""
186
+ at = self.constraintAttachment.to_model() # type: ignore[union-attr]
187
+ return DataConstraint(
188
+ id=self.id,
189
+ name=self.name,
190
+ agency=self.agency,
191
+ description=self.description,
192
+ version=self.version,
193
+ annotations=tuple([a.to_model() for a in self.annotations]),
194
+ is_external_reference=self.isExternalReference,
195
+ valid_from=self.validFrom,
196
+ valid_to=self.validTo,
197
+ constraint_attachment=at,
198
+ cube_regions=[r.to_model() for r in self.cubeRegions]
199
+ if self.cubeRegions
200
+ else (),
201
+ key_sets=[s.to_model() for s in self.dataKeySets]
202
+ if self.dataKeySets
203
+ else (),
204
+ )
205
+
206
+ @classmethod
207
+ def from_model(self, cons: DataConstraint) -> "JsonDataConstraint":
208
+ """Converts a pysdmx constraint to an SDMX-JSON one."""
209
+ crs = (
210
+ [JsonCubeRegion.from_model(r) for r in cons.cube_regions]
211
+ if cons.cube_regions
212
+ else None
213
+ )
214
+ dks = (
215
+ [JsonKeySet.from_model(s) for s in cons.key_sets]
216
+ if cons.key_sets
217
+ else None
218
+ )
219
+ if not cons.name:
220
+ raise errors.Invalid(
221
+ "Invalid input",
222
+ "SDMX-JSON data constraints must have a name",
223
+ {"data_constraint": cons.id},
224
+ )
225
+ if not cons.constraint_attachment:
226
+ raise errors.Invalid(
227
+ "Invalid input",
228
+ "SDMX-JSON data constraints must have a constraint attachment",
229
+ {"data_constraint": cons.id},
230
+ )
231
+ return JsonDataConstraint(
232
+ id=cons.id,
233
+ name=cons.name,
234
+ agency=(
235
+ cons.agency.id
236
+ if isinstance(cons.agency, Agency)
237
+ else cons.agency
238
+ ),
239
+ description=cons.description,
240
+ version=cons.version,
241
+ annotations=tuple(
242
+ [JsonAnnotation.from_model(a) for a in cons.annotations]
243
+ ),
244
+ isExternalReference=cons.is_external_reference,
245
+ validFrom=cons.valid_from,
246
+ validTo=cons.valid_to,
247
+ constraintAttachment=JsonConstraintAttachment.from_model(
248
+ cons.constraint_attachment
249
+ ),
250
+ cubeRegions=crs,
251
+ dataKeySets=dks,
252
+ )
253
+
254
+
255
+ class JsonDataConstraints(Struct, frozen=True, omit_defaults=True):
256
+ """SDMX-JSON payload for data constraints."""
257
+
258
+ dataConstraints: Sequence[JsonDataConstraint] = ()
259
+
260
+ def to_model(self) -> Sequence[DataConstraint]:
261
+ """Returns the requested data constraints."""
262
+ return [cc.to_model() for cc in self.dataConstraints]
263
+
264
+
265
+ class JsonDataConstraintMessage(Struct, frozen=True, omit_defaults=True):
266
+ """SDMX-JSON payload for /dataconstraint queries."""
267
+
268
+ data: JsonDataConstraints
269
+
270
+ def to_model(self) -> Sequence[DataConstraint]:
271
+ """Returns the requested data constraints."""
272
+ return self.data.to_model()
@@ -126,7 +126,10 @@ class JsonAttributeRelationship(Struct, frozen=True, omit_defaults=True):
126
126
  ) -> str:
127
127
  """Returns the attachment level."""
128
128
  if measures:
129
- return "O"
129
+ if len(measures) == 1 and measures[0] == "OBS_VALUE":
130
+ return "O"
131
+ else:
132
+ return ",".join(measures)
130
133
  elif self.dimensions:
131
134
  return ",".join(self.dimensions)
132
135
  elif self.group:
@@ -136,15 +139,17 @@ class JsonAttributeRelationship(Struct, frozen=True, omit_defaults=True):
136
139
  return "D"
137
140
 
138
141
  @classmethod
139
- def from_model(self, rel: str) -> "JsonAttributeRelationship":
142
+ def from_model(
143
+ self, rel: str, has_measure_rel: bool = False
144
+ ) -> "JsonAttributeRelationship":
140
145
  """Converts a pysdmx attribute relationship to an SDMX-JSON one."""
141
146
  if rel == "D":
142
147
  return JsonAttributeRelationship(dataflow={})
143
- elif rel == "O":
148
+ elif rel == "O" or has_measure_rel:
144
149
  return JsonAttributeRelationship(observation={})
145
150
  else:
146
- dims = rel.split(",")
147
- return JsonAttributeRelationship(dimensions=dims)
151
+ comps = rel.split(",")
152
+ return JsonAttributeRelationship(dimensions=comps)
148
153
 
149
154
 
150
155
  class JsonDimension(Struct, frozen=True, omit_defaults=True):
@@ -257,20 +262,37 @@ class JsonAttribute(Struct, frozen=True, omit_defaults=True):
257
262
  )
258
263
 
259
264
  @classmethod
260
- def from_model(self, attribute: Component) -> "JsonAttribute":
265
+ def from_model(
266
+ self, attribute: Component, measures: Sequence[Component]
267
+ ) -> "JsonAttribute":
261
268
  """Converts a pysdmx attribute to an SDMX-JSON one."""
262
269
  concept = _get_concept_reference(attribute)
263
270
  usage = "mandatory" if attribute.required else "optional"
271
+ repr = _get_json_representation(attribute)
272
+
273
+ ids = attribute.attachment_level.split(",") # type: ignore[union-attr]
274
+ comps = set(ids)
275
+ mids = {m.id for m in measures}
276
+ has_measure_rel = len(comps.intersection(mids)) > 0
264
277
  level = JsonAttributeRelationship.from_model(
265
- attribute.attachment_level # type: ignore[arg-type]
278
+ attribute.attachment_level, # type: ignore[arg-type]
279
+ has_measure_rel,
266
280
  )
267
- repr = _get_json_representation(attribute)
281
+
282
+ if attribute.attachment_level == "O":
283
+ mr = ["OBS_VALUE"]
284
+ elif has_measure_rel:
285
+ mr = ids
286
+ else:
287
+ mr = None
288
+
268
289
  return JsonAttribute(
269
290
  id=attribute.id,
270
291
  conceptIdentity=concept,
271
292
  attributeRelationship=level,
272
293
  usage=usage,
273
294
  localRepresentation=repr,
295
+ measureRelationship=mr,
274
296
  )
275
297
 
276
298
 
@@ -351,12 +373,14 @@ class JsonAttributes(Struct, frozen=True, omit_defaults=True):
351
373
 
352
374
  @classmethod
353
375
  def from_model(
354
- self, attributes: Sequence[Component]
376
+ self, attributes: Sequence[Component], measures: Sequence[Component]
355
377
  ) -> Optional["JsonAttributes"]:
356
378
  """Converts a pysdmx list of attributes to an SDMX-JSON one."""
357
379
  if len(attributes) > 0:
358
380
  return JsonAttributes(
359
- attributes=[JsonAttribute.from_model(a) for a in attributes]
381
+ attributes=[
382
+ JsonAttribute.from_model(a, measures) for a in attributes
383
+ ]
360
384
  )
361
385
  else:
362
386
  return None
@@ -447,19 +471,42 @@ class JsonComponents(Struct, frozen=True, omit_defaults=True):
447
471
  enums = [cl.to_model() for cl in cls]
448
472
  enums.extend([vl.to_model() for vl in vls])
449
473
  comps = []
450
- if constraints and constraints[0].cubeRegions:
451
- cons = constraints[0].cubeRegions[0].to_map()
474
+ if constraints:
475
+ incl_cubes = []
476
+ for const in constraints:
477
+ incl_cubes.extend(
478
+ [cr for cr in (const.cubeRegions or []) if cr.include]
479
+ )
480
+ if len(incl_cubes) == 1:
481
+ cons = {
482
+ kv.id: [v.value for v in kv.values]
483
+ for kv in incl_cubes[0].keyValues
484
+ }
485
+ else:
486
+ cons = {}
452
487
  else:
453
488
  cons = {}
454
- comps.extend(self.dimensionList.to_model(cs, enums, cons))
489
+ comps.extend(
490
+ self.dimensionList.to_model(
491
+ cs,
492
+ enums,
493
+ cons, # type: ignore[arg-type]
494
+ )
495
+ )
455
496
  if self.measureList:
456
- comps.extend(self.measureList.to_model(cs, enums, cons))
497
+ comps.extend(
498
+ self.measureList.to_model(
499
+ cs,
500
+ enums,
501
+ cons, # type: ignore[arg-type]
502
+ )
503
+ )
457
504
  if self.attributeList:
458
505
  comps.extend(
459
506
  self.attributeList.to_model(
460
507
  cs,
461
508
  enums,
462
- cons,
509
+ cons, # type: ignore[arg-type]
463
510
  self.groups,
464
511
  )
465
512
  )
@@ -474,7 +521,9 @@ class JsonComponents(Struct, frozen=True, omit_defaults=True):
474
521
  ) -> "JsonComponents":
475
522
  """Converts a pysdmx components list to an SDMX-JSON one."""
476
523
  dimensions = JsonDimensions.from_model(components.dimensions)
477
- attributes = JsonAttributes.from_model(components.attributes)
524
+ attributes = JsonAttributes.from_model(
525
+ components.attributes, components.measures
526
+ )
478
527
  measures = JsonMeasures.from_model(components.measures)
479
528
  if grps is None:
480
529
  groups = []
@@ -556,7 +605,7 @@ class JsonDataStructures(Struct, frozen=True, omit_defaults=True):
556
605
  conceptSchemes: Sequence[JsonConceptScheme] = ()
557
606
  valuelists: Sequence[JsonValuelist] = ()
558
607
  codelists: Sequence[JsonCodelist] = ()
559
- contentConstraints: Sequence[JsonDataConstraint] = ()
608
+ dataConstraints: Sequence[JsonDataConstraint] = ()
560
609
 
561
610
  def to_model(self) -> Sequence[DataStructureDefinition]:
562
611
  """Returns the requested dsds."""
@@ -565,7 +614,7 @@ class JsonDataStructures(Struct, frozen=True, omit_defaults=True):
565
614
  self.conceptSchemes,
566
615
  self.codelists,
567
616
  self.valuelists,
568
- self.contentConstraints,
617
+ self.dataConstraints,
569
618
  )
570
619
  for dsd in self.dataStructures
571
620
  ]
@@ -87,19 +87,20 @@ class JsonRepresentationMapping(Struct, frozen=True, omit_defaults=True):
87
87
  self, vm: Union[MultiValueMap, ValueMap]
88
88
  ) -> "JsonRepresentationMapping":
89
89
  """Converts a value map to an SDMX-JSON JsonRepresentationMapping."""
90
+ fmt = r"%Y-%m-%dT%H:%M:%S"
90
91
  if isinstance(vm, ValueMap):
91
92
  return JsonRepresentationMapping(
92
93
  [JsonSourceValue.from_model(vm.source)],
93
94
  [vm.target],
94
- vm.valid_from.strftime("%Y-%m-%d") if vm.valid_from else None,
95
- vm.valid_to.strftime("%Y-%m-%d") if vm.valid_to else None,
95
+ vm.valid_from.strftime(fmt) if vm.valid_from else None,
96
+ vm.valid_to.strftime(fmt) if vm.valid_to else None,
96
97
  )
97
98
  else:
98
99
  return JsonRepresentationMapping(
99
100
  [JsonSourceValue.from_model(s) for s in vm.source],
100
101
  vm.target,
101
- vm.valid_from.strftime("%Y-%m-%d") if vm.valid_from else None,
102
- vm.valid_to.strftime("%Y-%m-%d") if vm.valid_to else None,
102
+ vm.valid_from.strftime(fmt) if vm.valid_from else None,
103
+ vm.valid_to.strftime(fmt) if vm.valid_to else None,
103
104
  )
104
105
 
105
106
 
@@ -65,6 +65,7 @@ class JsonMetadataflow(MaintainableType, frozen=True, omit_defaults=True):
65
65
  [JsonAnnotation.from_model(a) for a in df.annotations]
66
66
  ),
67
67
  structure=dsdref,
68
+ targets=df.targets, # type: ignore[arg-type]
68
69
  )
69
70
 
70
71