pysdmx 1.10.0rc1__py3-none-any.whl → 1.10.0rc2__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 (33) hide show
  1. pysdmx/__init__.py +1 -1
  2. pysdmx/api/fmr/__init__.py +3 -2
  3. pysdmx/io/_pd_utils.py +83 -0
  4. pysdmx/io/csv/__csv_aux_writer.py +23 -0
  5. pysdmx/io/csv/sdmx10/reader/__init__.py +1 -1
  6. pysdmx/io/csv/sdmx10/writer/__init__.py +15 -9
  7. pysdmx/io/csv/sdmx20/reader/__init__.py +1 -1
  8. pysdmx/io/csv/sdmx20/writer/__init__.py +1 -1
  9. pysdmx/io/csv/sdmx21/reader/__init__.py +1 -1
  10. pysdmx/io/csv/sdmx21/writer/__init__.py +1 -1
  11. pysdmx/io/json/sdmxjson2/messages/__init__.py +4 -0
  12. pysdmx/io/json/sdmxjson2/messages/code.py +16 -6
  13. pysdmx/io/json/sdmxjson2/messages/constraint.py +235 -16
  14. pysdmx/io/json/sdmxjson2/messages/dsd.py +35 -7
  15. pysdmx/io/json/sdmxjson2/messages/map.py +5 -4
  16. pysdmx/io/json/sdmxjson2/messages/metadataflow.py +1 -0
  17. pysdmx/io/json/sdmxjson2/messages/msd.py +18 -10
  18. pysdmx/io/json/sdmxjson2/messages/schema.py +2 -2
  19. pysdmx/io/json/sdmxjson2/messages/structure.py +81 -44
  20. pysdmx/io/json/sdmxjson2/messages/vtl.py +13 -9
  21. pysdmx/io/xml/__write_data_aux.py +20 -7
  22. pysdmx/io/xml/__write_structure_specific_aux.py +71 -54
  23. pysdmx/io/xml/sdmx21/writer/generic.py +31 -19
  24. pysdmx/model/__base.py +46 -1
  25. pysdmx/model/__init__.py +18 -0
  26. pysdmx/model/category.py +17 -0
  27. pysdmx/model/concept.py +16 -0
  28. pysdmx/model/constraint.py +69 -0
  29. pysdmx/model/message.py +80 -71
  30. {pysdmx-1.10.0rc1.dist-info → pysdmx-1.10.0rc2.dist-info}/METADATA +1 -1
  31. {pysdmx-1.10.0rc1.dist-info → pysdmx-1.10.0rc2.dist-info}/RECORD +33 -31
  32. {pysdmx-1.10.0rc1.dist-info → pysdmx-1.10.0rc2.dist-info}/WHEEL +0 -0
  33. {pysdmx-1.10.0rc1.dist-info → pysdmx-1.10.0rc2.dist-info}/licenses/LICENSE +0 -0
@@ -265,12 +265,17 @@ class JsonAttribute(Struct, frozen=True, omit_defaults=True):
265
265
  attribute.attachment_level # type: ignore[arg-type]
266
266
  )
267
267
  repr = _get_json_representation(attribute)
268
+ # The line below will need to be changed when we work on
269
+ # Measure Relationship (cf. issue #467)
270
+ mr = ["OBS_VALUE"] if attribute.attachment_level == "O" else None
271
+
268
272
  return JsonAttribute(
269
273
  id=attribute.id,
270
274
  conceptIdentity=concept,
271
275
  attributeRelationship=level,
272
276
  usage=usage,
273
277
  localRepresentation=repr,
278
+ measureRelationship=mr,
274
279
  )
275
280
 
276
281
 
@@ -447,19 +452,42 @@ class JsonComponents(Struct, frozen=True, omit_defaults=True):
447
452
  enums = [cl.to_model() for cl in cls]
448
453
  enums.extend([vl.to_model() for vl in vls])
449
454
  comps = []
450
- if constraints and constraints[0].cubeRegions:
451
- cons = constraints[0].cubeRegions[0].to_map()
455
+ if constraints:
456
+ incl_cubes = []
457
+ for const in constraints:
458
+ incl_cubes.extend(
459
+ [cr for cr in (const.cubeRegions or []) if cr.include]
460
+ )
461
+ if len(incl_cubes) == 1:
462
+ cons = {
463
+ kv.id: [v.value for v in kv.values]
464
+ for kv in incl_cubes[0].keyValues
465
+ }
466
+ else:
467
+ cons = {}
452
468
  else:
453
469
  cons = {}
454
- comps.extend(self.dimensionList.to_model(cs, enums, cons))
470
+ comps.extend(
471
+ self.dimensionList.to_model(
472
+ cs,
473
+ enums,
474
+ cons, # type: ignore[arg-type]
475
+ )
476
+ )
455
477
  if self.measureList:
456
- comps.extend(self.measureList.to_model(cs, enums, cons))
478
+ comps.extend(
479
+ self.measureList.to_model(
480
+ cs,
481
+ enums,
482
+ cons, # type: ignore[arg-type]
483
+ )
484
+ )
457
485
  if self.attributeList:
458
486
  comps.extend(
459
487
  self.attributeList.to_model(
460
488
  cs,
461
489
  enums,
462
- cons,
490
+ cons, # type: ignore[arg-type]
463
491
  self.groups,
464
492
  )
465
493
  )
@@ -556,7 +584,7 @@ class JsonDataStructures(Struct, frozen=True, omit_defaults=True):
556
584
  conceptSchemes: Sequence[JsonConceptScheme] = ()
557
585
  valuelists: Sequence[JsonValuelist] = ()
558
586
  codelists: Sequence[JsonCodelist] = ()
559
- contentConstraints: Sequence[JsonDataConstraint] = ()
587
+ dataConstraints: Sequence[JsonDataConstraint] = ()
560
588
 
561
589
  def to_model(self) -> Sequence[DataStructureDefinition]:
562
590
  """Returns the requested dsds."""
@@ -565,7 +593,7 @@ class JsonDataStructures(Struct, frozen=True, omit_defaults=True):
565
593
  self.conceptSchemes,
566
594
  self.codelists,
567
595
  self.valuelists,
568
- self.contentConstraints,
596
+ self.dataConstraints,
569
597
  )
570
598
  for dsd in self.dataStructures
571
599
  ]
@@ -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
 
@@ -15,7 +15,6 @@ from pysdmx.io.json.sdmxjson2.messages.core import (
15
15
  from pysdmx.io.json.sdmxjson2.messages.dsd import (
16
16
  _find_concept,
17
17
  _get_concept_reference,
18
- _get_json_representation,
19
18
  _get_representation,
20
19
  )
21
20
  from pysdmx.model import (
@@ -28,6 +27,13 @@ from pysdmx.model import (
28
27
  from pysdmx.util import parse_item_urn
29
28
 
30
29
 
30
+ def _get_attr_repr(comp: MetadataComponent) -> Optional[JsonRepresentation]:
31
+ enum = comp.local_enum_ref if comp.local_enum_ref else None
32
+ return JsonRepresentation.from_model(
33
+ comp.local_dtype, enum, comp.local_facets, None
34
+ )
35
+
36
+
31
37
  class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
32
38
  """SDMX-JSON payload for an attribute."""
33
39
 
@@ -80,11 +86,13 @@ class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
80
86
  def from_model(self, cmp: MetadataComponent) -> "JsonMetadataAttribute":
81
87
  """Converts a pysdmx metadata attribute to an SDMX-JSON one."""
82
88
  concept = _get_concept_reference(cmp)
83
- repr = _get_json_representation(cmp)
89
+ repr = _get_attr_repr(cmp)
84
90
 
85
91
  min_occurs = cmp.array_def.min_size if cmp.array_def else 0
86
- if cmp.array_def is None or cmp.array_def.max_size is None:
87
- max_occurs: Union[int, Literal["unbounded"]] = "unbounded"
92
+ if cmp.array_def is None:
93
+ max_occurs: Union[int, Literal["unbounded"]] = 1
94
+ elif cmp.array_def.max_size is None:
95
+ max_occurs = "unbounded"
88
96
  else:
89
97
  max_occurs = cmp.array_def.max_size
90
98
 
@@ -95,9 +103,9 @@ class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
95
103
  minOccurs=min_occurs,
96
104
  maxOccurs=max_occurs,
97
105
  isPresentational=cmp.is_presentational,
98
- metadataAttributes=[
99
- JsonMetadataAttribute.from_model(c) for c in cmp.components
100
- ],
106
+ metadataAttributes=tuple(
107
+ [JsonMetadataAttribute.from_model(c) for c in cmp.components]
108
+ ),
101
109
  )
102
110
 
103
111
 
@@ -119,9 +127,9 @@ class JsonMetadataAttributes(Struct, frozen=True, omit_defaults=True):
119
127
  ) -> "JsonMetadataAttributes":
120
128
  """Converts a pysdmx list of metadata attributes to SDMX-JSON."""
121
129
  return JsonMetadataAttributes(
122
- metadataAttributes=[
123
- JsonMetadataAttribute.from_model(a) for a in attributes
124
- ]
130
+ metadataAttributes=tuple(
131
+ [JsonMetadataAttribute.from_model(a) for a in attributes]
132
+ )
125
133
  )
126
134
 
127
135
 
@@ -21,7 +21,7 @@ class JsonSchemas(msgspec.Struct, frozen=True, omit_defaults=True):
21
21
  dataStructures: Sequence[JsonDataStructure]
22
22
  valuelists: Sequence[JsonValuelist] = ()
23
23
  codelists: Sequence[JsonCodelist] = ()
24
- contentConstraints: Sequence[JsonDataConstraint] = ()
24
+ dataConstraints: Sequence[JsonDataConstraint] = ()
25
25
 
26
26
  def to_model(
27
27
  self,
@@ -32,7 +32,7 @@ class JsonSchemas(msgspec.Struct, frozen=True, omit_defaults=True):
32
32
  self.conceptSchemes,
33
33
  self.codelists,
34
34
  self.valuelists,
35
- self.contentConstraints,
35
+ self.dataConstraints,
36
36
  )
37
37
  return comps, grps # type: ignore[return-value]
38
38
 
@@ -17,6 +17,7 @@ from pysdmx.io.json.sdmxjson2.messages.code import (
17
17
  JsonValuelist,
18
18
  )
19
19
  from pysdmx.io.json.sdmxjson2.messages.concept import JsonConceptScheme
20
+ from pysdmx.io.json.sdmxjson2.messages.constraint import JsonDataConstraint
20
21
  from pysdmx.io.json.sdmxjson2.messages.core import JsonHeader
21
22
  from pysdmx.io.json.sdmxjson2.messages.dataflow import JsonDataflow
22
23
  from pysdmx.io.json.sdmxjson2.messages.dsd import JsonDataStructure
@@ -49,90 +50,95 @@ from pysdmx.model.message import StructureMessage
49
50
  class JsonStructures(Struct, frozen=True, omit_defaults=True):
50
51
  """The allowed strutures."""
51
52
 
52
- dataStructures: Sequence[JsonDataStructure] = ()
53
+ agencySchemes: Sequence[JsonAgencyScheme] = ()
54
+ categorisations: Sequence[JsonCategorisation] = ()
53
55
  categorySchemes: Sequence[JsonCategoryScheme] = ()
54
- conceptSchemes: Sequence[JsonConceptScheme] = ()
55
56
  codelists: Sequence[JsonCodelist] = ()
56
- valueLists: Sequence[JsonValuelist] = ()
57
+ conceptSchemes: Sequence[JsonConceptScheme] = ()
58
+ customTypeSchemes: Sequence[JsonCustomTypeScheme] = ()
59
+ dataConstraints: Sequence[JsonDataConstraint] = ()
60
+ dataflows: Sequence[JsonDataflow] = ()
61
+ dataProviderSchemes: Sequence[JsonDataProviderScheme] = ()
62
+ dataStructures: Sequence[JsonDataStructure] = ()
57
63
  hierarchies: Sequence[JsonHierarchy] = ()
58
64
  hierarchyAssociations: Sequence[JsonHierarchyAssociation] = ()
59
- agencySchemes: Sequence[JsonAgencyScheme] = ()
60
- dataProviderSchemes: Sequence[JsonDataProviderScheme] = ()
61
- metadataProviderSchemes: Sequence[JsonMetadataProviderScheme] = ()
62
- dataflows: Sequence[JsonDataflow] = ()
63
- provisionAgreements: Sequence[JsonProvisionAgreement] = ()
64
65
  metadataflows: Sequence[JsonMetadataflow] = ()
66
+ metadataProviderSchemes: Sequence[JsonMetadataProviderScheme] = ()
65
67
  metadataProvisionAgreements: Sequence[JsonMetadataProvisionAgreement] = ()
66
68
  metadataStructures: Sequence[JsonMetadataStructure] = ()
67
- structureMaps: Sequence[JsonStructureMap] = ()
68
- representationMaps: Sequence[JsonRepresentationMap] = ()
69
- categorisations: Sequence[JsonCategorisation] = ()
70
- customTypeSchemes: Sequence[JsonCustomTypeScheme] = ()
71
- vtlMappingSchemes: Sequence[JsonVtlMappingScheme] = ()
72
69
  namePersonalisationSchemes: Sequence[JsonNamePersonalisationScheme] = ()
70
+ provisionAgreements: Sequence[JsonProvisionAgreement] = ()
71
+ representationMaps: Sequence[JsonRepresentationMap] = ()
73
72
  rulesetSchemes: Sequence[JsonRulesetScheme] = ()
73
+ structureMaps: Sequence[JsonStructureMap] = ()
74
74
  transformationSchemes: Sequence[JsonTransformationScheme] = ()
75
75
  userDefinedOperatorSchemes: Sequence[JsonUserDefinedOperatorScheme] = ()
76
+ valueLists: Sequence[JsonValuelist] = ()
77
+ vtlMappingSchemes: Sequence[JsonVtlMappingScheme] = ()
76
78
 
77
79
  def to_model(self) -> Sequence[MaintainableArtefact]:
78
80
  """Map to pysdmx artefacts."""
79
81
  structures = [] # type: ignore[var-annotated]
80
82
  structures.extend(
81
- i.to_model(
82
- self.conceptSchemes, self.codelists, self.valueLists, ()
83
- )
84
- for i in self.dataStructures
83
+ i.to_model(self.dataflows) for i in self.agencySchemes
85
84
  )
85
+ structures.extend(i.to_model() for i in self.categorisations)
86
86
  structures.extend(i.to_model() for i in self.categorySchemes)
87
- structures.extend(
88
- i.to_model(self.codelists) for i in self.conceptSchemes
89
- )
90
87
  structures.extend(i.to_model() for i in self.codelists)
91
- structures.extend(i.to_model() for i in self.valueLists)
92
- structures.extend(i.to_model(self.codelists) for i in self.hierarchies)
93
88
  structures.extend(
94
- i.to_model(self.hierarchies, self.codelists)
95
- for i in self.hierarchyAssociations
89
+ i.to_model(self.codelists) for i in self.conceptSchemes
96
90
  )
91
+ structures.extend(i.to_model() for i in self.customTypeSchemes)
92
+ structures.extend(i.to_model() for i in self.dataConstraints)
97
93
  structures.extend(
98
- i.to_model(self.dataflows) for i in self.agencySchemes
94
+ i.to_model(
95
+ self.dataStructures,
96
+ self.conceptSchemes,
97
+ self.valueLists,
98
+ self.codelists,
99
+ )
100
+ for i in self.dataflows
99
101
  )
100
102
  structures.extend(
101
103
  i.to_model(self.provisionAgreements)
102
104
  for i in self.dataProviderSchemes
103
105
  )
106
+ structures.extend(
107
+ i.to_model(
108
+ self.conceptSchemes, self.codelists, self.valueLists, ()
109
+ )
110
+ for i in self.dataStructures
111
+ )
112
+ structures.extend(i.to_model(self.codelists) for i in self.hierarchies)
113
+ structures.extend(
114
+ i.to_model(self.hierarchies, self.codelists)
115
+ for i in self.hierarchyAssociations
116
+ )
117
+
118
+ structures.extend(i.to_model() for i in self.metadataflows)
104
119
  structures.extend(
105
120
  i.to_model(self.metadataProvisionAgreements)
106
121
  for i in self.metadataProviderSchemes
107
122
  )
123
+ structures.extend(
124
+ i.to_model() for i in self.metadataProvisionAgreements
125
+ )
108
126
  structures.extend(
109
127
  i.to_model(self.conceptSchemes, self.codelists, self.valueLists)
110
128
  for i in self.metadataStructures
111
129
  )
112
130
  structures.extend(
113
- i.to_model(
114
- self.dataStructures,
115
- self.conceptSchemes,
116
- self.valueLists,
117
- self.codelists,
118
- )
119
- for i in self.dataflows
131
+ i.to_model() for i in self.namePersonalisationSchemes
120
132
  )
121
133
  structures.extend(i.to_model() for i in self.provisionAgreements)
122
- structures.extend(i.to_model() for i in self.metadataflows)
123
134
  structures.extend(
124
- i.to_model() for i in self.metadataProvisionAgreements
135
+ i.to_model(bool(len(i.source) > 1 or len(i.target) > 1))
136
+ for i in self.representationMaps
125
137
  )
138
+ structures.extend(i.to_model() for i in self.rulesetSchemes)
126
139
  structures.extend(
127
140
  i.to_model(self.representationMaps) for i in self.structureMaps
128
141
  )
129
- structures.extend(i.to_model() for i in self.categorisations)
130
- structures.extend(i.to_model() for i in self.customTypeSchemes)
131
- structures.extend(i.to_model() for i in self.vtlMappingSchemes)
132
- structures.extend(
133
- i.to_model() for i in self.namePersonalisationSchemes
134
- )
135
- structures.extend(i.to_model() for i in self.rulesetSchemes)
136
142
  structures.extend(
137
143
  i.to_model(
138
144
  self.customTypeSchemes,
@@ -146,9 +152,8 @@ class JsonStructures(Struct, frozen=True, omit_defaults=True):
146
152
  structures.extend(
147
153
  i.to_model() for i in self.userDefinedOperatorSchemes
148
154
  )
149
- for rm in self.representationMaps:
150
- multi = bool(len(rm.source) > 1 or len(rm.target) > 1)
151
- structures.append(rm.to_model(multi))
155
+ structures.extend(i.to_model() for i in self.valueLists)
156
+ structures.extend(i.to_model() for i in self.vtlMappingSchemes)
152
157
  return structures
153
158
 
154
159
  @classmethod
@@ -264,6 +269,33 @@ class JsonStructures(Struct, frozen=True, omit_defaults=True):
264
269
  hierarchies = tuple(
265
270
  [JsonHierarchy.from_model(h) for h in msg.get_hierarchies()]
266
271
  )
272
+ constraints = tuple(
273
+ [
274
+ JsonDataConstraint.from_model(c)
275
+ for c in msg.get_data_constraints()
276
+ ]
277
+ )
278
+ mpas = tuple(
279
+ [
280
+ JsonMetadataProvisionAgreement.from_model(c)
281
+ for c in msg.get_metadata_provision_agreements()
282
+ ]
283
+ )
284
+ mprvs = tuple(
285
+ [
286
+ JsonMetadataProviderScheme.from_model(c)
287
+ for c in msg.get_metadata_provider_schemes()
288
+ ]
289
+ )
290
+ mdfs = tuple(
291
+ [JsonMetadataflow.from_model(c) for c in msg.get_metadataflows()]
292
+ )
293
+ msds = tuple(
294
+ [
295
+ JsonMetadataStructure.from_model(c)
296
+ for c in msg.get_metadata_structures()
297
+ ]
298
+ )
267
299
  return JsonStructures(
268
300
  agencySchemes=agencies,
269
301
  categorisations=categorisations,
@@ -271,11 +303,16 @@ class JsonStructures(Struct, frozen=True, omit_defaults=True):
271
303
  codelists=codelists,
272
304
  conceptSchemes=concept_schemes,
273
305
  customTypeSchemes=custom_types,
306
+ dataConstraints=constraints,
274
307
  dataflows=dataflows,
275
308
  dataProviderSchemes=data_providers,
276
309
  dataStructures=data_structures,
277
310
  hierarchies=hierarchies,
278
311
  hierarchyAssociations=hier_associations,
312
+ metadataflows=mdfs,
313
+ metadataProviderSchemes=mprvs,
314
+ metadataProvisionAgreements=mpas,
315
+ metadataStructures=msds,
279
316
  namePersonalisationSchemes=name_personalisations,
280
317
  provisionAgreements=agreements,
281
318
  representationMaps=representations_maps,
@@ -1,6 +1,6 @@
1
1
  """Collection of SDMX-JSON schemas for VTL artefacts."""
2
2
 
3
- from typing import Literal, Optional, Sequence
3
+ from typing import Dict, Literal, Optional, Sequence
4
4
 
5
5
  from msgspec import Struct
6
6
 
@@ -506,33 +506,37 @@ class JsonRulesetScheme(ItemSchemeType, frozen=True, omit_defaults=True):
506
506
  class JsonToVtlMapping(Struct, frozen=True, omit_defaults=True):
507
507
  """SDMX-JSON payload for To VTL mappings."""
508
508
 
509
- toVtlSubSpace: Sequence[str]
510
- type: Optional[str] = None
509
+ toVtlSubSpace: Dict[str, Sequence[str]]
510
+ method: Optional[str] = None
511
511
 
512
512
  def to_model(self) -> ToVtlMapping:
513
513
  """Converts deserialized class to pysdmx model class."""
514
- return ToVtlMapping(self.toVtlSubSpace, self.type)
514
+ return ToVtlMapping(self.toVtlSubSpace["keys"], self.method)
515
515
 
516
516
  @classmethod
517
517
  def from_model(cls, mapping: ToVtlMapping) -> "JsonToVtlMapping":
518
518
  """Converts a pysdmx "to VTL" mapping to an SDMX-JSON one."""
519
- return JsonToVtlMapping(mapping.to_vtl_sub_space, mapping.method)
519
+ return JsonToVtlMapping(
520
+ {"keys": mapping.to_vtl_sub_space}, mapping.method
521
+ )
520
522
 
521
523
 
522
524
  class JsonFromVtlMapping(Struct, frozen=True, omit_defaults=True):
523
525
  """SDMX-JSON payload for from VTL mappings."""
524
526
 
525
- fromVtlSuperSpace: Sequence[str]
526
- type: Optional[str] = None
527
+ fromVtlSuperSpace: Dict[str, Sequence[str]]
528
+ method: Optional[str] = None
527
529
 
528
530
  def to_model(self) -> FromVtlMapping:
529
531
  """Converts deserialized class to pysdmx model class."""
530
- return FromVtlMapping(self.fromVtlSuperSpace, self.type)
532
+ return FromVtlMapping(self.fromVtlSuperSpace["keys"], self.method)
531
533
 
532
534
  @classmethod
533
535
  def from_model(cls, mapping: FromVtlMapping) -> "JsonFromVtlMapping":
534
536
  """Converts a pysdmx "from VTL" mapping to an SDMX-JSON one."""
535
- return JsonFromVtlMapping(mapping.from_vtl_sub_space, mapping.method)
537
+ return JsonFromVtlMapping(
538
+ {"keys": mapping.from_vtl_sub_space}, mapping.method
539
+ )
536
540
 
537
541
 
538
542
  class JsonVtlMapping(NameableType, frozen=True, omit_defaults=True):
@@ -42,20 +42,31 @@ def check_dimension_at_observation(
42
42
  return dimension_at_observation
43
43
 
44
44
 
45
- def writing_validation(dataset: PandasDataset) -> None:
46
- """Structural validation of the dataset."""
45
+ def writing_validation(dataset: PandasDataset) -> Schema:
46
+ """Structural validation of the dataset.
47
+
48
+ Args:
49
+ dataset: The dataset to validate.
50
+
51
+ Returns:
52
+ The `Schema` from the dataset.
53
+
54
+ Raises:
55
+ Invalid: If the structure is not a `Schema` or validation fails.
56
+ """
47
57
  if not isinstance(dataset.structure, Schema):
48
58
  raise Invalid(
49
59
  "Dataset Structure is not a Schema. Cannot perform operation."
50
60
  )
61
+ schema = dataset.structure
51
62
  required_components = [
52
63
  comp.id
53
- for comp in dataset.structure.components
64
+ for comp in schema.components
54
65
  if comp.role in (Role.DIMENSION, Role.MEASURE)
55
66
  ]
56
67
  required_components.extend(
57
68
  att.id
58
- for att in dataset.structure.components.attributes
69
+ for att in schema.components.attributes
59
70
  if (
60
71
  att.required
61
72
  and att.attachment_level is not None
@@ -64,7 +75,7 @@ def writing_validation(dataset: PandasDataset) -> None:
64
75
  )
65
76
  non_required = [
66
77
  comp.id
67
- for comp in dataset.structure.components
78
+ for comp in schema.components
68
79
  if comp.id not in required_components
69
80
  ]
70
81
  # Columns match components
@@ -80,9 +91,11 @@ def writing_validation(dataset: PandasDataset) -> None:
80
91
  f"Difference: {', '.join(difference)}"
81
92
  )
82
93
  # Check if the dataset has at least one dimension and one measure
83
- if not dataset.structure.components.dimensions:
94
+ if not schema.components.dimensions:
84
95
  raise Invalid(
85
96
  "The dataset structure must have at least one dimension."
86
97
  )
87
- if not dataset.structure.components.measures:
98
+ if not schema.components.measures:
88
99
  raise Invalid("The dataset structure must have at least one measure.")
100
+
101
+ return schema