pysdmx 1.5.1__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 (60) 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/__structure_aux_reader.py +2 -2
  47. pysdmx/io/xml/__structure_aux_writer.py +5 -5
  48. pysdmx/io/xml/__write_data_aux.py +1 -54
  49. pysdmx/io/xml/__write_structure_specific_aux.py +1 -1
  50. pysdmx/io/xml/sdmx21/writer/generic.py +1 -1
  51. pysdmx/model/code.py +11 -1
  52. pysdmx/model/dataflow.py +26 -3
  53. pysdmx/model/map.py +12 -4
  54. pysdmx/model/message.py +9 -1
  55. pysdmx/toolkit/pd/_data_utils.py +100 -0
  56. pysdmx/toolkit/vtl/_validations.py +2 -3
  57. {pysdmx-1.5.1.dist-info → pysdmx-1.6.0.dist-info}/METADATA +3 -2
  58. {pysdmx-1.5.1.dist-info → pysdmx-1.6.0.dist-info}/RECORD +60 -48
  59. {pysdmx-1.5.1.dist-info → pysdmx-1.6.0.dist-info}/WHEEL +1 -1
  60. {pysdmx-1.5.1.dist-info → pysdmx-1.6.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,32 @@
1
+ """Writer interface for SDMX-JSON 2.0.0 Reference Metadata messages."""
2
+
3
+ import msgspec
4
+
5
+ from pysdmx import errors
6
+ from pysdmx.io.json.sdmxjson2.messages import JsonMetadataMessage
7
+ from pysdmx.model import decoders
8
+ from pysdmx.model.message import MetadataMessage
9
+
10
+
11
+ def read(input_str: str) -> MetadataMessage:
12
+ """Read an SDMX-JSON 2.0.0 Metadata Message.
13
+
14
+ Args:
15
+ input_str: SDMX-JSON reference metadata message to read.
16
+
17
+ Returns:
18
+ A pysdmx MetadataMessage
19
+ """
20
+ try:
21
+ msg = msgspec.json.Decoder(
22
+ JsonMetadataMessage, dec_hook=decoders
23
+ ).decode(input_str)
24
+ return msg.to_model()
25
+ except msgspec.DecodeError as de:
26
+ raise errors.Invalid(
27
+ "Invalid message",
28
+ (
29
+ "The supplied file could not be read as SDMX-JSON 2.0.0 "
30
+ "reference metadata message."
31
+ ),
32
+ ) from de
@@ -0,0 +1,32 @@
1
+ """Reader interface for SDMX-JSON 2.0.0 Structure messages."""
2
+
3
+ import msgspec
4
+
5
+ from pysdmx import errors
6
+ from pysdmx.io.json.sdmxjson2.messages import JsonStructureMessage
7
+ from pysdmx.model import decoders
8
+ from pysdmx.model.message import StructureMessage
9
+
10
+
11
+ def read(input_str: str) -> StructureMessage:
12
+ """Read an SDMX-JSON 2.0.0 Stucture Message.
13
+
14
+ Args:
15
+ input_str: SDMX-JSON structure message to read.
16
+
17
+ Returns:
18
+ A pysdmx StructureMessage
19
+ """
20
+ try:
21
+ msg = msgspec.json.Decoder(
22
+ JsonStructureMessage, dec_hook=decoders
23
+ ).decode(input_str)
24
+ return msg.to_model()
25
+ except msgspec.DecodeError as de:
26
+ raise errors.Invalid(
27
+ "Invalid message",
28
+ (
29
+ "The supplied file could not be read as SDMX-JSON 2.0.0 "
30
+ "structure message."
31
+ ),
32
+ ) from de
@@ -0,0 +1,9 @@
1
+ """Collection of writers for SDMX-JSON messages."""
2
+
3
+ from pysdmx.io.json.sdmxjson2 import messages as msg
4
+ from pysdmx.io.serde import Serializers
5
+
6
+ serializers = Serializers(
7
+ metadata_message=msg.JsonMetadataMessage,
8
+ structure_message=msg.JsonStructureMessage,
9
+ )
@@ -0,0 +1,60 @@
1
+ """Writer interface for SDMX-JSON 2.0.0 Reference Metadata messages."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional, Sequence, Union
5
+
6
+ import msgspec
7
+
8
+ from pysdmx.io.json.sdmxjson2.messages import JsonMetadataMessage
9
+ from pysdmx.model import MetadataReport, encoders
10
+ from pysdmx.model.message import Header, MetadataMessage
11
+
12
+
13
+ def write(
14
+ reports: Sequence[MetadataReport],
15
+ output_path: Optional[Union[str, Path]] = None,
16
+ prettyprint: bool = True,
17
+ header: Optional[Header] = None,
18
+ ) -> Optional[str]:
19
+ """Write metadata reports in SDMX-JSON 2.0.0.
20
+
21
+ Args:
22
+ reports: The reference metadata reports to be serialized.
23
+ output_path: The path to save the JSON file. If None or empty, the
24
+ serialized content is returned as a string instead.
25
+ prettyprint: Whether to format the JSON output with indentation (True)
26
+ or output compact JSON without extra whitespace (False).
27
+ header: The header to be used in the SDMX-JSON message
28
+ (will be generated if no header is supplied).
29
+
30
+ Returns:
31
+ The JSON string if output_path is None or empty, None otherwise.
32
+ """
33
+ if not header:
34
+ header = Header()
35
+ sm = MetadataMessage(header, reports)
36
+ jsm = JsonMetadataMessage.from_model(sm)
37
+
38
+ encoder = msgspec.json.Encoder(enc_hook=encoders)
39
+ serialized_data = encoder.encode(jsm)
40
+
41
+ # Apply pretty-printing if requested
42
+ if prettyprint:
43
+ serialized_data = msgspec.json.format(serialized_data, indent=4)
44
+
45
+ # If output_path is provided, write to file
46
+ if output_path:
47
+ # Convert to Path object if string
48
+ if isinstance(output_path, str):
49
+ output_path = Path(output_path)
50
+
51
+ # Create parent directories if they don't exist
52
+ output_path.parent.mkdir(parents=True, exist_ok=True)
53
+
54
+ # Write to file
55
+ with open(output_path, "wb") as f:
56
+ f.write(serialized_data)
57
+ return None
58
+ else:
59
+ # Return as string
60
+ return serialized_data.decode("utf-8")
@@ -0,0 +1,61 @@
1
+ """Writer interface for SDMX-JSON 2.0.0 Structure messages."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional, Sequence, Union
5
+
6
+ import msgspec
7
+
8
+ from pysdmx.io.json.sdmxjson2.messages import JsonStructureMessage
9
+ from pysdmx.model import encoders
10
+ from pysdmx.model.__base import MaintainableArtefact
11
+ from pysdmx.model.message import Header, StructureMessage
12
+
13
+
14
+ def write(
15
+ structures: Sequence[MaintainableArtefact],
16
+ output_path: Optional[Union[str, Path]] = None,
17
+ prettyprint: bool = True,
18
+ header: Optional[Header] = None,
19
+ ) -> Optional[str]:
20
+ """Write maintainable SDMX artefacts in SDMX-JSON 2.0.0.
21
+
22
+ Args:
23
+ structures: The maintainable SDMX artefacts to be serialized.
24
+ output_path: The path to save the JSON file. If None or empty, the
25
+ serialized content is returned as a string instead.
26
+ prettyprint: Whether to format the JSON output with indentation (True)
27
+ or output compact JSON without extra whitespace (False).
28
+ header: The header to be used in the SDMX-JSON message
29
+ (will be generated if no header is supplied).
30
+
31
+ Returns:
32
+ The JSON string if output_path is None or empty, None otherwise.
33
+ """
34
+ if not header:
35
+ header = Header()
36
+ sm = StructureMessage(header, structures)
37
+ jsm = JsonStructureMessage.from_model(sm)
38
+
39
+ encoder = msgspec.json.Encoder(enc_hook=encoders)
40
+ serialized_data = encoder.encode(jsm)
41
+
42
+ # Apply pretty-printing if requested
43
+ if prettyprint:
44
+ serialized_data = msgspec.json.format(serialized_data, indent=4)
45
+
46
+ # If output_path is provided, write to file
47
+ if output_path:
48
+ # Convert to Path object if string
49
+ if isinstance(output_path, str):
50
+ output_path = Path(output_path)
51
+
52
+ # Create parent directories if they don't exist
53
+ output_path.parent.mkdir(parents=True, exist_ok=True)
54
+
55
+ # Write to file
56
+ with open(output_path, "wb") as f:
57
+ f.write(serialized_data)
58
+ return None
59
+ else:
60
+ # Return as string
61
+ return serialized_data.decode("utf-8")
pysdmx/io/reader.py CHANGED
@@ -8,10 +8,10 @@ from pysdmx.errors import Invalid
8
8
  from pysdmx.io.format import Format
9
9
  from pysdmx.io.input_processor import process_string_to_read
10
10
  from pysdmx.model import Schema
11
- from pysdmx.model.__base import ItemScheme
12
- from pysdmx.model.dataflow import Dataflow, DataStructureDefinition
11
+ from pysdmx.model.__base import MaintainableArtefact
13
12
  from pysdmx.model.dataset import Dataset
14
13
  from pysdmx.model.message import Message
14
+ from pysdmx.model.metadata import MetadataReport
15
15
  from pysdmx.model.submission import SubmissionResult
16
16
  from pysdmx.util import parse_short_urn
17
17
  from pysdmx.util._model_utils import schema_generator
@@ -43,10 +43,9 @@ def read_sdmx( # noqa: C901
43
43
 
44
44
  header = None
45
45
  result_data: Sequence[Dataset] = []
46
- result_structures: Sequence[
47
- Union[ItemScheme, Dataflow, DataStructureDefinition]
48
- ] = []
46
+ result_structures: Sequence[MaintainableArtefact] = []
49
47
  result_submission: Sequence[SubmissionResult] = []
48
+ reports: Sequence[MetadataReport] = []
50
49
  if read_format == Format.STRUCTURE_SDMX_ML_2_1:
51
50
  from pysdmx.io.xml.header import read as read_header
52
51
  from pysdmx.io.xml.sdmx21.reader.structure import (
@@ -74,6 +73,24 @@ def read_sdmx( # noqa: C901
74
73
  header = read_header(input_str, validate=validate)
75
74
  # SDMX-ML 3.1 Structure
76
75
  result_structures = read_structure(input_str, validate=validate)
76
+ elif read_format == Format.STRUCTURE_SDMX_JSON_2_0_0:
77
+ from pysdmx.io.json.sdmxjson2.reader.structure import (
78
+ read as read_struct,
79
+ )
80
+
81
+ struct_msg = read_struct(input_str)
82
+ header = struct_msg.header
83
+ result_structures = (
84
+ struct_msg.structures if struct_msg.structures else []
85
+ )
86
+ elif read_format == Format.REFMETA_SDMX_JSON_2_0_0:
87
+ from pysdmx.io.json.sdmxjson2.reader.metadata import (
88
+ read as read_refmeta,
89
+ )
90
+
91
+ ref_msg = read_refmeta(input_str)
92
+ header = ref_msg.header
93
+ reports = ref_msg.get_reports()
77
94
  elif read_format == Format.DATA_SDMX_ML_2_1_GEN:
78
95
  from pysdmx.io.xml.header import read as read_header
79
96
  from pysdmx.io.xml.sdmx21.reader.generic import read as read_generic
@@ -127,18 +144,19 @@ def read_sdmx( # noqa: C901
127
144
  # SDMX-CSV 1.0
128
145
  result_data = read_csv_v1(input_str)
129
146
  else:
130
- # SDMX-CSV 2.0
131
- from pysdmx.io.csv.sdmx20.reader import read as read_csv_v2
147
+ # SDMX-CSV 2.1
148
+ from pysdmx.io.csv.sdmx21.reader import read as read_csv_v2
132
149
 
133
150
  result_data = read_csv_v2(input_str)
134
151
 
135
- if not (result_data or result_structures or result_submission):
152
+ if not (result_data or result_structures or result_submission or reports):
136
153
  raise Invalid("Empty SDMX Message")
137
154
 
138
155
  # Returning a Message class
139
156
  if read_format in (
140
157
  Format.DATA_SDMX_CSV_1_0_0,
141
158
  Format.DATA_SDMX_CSV_2_0_0,
159
+ Format.DATA_SDMX_CSV_2_1_0,
142
160
  Format.DATA_SDMX_ML_2_1_GEN,
143
161
  Format.DATA_SDMX_ML_2_1_STR,
144
162
  Format.DATA_SDMX_ML_3_0,
@@ -149,7 +167,8 @@ def read_sdmx( # noqa: C901
149
167
  return Message(header=header, data=result_data)
150
168
  elif read_format == Format.REGISTRY_SDMX_ML_2_1:
151
169
  return Message(header=header, submission=result_submission)
152
-
170
+ elif read_format == Format.REFMETA_SDMX_JSON_2_0_0:
171
+ return Message(header=header, reports=reports)
153
172
  # TODO: Ensure we have changed the signature of the structure readers
154
173
  return Message(header=header, structures=result_structures)
155
174
 
pysdmx/io/serde.py CHANGED
@@ -12,6 +12,15 @@ class Deserializer(Protocol):
12
12
  """Returns the domain objects."""
13
13
 
14
14
 
15
+ @runtime_checkable
16
+ class Serializer(Protocol):
17
+ """Creates an SDMX message from domain objects."""
18
+
19
+ @classmethod
20
+ def from_model(self, message: Any) -> Any:
21
+ """Returns the SDMX message."""
22
+
23
+
15
24
  @dataclass
16
25
  class Deserializers:
17
26
  """Collection of deserializers for a format."""
@@ -34,6 +43,14 @@ class Deserializers:
34
43
  transformation_scheme: Deserializer
35
44
 
36
45
 
46
+ @dataclass
47
+ class Serializers:
48
+ """Collection of serializers for a format."""
49
+
50
+ structure_message: Serializer
51
+ metadata_message: Serializer
52
+
53
+
37
54
  @dataclass
38
55
  class GdsDeserializers:
39
56
  """Collection of GDS deserializers for a format."""
pysdmx/io/writer.py CHANGED
@@ -7,11 +7,14 @@ from typing import Any, Optional, Sequence
7
7
 
8
8
  from pysdmx.errors import Invalid
9
9
  from pysdmx.io.format import Format
10
+ from pysdmx.model import MetadataReport
11
+ from pysdmx.model.__base import MaintainableArtefact
10
12
  from pysdmx.model.dataset import Dataset
11
13
 
12
14
  WRITERS = {
13
15
  Format.DATA_SDMX_CSV_1_0_0: "pysdmx.io.csv.sdmx10.writer",
14
16
  Format.DATA_SDMX_CSV_2_0_0: "pysdmx.io.csv.sdmx20.writer",
17
+ Format.DATA_SDMX_CSV_2_1_0: "pysdmx.io.csv.sdmx21.writer",
15
18
  Format.DATA_SDMX_ML_2_1_GEN: "pysdmx.io.xml.sdmx21.writer.generic",
16
19
  Format.DATA_SDMX_ML_2_1_STR: "pysdmx.io.xml.sdmx21.writer."
17
20
  "structure_specific",
@@ -22,14 +25,23 @@ WRITERS = {
22
25
  Format.DATA_SDMX_ML_3_1: "pysdmx.io.xml.sdmx31.writer."
23
26
  "structure_specific",
24
27
  Format.STRUCTURE_SDMX_ML_3_1: "pysdmx.io.xml.sdmx31.writer.structure",
28
+ Format.STRUCTURE_SDMX_JSON_2_0_0: (
29
+ "pysdmx.io.json.sdmxjson2.writer.structure"
30
+ ),
31
+ Format.REFMETA_SDMX_JSON_2_0_0: (
32
+ "pysdmx.io.json.sdmxjson2.writer.metadata"
33
+ ),
25
34
  }
26
35
 
27
36
  STRUCTURE_WRITERS = (
28
37
  Format.STRUCTURE_SDMX_ML_2_1,
29
38
  Format.STRUCTURE_SDMX_ML_3_0,
30
39
  Format.STRUCTURE_SDMX_ML_3_1,
40
+ Format.STRUCTURE_SDMX_JSON_2_0_0,
31
41
  )
32
42
 
43
+ REFMETA_WRITERS = (Format.REFMETA_SDMX_JSON_2_0_0,)
44
+
33
45
 
34
46
  def write_sdmx(
35
47
  sdmx_objects: Any,
@@ -97,20 +109,36 @@ def write_sdmx(
97
109
  writer = module.write
98
110
 
99
111
  is_structure = sdmx_format in STRUCTURE_WRITERS
112
+ is_ref_meta = sdmx_format in REFMETA_WRITERS
100
113
  is_xml = "xml" in WRITERS[sdmx_format]
101
- key = "structures" if is_structure else "datasets"
114
+ is_json = "json" in WRITERS[sdmx_format]
115
+ if is_structure:
116
+ key = "structures"
117
+ elif is_ref_meta:
118
+ key = "reports"
119
+ else:
120
+ key = "datasets"
102
121
  value = sdmx_objects if isinstance(sdmx_objects, list) else [sdmx_objects]
103
122
 
104
- if is_structure and any(isinstance(x, Dataset) for x in value):
123
+ if is_structure and not all(
124
+ isinstance(x, MaintainableArtefact) for x in value
125
+ ):
105
126
  raise Invalid(
106
- "Datasets cannot be written to structure formats. "
107
- "Use data formats instead."
127
+ "Only maintainable artefacts can be written to structure formats."
108
128
  )
109
- elif not is_structure and not all(isinstance(x, Dataset) for x in value):
129
+ elif is_ref_meta and not all(isinstance(x, MetadataReport) for x in value):
110
130
  raise Invalid(
111
- "Only Datasets can be written to data formats. "
112
- "Use structure formats for other SDMX objects."
131
+ (
132
+ "Only metadata reports can be written to reference "
133
+ "metadata formats."
134
+ )
113
135
  )
136
+ elif (
137
+ not is_structure
138
+ and not is_ref_meta
139
+ and not all(isinstance(x, Dataset) for x in value)
140
+ ):
141
+ raise Invalid("Only Datasets can be written to data formats.")
114
142
 
115
143
  args = {
116
144
  key: value,
@@ -120,7 +148,7 @@ def write_sdmx(
120
148
  "prettyprint": kwargs.get("prettyprint"),
121
149
  "header": kwargs.get("header"),
122
150
  }
123
- if is_xml
151
+ if is_xml or is_json
124
152
  else {}
125
153
  ),
126
154
  **(
@@ -132,7 +160,15 @@ def write_sdmx(
132
160
  if is_xml and not is_structure
133
161
  else {}
134
162
  ),
163
+ **(
164
+ {
165
+ "labels": kwargs.get("labels"),
166
+ "keys": kwargs.get("keys"),
167
+ "time_format": kwargs.get("time_format"),
168
+ }
169
+ if not is_xml
170
+ else {}
171
+ ),
135
172
  }
136
173
  args = {k: v for k, v in args.items() if v is not None}
137
-
138
174
  return writer(**args)
@@ -170,7 +170,7 @@ from pysdmx.model.dataflow import (
170
170
  Components,
171
171
  Dataflow,
172
172
  DataStructureDefinition,
173
- GroupDimension,
173
+ Group,
174
174
  Role,
175
175
  )
176
176
  from pysdmx.model.vtl import (
@@ -955,7 +955,7 @@ class StructureParser(Struct):
955
955
  for d in group_dimensions
956
956
  ]
957
957
 
958
- element[GROUPS_LOW] = [GroupDimension(**g) for g in groups]
958
+ element[GROUPS_LOW] = [Group(**g) for g in groups]
959
959
  del element[DSD_COMPS][GROUP]
960
960
  return element
961
961
 
@@ -124,7 +124,7 @@ from pysdmx.model.dataflow import (
124
124
  Component,
125
125
  Dataflow,
126
126
  DataStructureDefinition,
127
- GroupDimension,
127
+ Group,
128
128
  Role,
129
129
  )
130
130
  from pysdmx.util import (
@@ -392,7 +392,7 @@ def __write_item(
392
392
 
393
393
 
394
394
  def __write_groups(
395
- groups: list[GroupDimension], indent: str, references_30: bool = False
395
+ groups: list[Group], indent: str, references_30: bool = False
396
396
  ) -> str:
397
397
  out_file = ""
398
398
  for group in groups:
@@ -487,7 +487,7 @@ def __write_components( # noqa: C901
487
487
 
488
488
 
489
489
  def __find_matching_group_id(
490
- att_rel: str, groups: list[GroupDimension]
490
+ att_rel: str, groups: list[Group]
491
491
  ) -> Optional[str]:
492
492
  comps_to_relate = att_rel.split(",") if "," in att_rel else [att_rel]
493
493
  for group in groups:
@@ -519,7 +519,7 @@ def __write_attribute_relation( # noqa: C901
519
519
  item: Component,
520
520
  indent: str,
521
521
  component_info: Dict[str, Any],
522
- groups: list[GroupDimension],
522
+ groups: list[Group],
523
523
  references_30: bool = False,
524
524
  ) -> str:
525
525
  measure_relationship = ""
@@ -599,7 +599,7 @@ def __write_component(
599
599
  position: int,
600
600
  indent: str,
601
601
  component_info: Dict[str, Any],
602
- groups: list[GroupDimension],
602
+ groups: list[Group],
603
603
  references_30: bool = False,
604
604
  ) -> str:
605
605
  """Writes the component to the XML file."""
@@ -1,6 +1,4 @@
1
- from typing import Any, Dict, List, Optional, Sequence, Tuple
2
-
3
- import pandas as pd
1
+ from typing import Dict, Optional, Sequence
4
2
 
5
3
  from pysdmx.errors import Invalid
6
4
  from pysdmx.io.pd import PandasDataset
@@ -87,54 +85,3 @@ def writing_validation(dataset: PandasDataset) -> None:
87
85
  )
88
86
  if not dataset.structure.components.measures:
89
87
  raise Invalid("The dataset structure must have at least one measure.")
90
-
91
-
92
- def get_codes(
93
- dimension_code: str, structure: Schema, data: pd.DataFrame
94
- ) -> Tuple[List[str], List[str], List[Dict[str, Any]]]:
95
- """This function divides the components in Series and Obs."""
96
- series_codes = []
97
- groups = structure.groups
98
- group_codes = []
99
- obs_codes = [dimension_code, structure.components.measures[0].id]
100
-
101
- # Getting the series and obs codes
102
- for dim in structure.components.dimensions:
103
- if dim.id != dimension_code:
104
- series_codes.append(dim.id)
105
-
106
- # Adding the attributes based on the attachment level
107
- for att in structure.components.attributes:
108
- matching_group = next(
109
- (
110
- group
111
- for group in groups or []
112
- if set(group.dimensions)
113
- == set(att.attachment_level.split(",")) # type: ignore[union-attr]
114
- ),
115
- None,
116
- )
117
-
118
- if (
119
- att.attachment_level != "D"
120
- and att.id in data.columns
121
- and groups is not None
122
- and matching_group
123
- ):
124
- group_codes.append(
125
- {
126
- "group_id": matching_group.id,
127
- "attribute": att.id,
128
- "dimensions": matching_group.dimensions,
129
- }
130
- )
131
- elif att.attachment_level == "O" and att.id in data.columns:
132
- obs_codes.append(att.id)
133
- elif (
134
- att.attachment_level is not None
135
- and att.attachment_level != "D"
136
- and att.id in data.columns
137
- ):
138
- series_codes.append(att.id)
139
-
140
- return series_codes, obs_codes, group_codes
@@ -13,10 +13,10 @@ from pysdmx.io.xml.__write_aux import (
13
13
  get_structure,
14
14
  )
15
15
  from pysdmx.io.xml.__write_data_aux import (
16
- get_codes,
17
16
  writing_validation,
18
17
  )
19
18
  from pysdmx.io.xml.config import CHUNKSIZE
19
+ from pysdmx.toolkit.pd._data_utils import get_codes
20
20
  from pysdmx.util import parse_short_urn
21
21
 
22
22
 
@@ -21,11 +21,11 @@ from pysdmx.io.xml.__write_aux import (
21
21
  from pysdmx.io.xml.__write_data_aux import (
22
22
  check_content_dataset,
23
23
  check_dimension_at_observation,
24
- get_codes,
25
24
  writing_validation,
26
25
  )
27
26
  from pysdmx.io.xml.config import CHUNKSIZE
28
27
  from pysdmx.model.message import Header
28
+ from pysdmx.toolkit.pd._data_utils import get_codes
29
29
  from pysdmx.util import parse_short_urn
30
30
 
31
31
 
pysdmx/model/code.py CHANGED
@@ -21,7 +21,13 @@ from typing import Iterator, Literal, Optional, Sequence, Union
21
21
 
22
22
  from msgspec import Struct
23
23
 
24
- from pysdmx.model.__base import Agency, Item, ItemScheme, MaintainableArtefact
24
+ from pysdmx.model.__base import (
25
+ Agency,
26
+ Annotation,
27
+ Item,
28
+ ItemScheme,
29
+ MaintainableArtefact,
30
+ )
25
31
 
26
32
 
27
33
  class Code(Item, frozen=True, omit_defaults=True):
@@ -139,6 +145,8 @@ class HierarchicalCode(
139
145
  rel_valid_from: Start of the hierarchical relationship validity.
140
146
  rel_valid_to: End of the hierarchical relationship validity.
141
147
  codes: The child codes.
148
+ annotations: Annotations attached to the code.
149
+ urn: The URN of the code.
142
150
  """
143
151
 
144
152
  id: str
@@ -149,6 +157,8 @@ class HierarchicalCode(
149
157
  rel_valid_from: Optional[datetime] = None
150
158
  rel_valid_to: Optional[datetime] = None
151
159
  codes: Sequence["HierarchicalCode"] = ()
160
+ annotations: Sequence[Annotation] = ()
161
+ urn: Optional[str] = None
152
162
 
153
163
  def __iter__(self) -> Iterator["HierarchicalCode"]:
154
164
  """Return an iterator over the list of codes."""