pysdmx 1.8.0__tar.gz → 1.9.0__tar.gz

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 (167) hide show
  1. {pysdmx-1.8.0 → pysdmx-1.9.0}/PKG-INFO +4 -1
  2. {pysdmx-1.8.0 → pysdmx-1.9.0}/pyproject.toml +5 -3
  3. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/__extras_check.py +14 -0
  4. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/__init__.py +1 -1
  5. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/category.py +69 -41
  6. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/msd.py +1 -1
  7. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/category.py +76 -43
  8. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/core.py +2 -1
  9. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/msd.py +5 -1
  10. pysdmx-1.9.0/src/pysdmx/io/json/sdmxjson2/reader/doc_validation.py +108 -0
  11. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/reader/metadata.py +8 -1
  12. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/reader/structure.py +9 -2
  13. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/reader.py +18 -4
  14. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/__data_aux.py +9 -4
  15. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/__parse_xml.py +2 -0
  16. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/__structure_aux_reader.py +70 -0
  17. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/__structure_aux_writer.py +63 -9
  18. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/__tokens.py +3 -0
  19. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/__write_aux.py +35 -30
  20. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/header.py +7 -1
  21. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/__base.py +1 -1
  22. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/category.py +6 -1
  23. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/message.py +22 -6
  24. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/util/_model_utils.py +40 -3
  25. {pysdmx-1.8.0 → pysdmx-1.9.0}/LICENSE +0 -0
  26. {pysdmx-1.8.0 → pysdmx-1.9.0}/README.rst +0 -0
  27. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/__init__.py +0 -0
  28. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/dc/__init__.py +0 -0
  29. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/dc/_api.py +0 -0
  30. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/__init__.py +0 -0
  31. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/_model.py +0 -0
  32. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/_parsing_model.py +0 -0
  33. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/_parsing_util.py +0 -0
  34. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/_py_parser.py +0 -0
  35. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/_sql_parser.py +0 -0
  36. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/util.py +0 -0
  37. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/fmr/__init__.py +0 -0
  38. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/fmr/maintenance.py +0 -0
  39. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/gds/__init__.py +0 -0
  40. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/qb/__init__.py +0 -0
  41. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/qb/availability.py +0 -0
  42. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/qb/data.py +0 -0
  43. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/qb/gds.py +0 -0
  44. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/qb/refmeta.py +0 -0
  45. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/qb/registration.py +0 -0
  46. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/qb/schema.py +0 -0
  47. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/qb/service.py +0 -0
  48. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/qb/structure.py +0 -0
  49. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/api/qb/util.py +0 -0
  50. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/errors.py +0 -0
  51. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/__init__.py +0 -0
  52. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/csv/__csv_aux_reader.py +0 -0
  53. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/csv/__csv_aux_writer.py +0 -0
  54. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/csv/__init__.py +0 -0
  55. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx10/__init__.py +0 -0
  56. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx10/reader/__init__.py +0 -0
  57. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx10/writer/__init__.py +0 -0
  58. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx20/__init__.py +0 -0
  59. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx20/reader/__init__.py +0 -0
  60. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx20/writer/__init__.py +0 -0
  61. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx21/__init__.py +0 -0
  62. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx21/reader/__init__.py +0 -0
  63. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx21/writer/__init__.py +0 -0
  64. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/format.py +0 -0
  65. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/input_processor.py +0 -0
  66. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/__init__.py +0 -0
  67. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/code.py +0 -0
  68. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/concept.py +0 -0
  69. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/constraint.py +0 -0
  70. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/core.py +0 -0
  71. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/dataflow.py +0 -0
  72. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/dsd.py +0 -0
  73. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/map.py +0 -0
  74. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/metadataflow.py +0 -0
  75. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/mpa.py +0 -0
  76. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/org.py +0 -0
  77. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/pa.py +0 -0
  78. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/report.py +0 -0
  79. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/schema.py +0 -0
  80. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/vtl.py +0 -0
  81. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/reader/__init__.py +0 -0
  82. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/messages/__init__.py +0 -0
  83. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/messages/agencies.py +0 -0
  84. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/messages/catalog.py +0 -0
  85. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/messages/sdmx_api.py +0 -0
  86. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/messages/services.py +0 -0
  87. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/messages/urn_resolver.py +0 -0
  88. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/reader/__init__.py +0 -0
  89. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/__init__.py +0 -0
  90. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/agency.py +0 -0
  91. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/code.py +0 -0
  92. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/concept.py +0 -0
  93. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/constraint.py +0 -0
  94. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/dataflow.py +0 -0
  95. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/dsd.py +0 -0
  96. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/map.py +0 -0
  97. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/metadataflow.py +0 -0
  98. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/mpa.py +0 -0
  99. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/pa.py +0 -0
  100. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/provider.py +0 -0
  101. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/report.py +0 -0
  102. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/schema.py +0 -0
  103. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/structure.py +0 -0
  104. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/vtl.py +0 -0
  105. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/reader/__init__.py +0 -0
  106. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/writer/__init__.py +0 -0
  107. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/writer/metadata.py +0 -0
  108. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/writer/structure.py +0 -0
  109. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/pd.py +0 -0
  110. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/serde.py +0 -0
  111. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/writer.py +0 -0
  112. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/__allowed_lxml_errors.py +0 -0
  113. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/__init__.py +0 -0
  114. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/__ss_aux_reader.py +0 -0
  115. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/__write_data_aux.py +0 -0
  116. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/__write_structure_specific_aux.py +0 -0
  117. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/config.py +0 -0
  118. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/doc_validation.py +0 -0
  119. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/__init__.py +0 -0
  120. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/reader/__init__.py +0 -0
  121. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/reader/error.py +0 -0
  122. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/reader/generic.py +0 -0
  123. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/reader/structure.py +0 -0
  124. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/reader/structure_specific.py +0 -0
  125. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/reader/submission.py +0 -0
  126. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/writer/__init__.py +0 -0
  127. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/writer/error.py +0 -0
  128. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/writer/generic.py +0 -0
  129. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/writer/structure.py +0 -0
  130. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/writer/structure_specific.py +0 -0
  131. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/__init__.py +0 -0
  132. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/reader/__init__.py +0 -0
  133. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/reader/structure.py +0 -0
  134. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/reader/structure_specific.py +0 -0
  135. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/writer/__init__.py +0 -0
  136. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/writer/structure.py +0 -0
  137. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/writer/structure_specific.py +0 -0
  138. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/__init__.py +0 -0
  139. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/reader/__init__.py +0 -0
  140. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/reader/structure.py +0 -0
  141. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/reader/structure_specific.py +0 -0
  142. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/writer/__init__.py +0 -0
  143. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/writer/structure.py +0 -0
  144. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/writer/structure_specific.py +0 -0
  145. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/io/xml/utils.py +0 -0
  146. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/__init__.py +0 -0
  147. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/code.py +0 -0
  148. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/concept.py +0 -0
  149. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/dataflow.py +0 -0
  150. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/dataset.py +0 -0
  151. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/gds.py +0 -0
  152. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/map.py +0 -0
  153. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/metadata.py +0 -0
  154. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/organisation.py +0 -0
  155. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/submission.py +0 -0
  156. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/model/vtl.py +0 -0
  157. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/py.typed +0 -0
  158. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/toolkit/__init__.py +0 -0
  159. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/toolkit/pd/__init__.py +0 -0
  160. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/toolkit/pd/_data_utils.py +0 -0
  161. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/toolkit/vtl/__init__.py +0 -0
  162. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/toolkit/vtl/_validations.py +0 -0
  163. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/toolkit/vtl/script_generation.py +0 -0
  164. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/toolkit/vtl/validation.py +0 -0
  165. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/util/__init__.py +0 -0
  166. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/util/_date_pattern_map.py +0 -0
  167. {pysdmx-1.8.0 → pysdmx-1.9.0}/src/pysdmx/util/_net_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pysdmx
3
- Version: 1.8.0
3
+ Version: 1.9.0
4
4
  Summary: Your opinionated Python SDMX library
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -16,9 +16,11 @@ Classifier: Typing :: Typed
16
16
  Provides-Extra: all
17
17
  Provides-Extra: data
18
18
  Provides-Extra: dc
19
+ Provides-Extra: json
19
20
  Provides-Extra: vtl
20
21
  Provides-Extra: xml
21
22
  Requires-Dist: httpx[http2] (>=0)
23
+ Requires-Dist: jsonschema (>=4.10) ; extra == "json"
22
24
  Requires-Dist: lxml (>=5.2) ; extra == "all"
23
25
  Requires-Dist: lxml (>=5.2) ; extra == "xml"
24
26
  Requires-Dist: msgspec (>=0)
@@ -28,6 +30,7 @@ Requires-Dist: parsy (>=2.1)
28
30
  Requires-Dist: python-dateutil (>=2.8.2) ; extra == "all"
29
31
  Requires-Dist: python-dateutil (>=2.8.2) ; extra == "dc"
30
32
  Requires-Dist: sdmxschemas (>=1.0.0) ; extra == "all"
33
+ Requires-Dist: sdmxschemas (>=1.0.0) ; extra == "json"
31
34
  Requires-Dist: sdmxschemas (>=1.0.0) ; extra == "xml"
32
35
  Requires-Dist: vtlengine (>=1.1,<2.0) ; extra == "all"
33
36
  Requires-Dist: vtlengine (>=1.1,<2.0) ; extra == "vtl"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pysdmx"
3
- version = "1.8.0"
3
+ version = "1.9.0"
4
4
  description = "Your opinionated Python SDMX library"
5
5
  license = { text = "Apache-2.0" }
6
6
  readme = "README.rst"
@@ -23,7 +23,7 @@ classifiers = [
23
23
  dependencies = [
24
24
  "httpx[http2]>=0.*",
25
25
  "msgspec>=0.*",
26
- "parsy>=2.1",
26
+ "parsy>=2.1"
27
27
  ]
28
28
 
29
29
  [project.urls]
@@ -36,6 +36,7 @@ documentation = "https://bis-med-it.github.io/pysdmx"
36
36
  data = ["pandas>=2.1.4"]
37
37
  dc = ["python-dateutil>=2.8.2"]
38
38
  vtl = ["vtlengine>=1.1,<2.0"]
39
+ json = ["sdmxschemas>=1.0.0", "jsonschema>=4.10"]
39
40
  xml = ["lxml>=5.2", "xmltodict>=0.13", "sdmxschemas>=1.0.0"]
40
41
  all = ["lxml>=5.2", "xmltodict>=0.13", "sdmxschemas>=1.0.0", "pandas>=2.1.4", "python-dateutil>=2.8.2", "vtlengine>=1.1,<2.0"]
41
42
 
@@ -57,6 +58,7 @@ lxml-stubs = "^0.5.1"
57
58
  types-xmltodict = "^0.13.0.3"
58
59
  types-python-dateutil = "^2.9.0.20240316"
59
60
  pandas-stubs = "^2.1.4.231227"
61
+ types-jsonschema = "^4.25.1.20251009"
60
62
 
61
63
  [tool.poetry.group.docs.dependencies]
62
64
  sphinx = "^7.2.6"
@@ -93,7 +95,7 @@ lint.mccabe.max-complexity = 10
93
95
  lint.pydocstyle.convention = "google"
94
96
 
95
97
  [tool.ruff.lint.per-file-ignores]
96
- "tests/*" = ["DTZ", "D100", "D103", "D104", "PERF", "S101", "S311"]
98
+ "tests/*" = ["C901", "DTZ", "D100", "D103", "D104", "PERF", "S101", "S311"]
97
99
 
98
100
  [tool.mypy]
99
101
  files = "src"
@@ -55,3 +55,17 @@ def __check_vtl_extra() -> None:
55
55
  " and prettify",
56
56
  )
57
57
  ) from None
58
+
59
+
60
+ def __check_json_extra() -> None:
61
+ try:
62
+ import jsonschema # noqa: F401
63
+ import sdmxschemas # noqa: F401
64
+ except ImportError:
65
+ raise ImportError(
66
+ ERROR_MESSAGE.format(
67
+ extra_name="json",
68
+ extra_desc="the validation of SDMX-JSON Structure Messages "
69
+ "(hint, use validate=False if you don't need validation)",
70
+ )
71
+ ) from None
@@ -1,3 +1,3 @@
1
1
  """Your opinionated Python SDMX library."""
2
2
 
3
- __version__ = "1.8.0"
3
+ __version__ = "1.9.0"
@@ -1,7 +1,7 @@
1
1
  """Collection of Fusion-JSON schemas for categories and category schemes."""
2
2
 
3
3
  from collections import defaultdict
4
- from typing import Dict, Optional, Sequence
4
+ from typing import Dict, Optional, Sequence, Tuple, Union
5
5
 
6
6
  from msgspec import Struct
7
7
 
@@ -11,6 +11,8 @@ from pysdmx.model import (
11
11
  Agency,
12
12
  Category,
13
13
  DataflowRef,
14
+ ItemReference,
15
+ Reference,
14
16
  )
15
17
  from pysdmx.model import (
16
18
  Categorisation as CT,
@@ -21,7 +23,7 @@ from pysdmx.model import (
21
23
  from pysdmx.model import (
22
24
  Dataflow as DF,
23
25
  )
24
- from pysdmx.util import find_by_urn
26
+ from pysdmx.util import find_by_urn, parse_urn
25
27
 
26
28
 
27
29
  class FusionCategorisation(Struct, frozen=True, rename={"agency": "agencyId"}):
@@ -57,14 +59,46 @@ class FusionCategory(Struct, frozen=True):
57
59
  descriptions: Optional[Sequence[FusionString]] = None
58
60
  items: Sequence["FusionCategory"] = ()
59
61
 
60
- def to_model(self) -> Category:
62
+ def __add_flows(
63
+ self, cni: str, cf: Dict[str, list[DF]]
64
+ ) -> Sequence[DataflowRef]:
65
+ if cni in cf:
66
+ return [
67
+ DataflowRef(
68
+ (
69
+ df.agency.id
70
+ if isinstance(df.agency, Agency)
71
+ else df.agency
72
+ ),
73
+ df.id,
74
+ df.version,
75
+ df.name,
76
+ )
77
+ for df in cf[cni]
78
+ ]
79
+ else:
80
+ return ()
81
+
82
+ def to_model(
83
+ self,
84
+ cat_flows: dict[str, list[DF]],
85
+ cat_other: dict[str, list[Union[ItemReference, Reference]]],
86
+ parent_id: Optional[str] = None,
87
+ ) -> Category:
61
88
  """Converts a FusionCode to a standard code."""
62
89
  description = self.descriptions[0].value if self.descriptions else None
90
+ cni = f"{parent_id}.{self.id}" if parent_id else self.id
91
+ dataflows = self.__add_flows(cni, cat_flows)
92
+ others = cat_other.get(cni, ())
63
93
  return Category(
64
94
  id=self.id,
65
95
  name=self.names[0].value,
66
96
  description=description,
67
- categories=[c.to_model() for c in self.items],
97
+ categories=[
98
+ c.to_model(cat_flows, cat_other, cni) for c in self.items
99
+ ],
100
+ dataflows=dataflows,
101
+ other_references=others,
68
102
  )
69
103
 
70
104
 
@@ -78,16 +112,42 @@ class FusionCategoryScheme(Struct, frozen=True, rename={"agency": "agencyId"}):
78
112
  version: str = "1.0"
79
113
  items: Sequence[FusionCategory] = ()
80
114
 
81
- def to_model(self) -> CS:
115
+ def __group_refs(
116
+ self,
117
+ categorisations: Sequence[FusionCategorisation],
118
+ dataflows: Sequence[FusionDataflow],
119
+ ) -> Tuple[
120
+ dict[str, list[DF]], dict[str, list[Union[ItemReference, Reference]]]
121
+ ]:
122
+ flows: defaultdict[str, list[DF]] = defaultdict(list)
123
+ other: defaultdict[str, list[Union[ItemReference, Reference]]] = (
124
+ defaultdict(list)
125
+ )
126
+ for c in categorisations:
127
+ ref = parse_urn(c.structureReference)
128
+ src = c.categoryReference[c.categoryReference.find(")") + 2 :]
129
+ if ref.sdmx_type == "Dataflow":
130
+ d = find_by_urn(dataflows, c.structureReference)
131
+ flows[src].append(d.to_model())
132
+ else:
133
+ other[src].append(ref)
134
+ return (flows, other)
135
+
136
+ def to_model(
137
+ self,
138
+ categorisations: Sequence[FusionCategorisation] = (),
139
+ dataflows: Sequence[FusionDataflow] = (),
140
+ ) -> CS:
82
141
  """Converts a JsonCodelist to a standard codelist."""
83
142
  description = self.descriptions[0].value if self.descriptions else None
143
+ cat_flows, cat_others = self.__group_refs(categorisations, dataflows)
84
144
  return CS(
85
145
  id=self.id,
86
146
  name=self.names[0].value,
87
147
  agency=self.agency,
88
148
  description=description,
89
149
  version=self.version,
90
- items=[c.to_model() for c in self.items],
150
+ items=[c.to_model(cat_flows, cat_others) for c in self.items],
91
151
  )
92
152
 
93
153
 
@@ -98,43 +158,11 @@ class FusionCategorySchemeMessage(Struct, frozen=True):
98
158
  Categorisation: Sequence[FusionCategorisation] = ()
99
159
  Dataflow: Sequence[FusionDataflow] = ()
100
160
 
101
- def __group_flows(self) -> defaultdict[str, list[DF]]:
102
- out: defaultdict[str, list[DF]] = defaultdict(list)
103
- for c in self.Categorisation:
104
- d = find_by_urn(self.Dataflow, c.structureReference)
105
- src = c.categoryReference[c.categoryReference.find(")") + 2 :]
106
- out[src].append(d.to_model())
107
- return out
108
-
109
- def __add_flows(
110
- self, cat: Category, cni: str, cf: Dict[str, list[DF]]
111
- ) -> None:
112
- if cat.categories:
113
- for c in cat.categories:
114
- self.__add_flows(c, f"{cni}.{c.id}", cf)
115
- if cni in cf:
116
- dfrefs = [
117
- DataflowRef(
118
- (
119
- df.agency.id
120
- if isinstance(df.agency, Agency)
121
- else df.agency
122
- ),
123
- df.id,
124
- df.version,
125
- df.name,
126
- )
127
- for df in cf[cni]
128
- ]
129
- cat.dataflows = dfrefs
130
-
131
161
  def to_model(self) -> CS:
132
162
  """Returns the requested category scheme."""
133
- cf = self.__group_flows()
134
- cs = self.CategoryScheme[0].to_model()
135
- for c in cs:
136
- self.__add_flows(c, c.id, cf)
137
- return cs
163
+ return self.CategoryScheme[0].to_model(
164
+ self.Categorisation, self.Dataflow
165
+ )
138
166
 
139
167
 
140
168
  class FusionCategorisationMessage(Struct, frozen=True):
@@ -29,7 +29,7 @@ class FusionMetadataAttribute(Struct, frozen=True):
29
29
  id: str
30
30
  concept: str
31
31
  minOccurs: int
32
- maxOccurs: Union[int, Literal["unbounded"]]
32
+ maxOccurs: Union[int, Literal["unbounded"]] = "unbounded"
33
33
  presentational: Optional[bool] = False
34
34
  representation: Optional[FusionRepresentation] = None
35
35
  metadataAttributes: Sequence["FusionMetadataAttribute"] = ()
@@ -1,7 +1,7 @@
1
1
  """Collection of SDMX-JSON schemas for categories and category schemes."""
2
2
 
3
3
  from collections import defaultdict
4
- from typing import Dict, Sequence
4
+ from typing import Dict, Optional, Sequence, Tuple, Union
5
5
 
6
6
  from msgspec import Struct
7
7
 
@@ -20,8 +20,10 @@ from pysdmx.model import (
20
20
  CategoryScheme,
21
21
  Dataflow,
22
22
  DataflowRef,
23
+ ItemReference,
24
+ Reference,
23
25
  )
24
- from pysdmx.util import find_by_urn
26
+ from pysdmx.util import find_by_urn, parse_urn
25
27
 
26
28
 
27
29
  class JsonCategorisation(
@@ -84,14 +86,46 @@ class JsonCategory(NameableType, frozen=True, omit_defaults=True):
84
86
 
85
87
  categories: Sequence["JsonCategory"] = ()
86
88
 
87
- def to_model(self) -> Category:
89
+ def __add_flows(
90
+ self, cni: str, cf: Dict[str, list[Dataflow]]
91
+ ) -> Sequence[DataflowRef]:
92
+ if cni in cf:
93
+ return [
94
+ DataflowRef(
95
+ (
96
+ df.agency.id
97
+ if isinstance(df.agency, Agency)
98
+ else df.agency
99
+ ),
100
+ df.id,
101
+ df.version,
102
+ df.name,
103
+ )
104
+ for df in cf[cni]
105
+ ]
106
+ else:
107
+ return ()
108
+
109
+ def to_model(
110
+ self,
111
+ cat_flows: dict[str, list[Dataflow]],
112
+ cat_other: dict[str, list[Union[ItemReference, Reference]]],
113
+ parent_id: Optional[str] = None,
114
+ ) -> Category:
88
115
  """Converts a FusionCode to a standard code."""
116
+ cni = f"{parent_id}.{self.id}" if parent_id else self.id
117
+ dataflows = self.__add_flows(cni, cat_flows)
118
+ others = cat_other.get(cni, ())
89
119
  return Category(
90
120
  id=self.id,
91
121
  name=self.name,
92
122
  description=self.description,
93
- categories=[c.to_model() for c in self.categories],
123
+ categories=[
124
+ c.to_model(cat_flows, cat_other, cni) for c in self.categories
125
+ ],
94
126
  annotations=[a.to_model() for a in self.annotations],
127
+ dataflows=dataflows,
128
+ other_references=others,
95
129
  )
96
130
 
97
131
  @classmethod
@@ -126,15 +160,42 @@ class JsonCategoryScheme(
126
160
 
127
161
  categories: Sequence[JsonCategory] = ()
128
162
 
129
- def to_model(self) -> CategoryScheme:
130
- """Converts a JsonCodelist to a standard codelist."""
163
+ def __group_refs(
164
+ self,
165
+ categorisations: Sequence[JsonCategorisation] = (),
166
+ dataflows: Sequence[JsonDataflow] = (),
167
+ ) -> Tuple[
168
+ dict[str, list[Dataflow]],
169
+ dict[str, list[Union[ItemReference, Reference]]],
170
+ ]:
171
+ flows: defaultdict[str, list[Dataflow]] = defaultdict(list)
172
+ other: defaultdict[str, list[Union[ItemReference, Reference]]] = (
173
+ defaultdict(list)
174
+ )
175
+ for c in categorisations:
176
+ ref = parse_urn(c.source)
177
+ src = c.target[c.target.find(")") + 2 :]
178
+ if ref.sdmx_type == "Dataflow":
179
+ d = find_by_urn(dataflows, c.source)
180
+ flows[src].append(d.to_model())
181
+ else:
182
+ other[src].append(ref)
183
+ return (flows, other)
184
+
185
+ def to_model(
186
+ self,
187
+ categorisations: Sequence[JsonCategorisation] = (),
188
+ dataflows: Sequence[JsonDataflow] = (),
189
+ ) -> CategoryScheme:
190
+ """Converts a JsonCategoryScheme to a standard one."""
191
+ cat_flows, cat_other = self.__group_refs(categorisations, dataflows)
131
192
  return CategoryScheme(
132
193
  id=self.id,
133
194
  name=self.name,
134
195
  agency=self.agency,
135
196
  description=self.description,
136
197
  version=self.version,
137
- items=[c.to_model() for c in self.categories],
198
+ items=[c.to_model(cat_flows, cat_other) for c in self.categories],
138
199
  is_external_reference=self.isExternalReference,
139
200
  is_partial=self.isPartial,
140
201
  valid_from=self.validFrom,
@@ -179,49 +240,21 @@ class JsonCategorySchemes(Struct, frozen=True, omit_defaults=True):
179
240
  categorisations: Sequence[JsonCategorisation] = ()
180
241
  dataflows: Sequence[JsonDataflow] = ()
181
242
 
243
+ def to_model(self) -> CategoryScheme:
244
+ """Returns the requested codelist."""
245
+ return self.categorySchemes[0].to_model(
246
+ self.categorisations, self.dataflows
247
+ )
248
+
182
249
 
183
250
  class JsonCategorySchemeMessage(Struct, frozen=True, omit_defaults=True):
184
251
  """SDMX-JSON payload for /categoryscheme queries."""
185
252
 
186
253
  data: JsonCategorySchemes
187
254
 
188
- def __group_flows(self) -> defaultdict[str, list[Dataflow]]:
189
- out: defaultdict[str, list[Dataflow]] = defaultdict(list)
190
- for c in self.data.categorisations:
191
- d = find_by_urn(self.data.dataflows, c.source)
192
- src = c.target[c.target.find(")") + 2 :]
193
- out[src].append(d.to_model())
194
- return out
195
-
196
- def __add_flows(
197
- self, cat: Category, cni: str, cf: Dict[str, list[Dataflow]]
198
- ) -> None:
199
- if cat.categories:
200
- for c in cat.categories:
201
- self.__add_flows(c, f"{cni}.{c.id}", cf)
202
- if cni in cf:
203
- dfrefs = [
204
- DataflowRef(
205
- (
206
- df.agency.id
207
- if isinstance(df.agency, Agency)
208
- else df.agency
209
- ),
210
- df.id,
211
- df.version,
212
- df.name,
213
- )
214
- for df in cf[cni]
215
- ]
216
- cat.dataflows = dfrefs
217
-
218
255
  def to_model(self) -> CategoryScheme:
219
- """Returns the requested codelist."""
220
- cf = self.__group_flows()
221
- cs = self.data.categorySchemes[0].to_model()
222
- for c in cs:
223
- self.__add_flows(c, c.id, cf)
224
- return cs
256
+ """Returns the requested category scheme."""
257
+ return self.data.to_model()
225
258
 
226
259
 
227
260
  class JsonCategorisations(Struct, frozen=True, omit_defaults=True):
@@ -299,6 +299,7 @@ class JsonHeader(msgspec.Struct, frozen=True, omit_defaults=True):
299
299
  test=self.test,
300
300
  prepared=self.prepared,
301
301
  sender=self.sender,
302
+ receiver=self.receivers if self.receivers else (),
302
303
  )
303
304
 
304
305
  @classmethod
@@ -313,7 +314,7 @@ class JsonHeader(msgspec.Struct, frozen=True, omit_defaults=True):
313
314
  header.prepared,
314
315
  header.sender,
315
316
  header.test,
316
- receivers=(header.receiver,) if header.receiver else None,
317
+ receivers=header.receiver if header.receiver else None,
317
318
  schema=(
318
319
  "https://raw.githubusercontent.com/sdmx-twg/sdmx-json/"
319
320
  "develop/structure-message/tools/schemas/2.0.0/"
@@ -37,6 +37,7 @@ class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
37
37
  maxOccurs: Union[int, Literal["unbounded"]]
38
38
  isPresentational: bool
39
39
  localRepresentation: Optional[JsonRepresentation] = None
40
+ metadataAttributes: Sequence["JsonMetadataAttribute"] = ()
40
41
 
41
42
  def to_model(
42
43
  self, cs: Sequence[JsonConceptScheme], cls: Sequence[Codelist]
@@ -72,7 +73,7 @@ class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
72
73
  local_codes=codes,
73
74
  array_def=ab,
74
75
  local_enum_ref=local_enum_ref,
75
- components=(),
76
+ components=[a.to_model(cs, cls) for a in self.metadataAttributes],
76
77
  )
77
78
 
78
79
  @classmethod
@@ -94,6 +95,9 @@ class JsonMetadataAttribute(Struct, frozen=True, omit_defaults=True):
94
95
  minOccurs=min_occurs,
95
96
  maxOccurs=max_occurs,
96
97
  isPresentational=cmp.is_presentational,
98
+ metadataAttributes=[
99
+ JsonMetadataAttribute.from_model(c) for c in cmp.components
100
+ ],
97
101
  )
98
102
 
99
103
 
@@ -0,0 +1,108 @@
1
+ """SDMX-JSON document validation against JSON schemas."""
2
+
3
+ import json
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Any, Callable, Mapping, Match, Optional
7
+
8
+ from jsonschema import Draft202012Validator
9
+ from jsonschema.exceptions import ValidationError
10
+ from sdmxschemas import SDMX_JSON_20_DATA_PATH as SCHEMA_PATH_JSON20_DATA
11
+ from sdmxschemas import (
12
+ SDMX_JSON_20_METADATA_PATH as SCHEMA_PATH_JSON20_METADATA,
13
+ )
14
+ from sdmxschemas import (
15
+ SDMX_JSON_20_STRUCTURE_PATH as SCHEMA_PATH_JSON20_STRUCTURE,
16
+ )
17
+
18
+ from pysdmx import errors
19
+
20
+ _SCHEMA_FILES: Mapping[str, Path] = {
21
+ "structure": SCHEMA_PATH_JSON20_STRUCTURE,
22
+ "metadata": SCHEMA_PATH_JSON20_METADATA,
23
+ "data": SCHEMA_PATH_JSON20_DATA,
24
+ }
25
+
26
+
27
+ def _schema_for(instance: Mapping[str, Any]) -> dict[str, Any]:
28
+ schema_url = instance.get("meta", {}).get("schema")
29
+ p = next(p for p in _SCHEMA_FILES.values() if p.name in schema_url)
30
+ with p.open("r", encoding="utf-8") as f:
31
+ schema = json.load(f)
32
+ return schema
33
+
34
+
35
+ def validate_sdmx_json(input_str: str) -> None:
36
+ """Validates an SDMX-JSON message against the appropriate JSON schema.
37
+
38
+ Args: input_str: The SDMX-JSON message to validate.
39
+ Raises:
40
+ invalid: If the SDMX-JSON message does not validate against the schema.
41
+
42
+ """
43
+ instance = json.loads(input_str)
44
+ schema = _schema_for(instance)
45
+ validator = Draft202012Validator(schema)
46
+
47
+ failures = sorted(
48
+ validator.iter_errors(instance),
49
+ key=lambda e: (list(e.path), e.message),
50
+ )
51
+ if failures:
52
+
53
+ def compact(e: ValidationError) -> str:
54
+ path = "$" if not e.path else "$." + ".".join(map(str, e.path))
55
+ sub = " | ".join(
56
+ getattr(e, "context", [])
57
+ and [c.message for c in e.context]
58
+ or []
59
+ )
60
+ raw = f"{e.message} | {sub}"
61
+
62
+ patterns: list[tuple[str, Callable[[Match[str]], str]]] = [
63
+ (
64
+ r"Additional properties are not allowed.*'([^']+)'",
65
+ lambda m: f"unexpected property '{m.group(1)}'",
66
+ ),
67
+ (
68
+ r"is not of type '([^']+)'",
69
+ lambda m: f"invalid type (expected {m.group(1)})",
70
+ ),
71
+ (
72
+ r"""['"]?([^'"\n]+)['"]?\s+is not one of\s+\[([^\]]+)\]""",
73
+ lambda m: "invalid value {!r}"
74
+ " (expected one of: {})".format(
75
+ m.group(1),
76
+ ", ".join(
77
+ s.strip().strip("'\"")
78
+ for s in m.group(2).split(",")
79
+ ),
80
+ ),
81
+ ),
82
+ (
83
+ r"'([^']+)' is a required property",
84
+ lambda m: f"missing property '{m.group(1)}'",
85
+ ),
86
+ (
87
+ r"""does not match ['"]([^'"]+)['"]""",
88
+ lambda m: f"does not match required"
89
+ f" pattern {m.group(1)!r}",
90
+ ),
91
+ ]
92
+
93
+ msg: Optional[str] = next(
94
+ (
95
+ fmt(re.search(rx, raw)) # type: ignore[arg-type]
96
+ for rx, fmt in patterns
97
+ if re.search(rx, raw)
98
+ ),
99
+ None,
100
+ )
101
+ msg = msg or e.message
102
+ return f"{path}: {msg}"
103
+
104
+ summary = "; ".join(compact(e) for e in failures[:3])
105
+ more = (
106
+ f" (+{len(failures) - 3} more errors)" if len(failures) > 3 else ""
107
+ )
108
+ raise errors.Invalid("Validation Error", f"{summary}{more}")
@@ -3,20 +3,27 @@
3
3
  import msgspec
4
4
 
5
5
  from pysdmx import errors
6
+ from pysdmx.__extras_check import __check_json_extra
6
7
  from pysdmx.io.json.sdmxjson2.messages import JsonMetadataMessage
8
+ from pysdmx.io.json.sdmxjson2.reader.doc_validation import validate_sdmx_json
7
9
  from pysdmx.model import decoders
8
10
  from pysdmx.model.message import MetadataMessage
9
11
 
10
12
 
11
- def read(input_str: str) -> MetadataMessage:
13
+ def read(input_str: str, validate: bool = True) -> MetadataMessage:
12
14
  """Read an SDMX-JSON 2.0.0 Metadata Message.
13
15
 
14
16
  Args:
15
17
  input_str: SDMX-JSON reference metadata message to read.
18
+ validate: If True, the JSON data will be validated against the schemas.
16
19
 
17
20
  Returns:
18
21
  A pysdmx MetadataMessage
19
22
  """
23
+ if validate:
24
+ __check_json_extra()
25
+ validate_sdmx_json(input_str)
26
+
20
27
  try:
21
28
  msg = msgspec.json.Decoder(
22
29
  JsonMetadataMessage, dec_hook=decoders
@@ -3,20 +3,27 @@
3
3
  import msgspec
4
4
 
5
5
  from pysdmx import errors
6
+ from pysdmx.__extras_check import __check_json_extra
6
7
  from pysdmx.io.json.sdmxjson2.messages import JsonStructureMessage
8
+ from pysdmx.io.json.sdmxjson2.reader.doc_validation import validate_sdmx_json
7
9
  from pysdmx.model import decoders
8
10
  from pysdmx.model.message import StructureMessage
9
11
 
10
12
 
11
- def read(input_str: str) -> StructureMessage:
12
- """Read an SDMX-JSON 2.0.0 Stucture Message.
13
+ def read(input_str: str, validate: bool = True) -> StructureMessage:
14
+ """Read an SDMX-JSON 2.0.0 Structure Message.
13
15
 
14
16
  Args:
15
17
  input_str: SDMX-JSON structure message to read.
18
+ validate: If True, the JSON data will be validated against the schemas.
16
19
 
17
20
  Returns:
18
21
  A pysdmx StructureMessage
19
22
  """
23
+ if validate:
24
+ __check_json_extra()
25
+ validate_sdmx_json(input_str)
26
+
20
27
  try:
21
28
  msg = msgspec.json.Decoder(
22
29
  JsonStructureMessage, dec_hook=decoders