pysdmx 1.3.0__py3-none-any.whl → 1.4.0rc1__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 (100) hide show
  1. pysdmx/__extras_check.py +3 -2
  2. pysdmx/__init__.py +1 -1
  3. pysdmx/api/fmr/__init__.py +4 -4
  4. pysdmx/api/gds/__init__.py +328 -0
  5. pysdmx/api/qb/gds.py +153 -0
  6. pysdmx/api/qb/service.py +91 -3
  7. pysdmx/api/qb/structure.py +1 -0
  8. pysdmx/api/qb/util.py +1 -0
  9. pysdmx/io/__init__.py +2 -1
  10. pysdmx/io/csv/sdmx10/reader/__init__.py +4 -2
  11. pysdmx/io/csv/sdmx10/writer/__init__.py +15 -2
  12. pysdmx/io/csv/sdmx20/reader/__init__.py +5 -2
  13. pysdmx/io/csv/sdmx20/writer/__init__.py +13 -2
  14. pysdmx/io/format.py +4 -0
  15. pysdmx/io/input_processor.py +12 -3
  16. pysdmx/io/json/fusion/messages/core.py +2 -0
  17. pysdmx/io/json/fusion/messages/report.py +13 -7
  18. pysdmx/io/json/gds/messages/__init__.py +35 -0
  19. pysdmx/io/json/gds/messages/agencies.py +41 -0
  20. pysdmx/io/json/gds/messages/catalog.py +79 -0
  21. pysdmx/io/json/gds/messages/sdmx_api.py +23 -0
  22. pysdmx/io/json/gds/messages/services.py +49 -0
  23. pysdmx/io/json/gds/messages/urn_resolver.py +43 -0
  24. pysdmx/io/json/gds/reader/__init__.py +12 -0
  25. pysdmx/io/json/sdmxjson2/messages/__init__.py +12 -4
  26. pysdmx/io/json/sdmxjson2/messages/agency.py +72 -0
  27. pysdmx/io/json/sdmxjson2/messages/category.py +22 -29
  28. pysdmx/io/json/sdmxjson2/messages/code.py +68 -64
  29. pysdmx/io/json/sdmxjson2/messages/concept.py +9 -18
  30. pysdmx/io/json/sdmxjson2/messages/constraint.py +2 -13
  31. pysdmx/io/json/sdmxjson2/messages/core.py +113 -21
  32. pysdmx/io/json/sdmxjson2/messages/dataflow.py +51 -21
  33. pysdmx/io/json/sdmxjson2/messages/dsd.py +110 -36
  34. pysdmx/io/json/sdmxjson2/messages/map.py +61 -49
  35. pysdmx/io/json/sdmxjson2/messages/pa.py +9 -17
  36. pysdmx/io/json/sdmxjson2/messages/provider.py +88 -0
  37. pysdmx/io/json/sdmxjson2/messages/report.py +84 -14
  38. pysdmx/io/json/sdmxjson2/messages/schema.py +14 -5
  39. pysdmx/io/json/sdmxjson2/messages/structure.py +105 -36
  40. pysdmx/io/json/sdmxjson2/messages/vtl.py +42 -96
  41. pysdmx/io/pd.py +2 -9
  42. pysdmx/io/reader.py +72 -27
  43. pysdmx/io/serde.py +11 -0
  44. pysdmx/io/writer.py +134 -0
  45. pysdmx/io/xml/{sdmx21/reader/__data_aux.py → __data_aux.py} +9 -2
  46. pysdmx/io/xml/{sdmx21/reader/__parse_xml.py → __parse_xml.py} +30 -6
  47. pysdmx/io/xml/__ss_aux_reader.py +96 -0
  48. pysdmx/io/xml/__structure_aux_reader.py +1174 -0
  49. pysdmx/io/xml/__structure_aux_writer.py +1233 -0
  50. pysdmx/io/xml/{sdmx21/__tokens.py → __tokens.py} +33 -1
  51. pysdmx/io/xml/{sdmx21/writer/__write_aux.py → __write_aux.py} +129 -37
  52. pysdmx/io/xml/{sdmx21/writer/__write_data_aux.py → __write_data_aux.py} +1 -1
  53. pysdmx/io/xml/__write_structure_specific_aux.py +254 -0
  54. pysdmx/io/xml/{sdmx21/reader/doc_validation.py → doc_validation.py} +10 -2
  55. pysdmx/io/xml/{sdmx21/reader/header.py → header.py} +11 -3
  56. pysdmx/io/xml/sdmx21/reader/error.py +2 -2
  57. pysdmx/io/xml/sdmx21/reader/generic.py +12 -8
  58. pysdmx/io/xml/sdmx21/reader/structure.py +5 -840
  59. pysdmx/io/xml/sdmx21/reader/structure_specific.py +13 -97
  60. pysdmx/io/xml/sdmx21/reader/submission.py +2 -2
  61. pysdmx/io/xml/sdmx21/writer/error.py +1 -1
  62. pysdmx/io/xml/sdmx21/writer/generic.py +13 -7
  63. pysdmx/io/xml/sdmx21/writer/structure.py +16 -828
  64. pysdmx/io/xml/sdmx21/writer/structure_specific.py +13 -238
  65. pysdmx/io/xml/sdmx30/__init__.py +1 -0
  66. pysdmx/io/xml/sdmx30/reader/__init__.py +1 -0
  67. pysdmx/io/xml/sdmx30/reader/structure.py +39 -0
  68. pysdmx/io/xml/sdmx30/reader/structure_specific.py +39 -0
  69. pysdmx/io/xml/sdmx30/writer/__init__.py +1 -0
  70. pysdmx/io/xml/sdmx30/writer/structure.py +67 -0
  71. pysdmx/io/xml/sdmx30/writer/structure_specific.py +108 -0
  72. pysdmx/model/__base.py +99 -34
  73. pysdmx/model/__init__.py +4 -0
  74. pysdmx/model/category.py +20 -0
  75. pysdmx/model/code.py +29 -8
  76. pysdmx/model/concept.py +52 -11
  77. pysdmx/model/dataflow.py +117 -33
  78. pysdmx/model/dataset.py +66 -14
  79. pysdmx/model/gds.py +161 -0
  80. pysdmx/model/map.py +51 -8
  81. pysdmx/model/message.py +235 -55
  82. pysdmx/model/metadata.py +79 -16
  83. pysdmx/model/submission.py +12 -7
  84. pysdmx/model/vtl.py +30 -13
  85. pysdmx/toolkit/__init__.py +1 -1
  86. pysdmx/toolkit/pd/__init__.py +85 -0
  87. pysdmx/toolkit/vtl/__init__.py +2 -1
  88. pysdmx/toolkit/vtl/_validations.py +1 -1
  89. pysdmx/toolkit/vtl/{generate_vtl_script.py → script_generation.py} +30 -4
  90. pysdmx/toolkit/vtl/validation.py +119 -0
  91. pysdmx/util/_model_utils.py +1 -1
  92. pysdmx-1.4.0rc1.dist-info/METADATA +119 -0
  93. pysdmx-1.4.0rc1.dist-info/RECORD +140 -0
  94. pysdmx/io/json/sdmxjson2/messages/org.py +0 -140
  95. pysdmx/toolkit/vtl/model_validations.py +0 -50
  96. pysdmx-1.3.0.dist-info/METADATA +0 -76
  97. pysdmx-1.3.0.dist-info/RECORD +0 -116
  98. /pysdmx/io/xml/{sdmx21/writer/config.py → config.py} +0 -0
  99. {pysdmx-1.3.0.dist-info → pysdmx-1.4.0rc1.dist-info}/LICENSE +0 -0
  100. {pysdmx-1.3.0.dist-info → pysdmx-1.4.0rc1.dist-info}/WHEEL +0 -0
pysdmx/model/__base.py CHANGED
@@ -1,16 +1,19 @@
1
1
  from datetime import datetime
2
- from typing import Any, Dict, Optional, Sequence, Union
2
+ from typing import Any, Optional, Sequence, Union
3
3
 
4
4
  from msgspec import Struct
5
5
 
6
6
  from pysdmx.errors import Invalid
7
7
 
8
8
 
9
- class Annotation(Struct, frozen=True, omit_defaults=True):
9
+ class Annotation(
10
+ Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True
11
+ ):
10
12
  """Annotation class.
11
13
 
12
14
  It is used to convey extra information to describe any
13
15
  SDMX construct.
16
+
14
17
  This information may be in the form of a URL reference and/or
15
18
  a multilingual text (represented by the association to
16
19
  InternationalString).
@@ -18,9 +21,9 @@ class Annotation(Struct, frozen=True, omit_defaults=True):
18
21
  Attributes:
19
22
  id: The identifier of the annotation.
20
23
  title: The title of the annotation.
21
- text: The text of the annotation.
22
- url: The URL of the annotation.
23
24
  type: The type of the annotation.
25
+ url: The URL of the annotation.
26
+ text: The value of the annotation.
24
27
  """
25
28
 
26
29
  id: Optional[str] = None
@@ -29,6 +32,11 @@ class Annotation(Struct, frozen=True, omit_defaults=True):
29
32
  url: Optional[str] = None
30
33
  type: Optional[str] = None
31
34
 
35
+ @property
36
+ def value(self) -> Optional[str]:
37
+ """Alias to text."""
38
+ return self.text
39
+
32
40
  def __post_init__(self) -> None:
33
41
  """Additional validation checks for Annotation."""
34
42
  if (
@@ -47,16 +55,27 @@ class Annotation(Struct, frozen=True, omit_defaults=True):
47
55
  )
48
56
 
49
57
  def __str__(self) -> str:
50
- """Returns a human-friendly description."""
51
- out = []
52
- for k in self.__annotations__:
53
- v = self.__getattribute__(k)
54
- if v:
55
- out.append(f"{k}={str(v)}")
56
- return ", ".join(out)
57
-
58
-
59
- class AnnotableArtefact(Struct, frozen=True, omit_defaults=True, kw_only=True):
58
+ """Custom string representation without the class name."""
59
+ processed_output = []
60
+ for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
61
+ processed_output.append(f"{attr}: {value}")
62
+ return f"{', '.join(processed_output)}"
63
+
64
+ def __repr__(self) -> str:
65
+ """Custom __repr__ that omits empty sequences."""
66
+ attrs = []
67
+ for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
68
+ attrs.append(f"{attr}={repr(value)}")
69
+ return f"{self.__class__.__name__}({', '.join(attrs)})"
70
+
71
+
72
+ class AnnotableArtefact(
73
+ Struct,
74
+ frozen=True,
75
+ omit_defaults=True,
76
+ repr_omit_defaults=True,
77
+ kw_only=True,
78
+ ):
60
79
  """Annotable Artefact class.
61
80
 
62
81
  Superclass of all SDMX artefacts.
@@ -68,22 +87,32 @@ class AnnotableArtefact(Struct, frozen=True, omit_defaults=True, kw_only=True):
68
87
 
69
88
  annotations: Sequence[Annotation] = ()
70
89
 
71
- @classmethod
72
- def __all_annotations(cls) -> Dict[str, Any]:
73
- class_attributes = {}
74
- for c in cls.__mro__:
75
- if "__annotations__" in c.__dict__:
76
- class_attributes.update(c.__annotations__)
77
- return dict(reversed(list(class_attributes.items())))
78
-
79
90
  def __str__(self) -> str:
80
- """Returns a human-friendly description."""
81
- out = []
82
- for k in self.__all_annotations():
83
- v = self.__getattribute__(k)
84
- if v:
85
- out.append(f"{k}={str(v)}")
86
- return ", ".join(out)
91
+ """Custom string representation without the class name."""
92
+ processed_output = []
93
+ for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
94
+ # str is taken as a Sequence, so we need to check it's not a str
95
+ if isinstance(value, Sequence) and not isinstance(value, str):
96
+ # Handle non-empty lists
97
+ if value:
98
+ class_name = value[0].__class__.__name__
99
+ value = f"{len(value)} {class_name.lower()}s"
100
+ # redundant if check for python 3.9 and lower versions cov
101
+ if not value:
102
+ continue
103
+
104
+ processed_output.append(f"{attr}: {value}")
105
+ return f"{', '.join(processed_output)}"
106
+
107
+ def __repr__(self) -> str:
108
+ """Custom __repr__ that omits empty sequences."""
109
+ attrs = []
110
+ for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
111
+ # Omit empty sequences
112
+ if isinstance(value, (list, tuple, set)) and not value:
113
+ continue
114
+ attrs.append(f"{attr}={repr(value)}")
115
+ return f"{self.__class__.__name__}({', '.join(attrs)})"
87
116
 
88
117
 
89
118
  class IdentifiableArtefact(AnnotableArtefact, frozen=True, omit_defaults=True):
@@ -143,7 +172,9 @@ class Item(NameableArtefact, frozen=True, omit_defaults=True):
143
172
  """
144
173
 
145
174
 
146
- class Contact(Struct, frozen=True, omit_defaults=True):
175
+ class Contact(
176
+ Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True
177
+ ):
147
178
  """Contact details such as the name of a contact and his email address.
148
179
 
149
180
  Attributes:
@@ -172,6 +203,31 @@ class Contact(Struct, frozen=True, omit_defaults=True):
172
203
  uris: Optional[Sequence[str]] = None
173
204
  emails: Optional[Sequence[str]] = None
174
205
 
206
+ def __str__(self) -> str:
207
+ """Custom string representation without the class name."""
208
+ processed_output = []
209
+ for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
210
+ # str is taken as a Sequence, so we need to check it's not a str
211
+ if isinstance(value, Sequence) and not isinstance(value, str):
212
+ # Handle non-empty lists
213
+ if not value:
214
+ continue
215
+ class_name = value[0].__class__.__name__
216
+ value = f"{len(value)} {class_name.lower()}s"
217
+
218
+ processed_output.append(f"{attr}: {value}")
219
+ return f"{', '.join(processed_output)}"
220
+
221
+ def __repr__(self) -> str:
222
+ """Custom __repr__ that omits empty sequences."""
223
+ attrs = []
224
+ for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
225
+ # Omit empty sequences
226
+ if isinstance(value, (list, tuple, set)) and not value:
227
+ continue
228
+ attrs.append(f"{attr}={repr(value)}")
229
+ return f"{self.__class__.__name__}({', '.join(attrs)})"
230
+
175
231
 
176
232
  class Organisation(Item, frozen=True, omit_defaults=True):
177
233
  """Organisation class.
@@ -272,7 +328,9 @@ class ItemScheme(MaintainableArtefact, frozen=True, omit_defaults=True):
272
328
  is_partial: bool = False
273
329
 
274
330
 
275
- class DataflowRef(Struct, frozen=True, omit_defaults=True, tag=True):
331
+ class DataflowRef(
332
+ Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True, tag=True
333
+ ):
276
334
  """A unique reference to a dataflow.
277
335
 
278
336
  Attributes:
@@ -304,11 +362,18 @@ class DataflowRef(Struct, frozen=True, omit_defaults=True, tag=True):
304
362
  )
305
363
 
306
364
  def __str__(self) -> str:
307
- """A string representating the dataflow's reference."""
365
+ """Short_urn representation of the dataflow reference."""
308
366
  return f"Dataflow={self.agency}:{self.id}({self.version})"
309
367
 
368
+ def __repr__(self) -> str:
369
+ """Custom __repr__ that omits empty sequences."""
370
+ attrs = []
371
+ for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
372
+ attrs.append(f"{attr}={repr(value)}")
373
+ return f"{self.__class__.__name__}({', '.join(attrs)})"
374
+
310
375
 
311
- class Reference(Struct, frozen=True):
376
+ class Reference(Struct, frozen=True, repr_omit_defaults=True):
312
377
  """The coordinates of an SDMX maintainable artefact.
313
378
 
314
379
  Attributes:
@@ -328,7 +393,7 @@ class Reference(Struct, frozen=True):
328
393
  return f"{self.sdmx_type}={self.agency}:{self.id}({self.version})"
329
394
 
330
395
 
331
- class ItemReference(Struct, frozen=True, tag=True):
396
+ class ItemReference(Struct, frozen=True, repr_omit_defaults=True, tag=True):
332
397
  """The coordinates of an SDMX non-nested item.
333
398
 
334
399
  Attributes:
pysdmx/model/__init__.py CHANGED
@@ -10,6 +10,7 @@ import msgspec
10
10
 
11
11
  from pysdmx.model.__base import (
12
12
  Agency,
13
+ Annotation,
13
14
  Contact,
14
15
  DataConsumer,
15
16
  DataflowRef,
@@ -34,6 +35,7 @@ from pysdmx.model.dataflow import (
34
35
  Components,
35
36
  Dataflow,
36
37
  DataflowInfo,
38
+ DataStructureDefinition,
37
39
  ProvisionAgreement,
38
40
  Role,
39
41
  Schema,
@@ -140,6 +142,7 @@ def decoders(type: Type, obj: Any) -> Any: # type: ignore[type-arg]
140
142
  __all__ = [
141
143
  "Agency",
142
144
  "AgencyScheme",
145
+ "Annotation",
143
146
  "ArrayBoundaries",
144
147
  "Categorisation",
145
148
  "Category",
@@ -163,6 +166,7 @@ __all__ = [
163
166
  "DatePatternMap",
164
167
  "DataProvider",
165
168
  "DataProviderScheme",
169
+ "DataStructureDefinition",
166
170
  "Facets",
167
171
  "FromVtlMapping",
168
172
  "FixedValueMap",
pysdmx/model/category.py CHANGED
@@ -135,6 +135,26 @@ class CategoryScheme(ItemScheme, frozen=True, omit_defaults=True):
135
135
  flows.extend(self.__extract_flows(sub))
136
136
  return flows
137
137
 
138
+ def __str__(self) -> str:
139
+ """Custom string representation without the class name."""
140
+ processed_output = []
141
+ for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
142
+ # str is taken as a Sequence, so we need to check it's not a str
143
+ if isinstance(value, Sequence) and not isinstance(value, str):
144
+ # Handle empty lists
145
+ if not value:
146
+ continue
147
+ class_name = value[0].__class__.__name__
148
+ class_name = (
149
+ class_name.lower() + "s"
150
+ if attr != "items"
151
+ else "categories"
152
+ )
153
+ value = f"{len(value)} {class_name}"
154
+
155
+ processed_output.append(f"{attr}: {value}")
156
+ return f"{', '.join(processed_output)}"
157
+
138
158
 
139
159
  class Categorisation(
140
160
  MaintainableArtefact, frozen=True, omit_defaults=True, kw_only=True
pysdmx/model/code.py CHANGED
@@ -17,7 +17,7 @@ representation of hierarchical relationships to hierarchies only.
17
17
  """
18
18
 
19
19
  from datetime import datetime
20
- from typing import Iterator, Literal, Optional, Sequence
20
+ from typing import Iterator, Literal, Optional, Sequence, Union
21
21
 
22
22
  from msgspec import Struct
23
23
 
@@ -95,7 +95,9 @@ class Codelist(ItemScheme, frozen=True, omit_defaults=True, tag=True):
95
95
  return bool(self.__getitem__(id_))
96
96
 
97
97
 
98
- class HierarchicalCode(Struct, frozen=True, omit_defaults=True):
98
+ class HierarchicalCode(
99
+ Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True
100
+ ):
99
101
  """A code, as used in a hierarchy.
100
102
 
101
103
  Hierachical codes may contain other codes.
@@ -135,11 +137,28 @@ class HierarchicalCode(Struct, frozen=True, omit_defaults=True):
135
137
  yield from self.codes
136
138
 
137
139
  def __str__(self) -> str:
138
- """Returns a human-friendly description."""
139
- out = self.id
140
- if self.name:
141
- out = f"{out} ({self.name})"
142
- return out
140
+ """Custom string representation without the class name."""
141
+ processed_output = []
142
+ for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
143
+ # str is taken as a Sequence, so we need to check it's not a str
144
+ if isinstance(value, Sequence) and not isinstance(value, str):
145
+ if not value:
146
+ continue
147
+ # Handle non-empty lists
148
+ value = f"{len(value)} hierarchical codes"
149
+
150
+ processed_output.append(f"{attr}: {value}")
151
+ return f"{', '.join(processed_output)}"
152
+
153
+ def __repr__(self) -> str:
154
+ """Custom __repr__ that omits empty sequences."""
155
+ attrs = []
156
+ for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
157
+ # Omit empty sequences
158
+ if isinstance(value, (list, tuple, set)) and not value:
159
+ continue
160
+ attrs.append(f"{attr}={repr(value)}")
161
+ return f"{self.__class__.__name__}({', '.join(attrs)})"
143
162
 
144
163
 
145
164
  class Hierarchy(
@@ -171,10 +190,12 @@ class Hierarchy(
171
190
  assume that the operator property references a VTL operator
172
191
  representing a sum. This can then be used for validation purposes,
173
192
  to check that A = B + C.
193
+ is_partial: Whether the hierarchy is partial.
174
194
  """
175
195
 
176
196
  codes: Sequence[HierarchicalCode] = ()
177
197
  operator: Optional[str] = None
198
+ is_partial: bool = True
178
199
 
179
200
  def __iter__(self) -> Iterator[HierarchicalCode]:
180
201
  """Return an iterator over the list of codes."""
@@ -287,7 +308,7 @@ class HierarchyAssociation(
287
308
  ):
288
309
  """Links a hierarchy to a component withing the context of a dataflow."""
289
310
 
290
- hierarchy: Optional[Hierarchy] = None
311
+ hierarchy: Optional[Union[Hierarchy, str]] = None
291
312
  component_ref: str = ""
292
313
  context_ref: str = ""
293
314
  operator: Optional[str] = None
pysdmx/model/concept.py CHANGED
@@ -24,13 +24,17 @@ class DataType(str, Enum):
24
24
  """The expected data type for a concept."""
25
25
 
26
26
  ALPHA = "Alpha"
27
- """Alphabetical characters."""
27
+ """The alphabetic character set of A-Z, a-z.."""
28
28
  ALPHA_NUM = "AlphaNumeric"
29
- """Alphabetical and numerical characters."""
29
+ """The character set of A-Z, a-z and 0-9."""
30
+ BASIC_TIME_PERIOD = "BasicTimePeriod"
31
+ """The ISO standard periods."""
30
32
  BIG_INTEGER = "BigInteger"
31
33
  """Immutable arbitrary-precision signed integer."""
32
34
  BOOLEAN = "Boolean"
33
35
  """True or False."""
36
+ COUNT = "Count"
37
+ """A simple incrementing integer type."""
34
38
  DATE = "GregorianDay"
35
39
  """A ISO 8601 date (e.g. ``2011-06-17``)."""
36
40
  DATE_TIME = "DateTime"
@@ -41,8 +45,12 @@ class DataType(str, Enum):
41
45
  """Immutable arbitrary-precision signed decimal number."""
42
46
  DOUBLE = "Double"
43
47
  """A decimal number (8 bytes)."""
48
+ DURATION = "Duration"
49
+ """An ISO 8601 duration."""
44
50
  FLOAT = "Float"
45
51
  """A decimal number (4 bytes)."""
52
+ GREGORIAN_TIME_PERIOD = "GregorianTimePeriod"
53
+ """This is the union of YEAR, YEAR_MONTH, and DATE."""
46
54
  INTEGER = "Integer"
47
55
  """A whole number (4 bytes)."""
48
56
  LONG = "Long"
@@ -52,24 +60,50 @@ class DataType(str, Enum):
52
60
  MONTH_DAY = "MonthDay"
53
61
  """A month day in the ISO 8601 calendar (e.g. ``--12-31``)."""
54
62
  NUMERIC = "Numeric"
55
- """A numerical value (integer or decimal)."""
63
+ """The simple numeric character set of 0-9, treated as a string."""
56
64
  PERIOD = "ObservationalTimePeriod"
57
65
  """A reporting period. The format varies with the frequency."""
66
+ REP_DAY = "ReportingDay"
67
+ """The SDMX reporting period for daily frequency (e.g. 2025-D001)."""
68
+ REP_MONTH = "ReportingMonth"
69
+ """The SDMX reporting period for monthly frequency (e.g. 2025-M01)."""
70
+ REP_QUARTER = "ReportingQuarter"
71
+ """The SDMX reporting period for quarterly frequency (e.g. 2025-Q1)."""
72
+ REP_SEMESTER = "ReportingSemester"
73
+ """The SDMX reporting period for half-yearly frequency (e.g. 2025-S1)."""
74
+ REP_TRIMESTER = "ReportingTrimester"
75
+ """The SDMX reporting period for trimestrial frequency (e.g. 2025-T1)."""
76
+ REP_WEEK = "ReportingWeek"
77
+ """The SDMX reporting period for weekly frequency (e.g. 2025-W01)."""
78
+ REP_YEAR = "ReportingYear"
79
+ """The SDMX reporting period for yearly frequency (e.g. 2025-A1)."""
58
80
  SHORT = "Short"
59
81
  """A whole number (2 bytes)."""
82
+ STD_TIME_PERIOD = "StandardTimePeriod"
83
+ """The ISO standard periods and the SDMX reporting periods."""
60
84
  STRING = "String"
61
85
  """A string (as immutable sequence of Unicode code points)."""
62
86
  TIME = "Time"
63
87
  """An ISO 8601 time (e.g. ``12:50:42``)."""
64
88
  URI = "URI"
65
89
  """A uniform resource identifier, such as a URL."""
90
+ XHTML = "XHTML"
91
+ """XHTML (or HTML) markup."""
66
92
  YEAR = "GregorianYear"
67
93
  """An ISO 8601 year (e.g. ``2000``)."""
68
94
  YEAR_MONTH = "GregorianYearMonth"
69
95
  """An ISO 8601 year and month (e.g. ``2000-01``)."""
70
96
 
97
+ def __str__(self) -> str:
98
+ """Data Type String representation."""
99
+ return self.value
100
+
101
+ def __repr__(self) -> str:
102
+ """Data Type String representation."""
103
+ return f"{self.__class__.__name__}.{self._name_}"
104
+
71
105
 
72
- class Facets(Struct, frozen=True, omit_defaults=True):
106
+ class Facets(Struct, frozen=True, omit_defaults=True, repr_omit_defaults=True):
73
107
  """Additional information about the concept expected values.
74
108
 
75
109
  The facets that apply vary with the type. For example,
@@ -104,15 +138,22 @@ class Facets(Struct, frozen=True, omit_defaults=True):
104
138
  """Indicates the ending point of a sequence."""
105
139
  is_sequence: bool = False
106
140
  """Whether the values are intended to be ordered."""
141
+ is_multilingual: bool = False
142
+ """Whether the text can be in multiple languages."""
107
143
 
108
144
  def __str__(self) -> str:
109
- """Returns a human-friendly description."""
110
- out = []
111
- for k in self.__annotations__:
112
- v = self.__getattribute__(k)
113
- if v:
114
- out.append(f"{k}={v}")
115
- return ", ".join(out)
145
+ """Custom string representation without the class name."""
146
+ processed_output = []
147
+ for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
148
+ processed_output.append(f"{attr}: {value}")
149
+ return f"{', '.join(processed_output)}"
150
+
151
+ def __repr__(self) -> str:
152
+ """Custom __repr__ that omits empty sequences."""
153
+ attrs = []
154
+ for attr, value, *_ in self.__rich_repr__(): # type: ignore[misc]
155
+ attrs.append(f"{attr}={repr(value)}")
156
+ return f"{self.__class__.__name__}({', '.join(attrs)})"
116
157
 
117
158
 
118
159
  class Concept(Item, frozen=True, omit_defaults=True, tag=True):