pysdmx 1.6.0__tar.gz → 1.7.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 (160) hide show
  1. {pysdmx-1.6.0 → pysdmx-1.7.0}/PKG-INFO +2 -2
  2. {pysdmx-1.6.0 → pysdmx-1.7.0}/pyproject.toml +22 -8
  3. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/__init__.py +1 -1
  4. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/input_processor.py +4 -4
  5. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/concept.py +2 -8
  6. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/code.py +35 -13
  7. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/concept.py +5 -8
  8. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/__ss_aux_reader.py +1 -2
  9. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/__structure_aux_reader.py +15 -10
  10. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/__structure_aux_writer.py +15 -13
  11. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/__write_data_aux.py +5 -3
  12. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/__write_structure_specific_aux.py +6 -2
  13. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/doc_validation.py +1 -3
  14. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx21/writer/generic.py +5 -3
  15. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/__init__.py +1 -3
  16. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/map.py +7 -9
  17. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/message.py +1 -4
  18. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/toolkit/pd/_data_utils.py +3 -4
  19. {pysdmx-1.6.0 → pysdmx-1.7.0}/LICENSE +0 -0
  20. {pysdmx-1.6.0 → pysdmx-1.7.0}/README.rst +0 -0
  21. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/__extras_check.py +0 -0
  22. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/__init__.py +0 -0
  23. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/dc/__init__.py +0 -0
  24. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/dc/_api.py +0 -0
  25. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/dc/query/__init__.py +0 -0
  26. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/dc/query/_model.py +0 -0
  27. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/dc/query/_parsing_model.py +0 -0
  28. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/dc/query/_parsing_util.py +0 -0
  29. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/dc/query/_py_parser.py +0 -0
  30. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/dc/query/_sql_parser.py +0 -0
  31. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/dc/query/util.py +0 -0
  32. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/fmr/__init__.py +0 -0
  33. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/fmr/maintenance.py +0 -0
  34. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/gds/__init__.py +0 -0
  35. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/qb/__init__.py +0 -0
  36. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/qb/availability.py +0 -0
  37. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/qb/data.py +0 -0
  38. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/qb/gds.py +0 -0
  39. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/qb/refmeta.py +0 -0
  40. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/qb/registration.py +0 -0
  41. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/qb/schema.py +0 -0
  42. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/qb/service.py +0 -0
  43. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/qb/structure.py +0 -0
  44. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/api/qb/util.py +0 -0
  45. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/errors.py +0 -0
  46. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/__init__.py +0 -0
  47. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/csv/__csv_aux_reader.py +0 -0
  48. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/csv/__csv_aux_writer.py +0 -0
  49. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/csv/__init__.py +0 -0
  50. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/csv/sdmx10/__init__.py +0 -0
  51. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/csv/sdmx10/reader/__init__.py +0 -0
  52. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/csv/sdmx10/writer/__init__.py +0 -0
  53. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/csv/sdmx20/__init__.py +0 -0
  54. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/csv/sdmx20/reader/__init__.py +0 -0
  55. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/csv/sdmx20/writer/__init__.py +0 -0
  56. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/csv/sdmx21/__init__.py +0 -0
  57. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/csv/sdmx21/reader/__init__.py +0 -0
  58. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/csv/sdmx21/writer/__init__.py +0 -0
  59. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/format.py +0 -0
  60. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/__init__.py +0 -0
  61. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/category.py +0 -0
  62. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/code.py +0 -0
  63. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/constraint.py +0 -0
  64. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/core.py +0 -0
  65. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/dataflow.py +0 -0
  66. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/dsd.py +0 -0
  67. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/map.py +0 -0
  68. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/org.py +0 -0
  69. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/pa.py +0 -0
  70. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/report.py +0 -0
  71. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/schema.py +0 -0
  72. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/messages/vtl.py +0 -0
  73. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/fusion/reader/__init__.py +0 -0
  74. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/gds/messages/__init__.py +0 -0
  75. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/gds/messages/agencies.py +0 -0
  76. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/gds/messages/catalog.py +0 -0
  77. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/gds/messages/sdmx_api.py +0 -0
  78. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/gds/messages/services.py +0 -0
  79. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/gds/messages/urn_resolver.py +0 -0
  80. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/gds/reader/__init__.py +0 -0
  81. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/__init__.py +0 -0
  82. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/agency.py +0 -0
  83. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/category.py +0 -0
  84. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/constraint.py +0 -0
  85. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/core.py +0 -0
  86. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/dataflow.py +0 -0
  87. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/dsd.py +0 -0
  88. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/map.py +0 -0
  89. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/pa.py +0 -0
  90. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/provider.py +0 -0
  91. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/report.py +0 -0
  92. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/schema.py +0 -0
  93. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/structure.py +0 -0
  94. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/messages/vtl.py +0 -0
  95. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/reader/__init__.py +0 -0
  96. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/reader/metadata.py +0 -0
  97. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/reader/structure.py +0 -0
  98. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/writer/__init__.py +0 -0
  99. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/writer/metadata.py +0 -0
  100. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/json/sdmxjson2/writer/structure.py +0 -0
  101. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/pd.py +0 -0
  102. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/reader.py +0 -0
  103. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/serde.py +0 -0
  104. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/writer.py +0 -0
  105. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/__allowed_lxml_errors.py +0 -0
  106. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/__data_aux.py +0 -0
  107. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/__init__.py +0 -0
  108. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/__parse_xml.py +0 -0
  109. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/__tokens.py +0 -0
  110. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/__write_aux.py +0 -0
  111. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/config.py +0 -0
  112. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/header.py +0 -0
  113. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx21/__init__.py +0 -0
  114. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx21/reader/__init__.py +0 -0
  115. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx21/reader/error.py +0 -0
  116. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx21/reader/generic.py +0 -0
  117. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx21/reader/structure.py +0 -0
  118. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx21/reader/structure_specific.py +0 -0
  119. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx21/reader/submission.py +0 -0
  120. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx21/writer/__init__.py +0 -0
  121. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx21/writer/error.py +0 -0
  122. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx21/writer/structure.py +0 -0
  123. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx21/writer/structure_specific.py +0 -0
  124. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx30/__init__.py +0 -0
  125. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx30/reader/__init__.py +0 -0
  126. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx30/reader/structure.py +0 -0
  127. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx30/reader/structure_specific.py +0 -0
  128. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx30/writer/__init__.py +0 -0
  129. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx30/writer/structure.py +0 -0
  130. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx30/writer/structure_specific.py +0 -0
  131. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx31/__init__.py +0 -0
  132. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx31/reader/__init__.py +0 -0
  133. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx31/reader/structure.py +0 -0
  134. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx31/reader/structure_specific.py +0 -0
  135. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx31/writer/__init__.py +0 -0
  136. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx31/writer/structure.py +0 -0
  137. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/sdmx31/writer/structure_specific.py +0 -0
  138. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/io/xml/utils.py +0 -0
  139. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/__base.py +0 -0
  140. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/category.py +0 -0
  141. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/code.py +0 -0
  142. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/concept.py +0 -0
  143. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/dataflow.py +0 -0
  144. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/dataset.py +0 -0
  145. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/gds.py +0 -0
  146. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/metadata.py +0 -0
  147. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/organisation.py +0 -0
  148. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/submission.py +0 -0
  149. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/model/vtl.py +0 -0
  150. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/py.typed +0 -0
  151. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/toolkit/__init__.py +0 -0
  152. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/toolkit/pd/__init__.py +0 -0
  153. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/toolkit/vtl/__init__.py +0 -0
  154. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/toolkit/vtl/_validations.py +0 -0
  155. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/toolkit/vtl/script_generation.py +0 -0
  156. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/toolkit/vtl/validation.py +0 -0
  157. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/util/__init__.py +0 -0
  158. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/util/_date_pattern_map.py +0 -0
  159. {pysdmx-1.6.0 → pysdmx-1.7.0}/src/pysdmx/util/_model_utils.py +0 -0
  160. {pysdmx-1.6.0 → pysdmx-1.7.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.6.0
3
+ Version: 1.7.0
4
4
  Summary: Your opinionated Python SDMX library
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -18,7 +18,7 @@ Provides-Extra: data
18
18
  Provides-Extra: dc
19
19
  Provides-Extra: vtl
20
20
  Provides-Extra: xml
21
- Requires-Dist: httpx (>=0)
21
+ Requires-Dist: httpx[http2] (>=0)
22
22
  Requires-Dist: lxml (>=5.2) ; extra == "all"
23
23
  Requires-Dist: lxml (>=5.2) ; extra == "xml"
24
24
  Requires-Dist: msgspec (>=0)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pysdmx"
3
- version = "1.6.0"
3
+ version = "1.7.0"
4
4
  description = "Your opinionated Python SDMX library"
5
5
  license = { text = "Apache-2.0" }
6
6
  readme = "README.rst"
@@ -21,7 +21,7 @@ classifiers = [
21
21
  "Typing :: Typed"
22
22
  ]
23
23
  dependencies = [
24
- "httpx>=0.*",
24
+ "httpx[http2]>=0.*",
25
25
  "msgspec>=0.*",
26
26
  "parsy>=2.1",
27
27
  ]
@@ -46,7 +46,7 @@ requires-poetry = ">=2.0"
46
46
  python = ">=3.9,<4.0"
47
47
 
48
48
  [tool.poetry.group.dev.dependencies]
49
- ruff = "^0.8.1"
49
+ ruff = ">=0.13.2"
50
50
  mypy = "^1.1.1"
51
51
  pytest = "^8.3.2"
52
52
  pytest-asyncio = "^0.21.1"
@@ -70,16 +70,30 @@ build-backend = "poetry.core.masonry.api"
70
70
  [tool.ruff]
71
71
  line-length = 79
72
72
  lint.select = [
73
- "ASYNC", "B", "C4", "C90", "D", "E", "F", "I", "LOG", "PT", "S", "SIM", "W"
74
- ]
75
- lint.ignore = [
76
- "E203", "D411", "F901", "S320"
73
+ "ASYNC", # flake8-async: checks for async/await best practices
74
+ "B", # flake8-bugbear: detect bugs and design problems
75
+ "C4", # flake8-comprehensions: better list/dict/set comprehensions
76
+ "C90", # mccabe: code complexity checker
77
+ "D", # pydocstyle: enforce docstring conventions
78
+ "DTZ", # flake8-datetimez: datetime best practices and timezone issues
79
+ "E", # pycodestyle errors
80
+ "ERA", # eradicate: Find commented-out code that should be removed
81
+ "F", # pyflakes: detect various errors
82
+ "FURB", # refurb: functional Python improvements and refactoring
83
+ "I", # isort: import sorting
84
+ "LOG", # flake8-logging: logging best practices
85
+ "PERF", # perflint: performance optimizations
86
+ "PT", # flake8-pytest-style: pytest best practices
87
+ "S", # flake8-bandit: security issues
88
+ "SIM", # flake8-simplify: code simplification suggestions
89
+ "W" # pycodestyle warnings
77
90
  ]
91
+ lint.ignore = ["D411", "E203", "F901"]
78
92
  lint.mccabe.max-complexity = 10
79
93
  lint.pydocstyle.convention = "google"
80
94
 
81
95
  [tool.ruff.lint.per-file-ignores]
82
- "tests/*" = ["S101", "S311", "D100", "D103", "D104"]
96
+ "tests/*" = ["DTZ", "D100", "D103", "D104", "PERF", "S101", "S311"]
83
97
 
84
98
  [tool.mypy]
85
99
  files = "src"
@@ -1,3 +1,3 @@
1
1
  """Your opinionated Python SDMX library."""
2
2
 
3
- __version__ = "1.6.0"
3
+ __version__ = "1.7.0"
@@ -3,11 +3,11 @@
3
3
  import csv
4
4
  import os.path
5
5
  from io import BytesIO, StringIO, TextIOWrapper
6
- from json import JSONDecodeError, loads
7
6
  from os import PathLike
8
7
  from pathlib import Path
9
8
  from typing import Optional, Tuple, Union
10
9
 
10
+ import msgspec
11
11
  from httpx import Client as httpx_Client
12
12
  from httpx import HTTPStatusError, create_ssl_context
13
13
 
@@ -29,7 +29,7 @@ def __check_xml(input_str: str) -> bool:
29
29
 
30
30
  def __check_csv(input_str: str) -> bool:
31
31
  try:
32
- max_length = len(input_str) if len(input_str) < 2048 else 2048
32
+ max_length = min(2048, len(input_str))
33
33
  dialect = csv.Sniffer().sniff(input_str[:max_length])
34
34
  control_csv_format = (
35
35
  dialect.delimiter == "," and dialect.quotechar == '"'
@@ -47,9 +47,9 @@ def __check_csv(input_str: str) -> bool:
47
47
 
48
48
  def __check_json(input_str: str) -> bool:
49
49
  try:
50
- loads(input_str)
50
+ msgspec.json.decode(input_str)
51
51
  return True
52
- except JSONDecodeError:
52
+ except msgspec.DecodeError:
53
53
  return False
54
54
 
55
55
 
@@ -17,6 +17,7 @@ class FusionConcept(msgspec.Struct, frozen=True):
17
17
  """Fusion-JSON payload for concepts."""
18
18
 
19
19
  id: str
20
+ urn: str
20
21
  names: Sequence[FusionString]
21
22
  representation: Optional[FusionRepresentation] = None
22
23
  descriptions: Optional[Sequence[FusionString]] = None
@@ -48,6 +49,7 @@ class FusionConcept(msgspec.Struct, frozen=True):
48
49
  description=d,
49
50
  codes=c,
50
51
  enum_ref=cl_ref,
52
+ urn=self.urn,
51
53
  )
52
54
 
53
55
 
@@ -63,18 +65,10 @@ class FusionConceptScheme(
63
65
  version: str = "1.0"
64
66
  items: Sequence[FusionConcept] = ()
65
67
 
66
- def __set_urn(self, concept: Concept) -> Concept:
67
- urn = (
68
- "urn:sdmx:org.sdmx.infomodel.conceptscheme.Concept="
69
- f"{self.agency}:{self.id}({self.version}).{concept.id}"
70
- )
71
- return msgspec.structs.replace(concept, urn=urn)
72
-
73
68
  def to_model(self, codelists: Sequence[FusionCodelist]) -> CS:
74
69
  """Converts a FusionConceptScheme to a standard concept scheme."""
75
70
  d = self.descriptions[0].value if self.descriptions else None
76
71
  concepts = [c.to_model(codelists) for c in self.items]
77
- concepts = [self.__set_urn(c) for c in concepts]
78
72
  return CS(
79
73
  id=self.id,
80
74
  name=self.names[0].value,
@@ -34,7 +34,7 @@ class JsonCode(NameableType, frozen=True, omit_defaults=True):
34
34
  parent: Optional[str] = None
35
35
 
36
36
  def __handle_date(self, datestr: str) -> datetime:
37
- return datetime.strptime(datestr, _VAL_FMT)
37
+ return datetime.strptime(datestr, _VAL_FMT) # noqa
38
38
 
39
39
  def __get_val(
40
40
  self, a: JsonAnnotation
@@ -49,26 +49,38 @@ class JsonCode(NameableType, frozen=True, omit_defaults=True):
49
49
 
50
50
  def to_model(self) -> Code:
51
51
  """Converts a JsonCode to a standard code."""
52
+ # Pre-filter annotations once outside the tuple creation
53
+ vf, vt = None, None
54
+
52
55
  if self.annotations:
53
- vp = [
54
- a for a in self.annotations if a.type == "FR_VALIDITY_PERIOD"
56
+ # Get validity period info
57
+ vp = next(
58
+ (
59
+ a
60
+ for a in self.annotations
61
+ if a.type == "FR_VALIDITY_PERIOD"
62
+ ),
63
+ None,
64
+ )
65
+ if vp:
66
+ vf, vt = self.__get_val(vp)
67
+
68
+ # Pre-filter non-validity period annotations
69
+ filtered_annotations = [
70
+ a.to_model()
71
+ for a in self.annotations
72
+ if a.type != "FR_VALIDITY_PERIOD"
55
73
  ]
56
74
  else:
57
- vp = None
58
- vf, vt = self.__get_val(vp[0]) if vp else (None, None)
75
+ filtered_annotations = []
76
+
59
77
  return Code(
60
78
  id=self.id,
61
79
  name=self.name,
62
80
  description=self.description,
63
81
  valid_from=vf,
64
82
  valid_to=vt,
65
- annotations=tuple(
66
- [
67
- a.to_model()
68
- for a in self.annotations
69
- if a.type != "FR_VALIDITY_PERIOD"
70
- ]
71
- ),
83
+ annotations=tuple(filtered_annotations),
72
84
  )
73
85
 
74
86
  @classmethod
@@ -113,13 +125,23 @@ class JsonCodelist(ItemSchemeType, frozen=True, omit_defaults=True):
113
125
 
114
126
  def to_model(self) -> Codelist:
115
127
  """Converts a JsonCodelist to a standard codelist."""
128
+ # Process codes in batches to reduce memory pressure
129
+ batch_size = 10000
130
+ all_codes = []
131
+
132
+ # Process in batches
133
+ for i in range(0, len(self.codes), batch_size):
134
+ batch = self.codes[i : i + batch_size]
135
+ batch_models = [code.to_model() for code in batch]
136
+ all_codes.extend(batch_models)
137
+
116
138
  return Codelist(
117
139
  id=self.id,
118
140
  name=self.name,
119
141
  agency=self.agency,
120
142
  description=self.description,
121
143
  version=self.version,
122
- items=tuple([i.to_model() for i in self.codes]),
144
+ items=tuple(all_codes),
123
145
  annotations=tuple([a.to_model() for a in self.annotations]),
124
146
  is_external_reference=self.isExternalReference,
125
147
  is_partial=self.isPartial,
@@ -9,6 +9,7 @@ from pysdmx.io.json.sdmxjson2.messages.code import JsonCodelist
9
9
  from pysdmx.io.json.sdmxjson2.messages.core import (
10
10
  ItemSchemeType,
11
11
  JsonAnnotation,
12
+ JsonLink,
12
13
  JsonRepresentation,
13
14
  NameableType,
14
15
  )
@@ -29,6 +30,7 @@ class JsonConcept(NameableType, frozen=True, omit_defaults=True):
29
30
  coreRepresentation: Optional[JsonRepresentation] = None
30
31
  parent: Optional[str] = None
31
32
  isoConceptReference: Optional[IsoConceptReference] = None
33
+ links: Sequence[JsonLink] = ()
32
34
 
33
35
  def to_model(self, codelists: Sequence[Codelist]) -> Concept:
34
36
  """Converts a JsonConcept to a standard concept."""
@@ -48,6 +50,8 @@ class JsonConcept(NameableType, frozen=True, omit_defaults=True):
48
50
  facets = None
49
51
  codes = None
50
52
  cl_ref = None
53
+ urn_lnk = [lnk for lnk in self.links if lnk.rel == "self"]
54
+ c_urn = urn_lnk[0].urn if len(urn_lnk) > 0 else None
51
55
  return Concept(
52
56
  id=self.id,
53
57
  dtype=dt,
@@ -56,6 +60,7 @@ class JsonConcept(NameableType, frozen=True, omit_defaults=True):
56
60
  description=self.description,
57
61
  codes=codes,
58
62
  enum_ref=cl_ref,
63
+ urn=c_urn,
59
64
  )
60
65
 
61
66
  @classmethod
@@ -95,18 +100,10 @@ class JsonConceptScheme(ItemSchemeType, frozen=True, omit_defaults=True):
95
100
 
96
101
  concepts: Sequence[JsonConcept] = ()
97
102
 
98
- def __set_urn(self, concept: Concept) -> Concept:
99
- urn = (
100
- "urn:sdmx:org.sdmx.infomodel.conceptscheme.Concept="
101
- f"{self.agency}:{self.id}({self.version}).{concept.id}"
102
- )
103
- return msgspec.structs.replace(concept, urn=urn)
104
-
105
103
  def to_model(self, codelists: Sequence[JsonCodelist]) -> ConceptScheme:
106
104
  """Converts a JsonConceptScheme to a standard concept scheme."""
107
105
  cls = [cl.to_model() for cl in codelists]
108
106
  concepts = [c.to_model(cls) for c in self.concepts]
109
- concepts = [self.__set_urn(c) for c in concepts]
110
107
  return ConceptScheme(
111
108
  id=self.id,
112
109
  name=self.name,
@@ -30,8 +30,7 @@ def _reading_str_series(dataset: Dict[str, Any]) -> pd.DataFrame:
30
30
  if OBS in data:
31
31
  del keys[OBS]
32
32
  data[OBS] = add_list(data[OBS])
33
- for j in data[OBS]:
34
- test_list.append({**keys, **j})
33
+ test_list.extend([{**keys, **j} for j in data[OBS]])
35
34
  else:
36
35
  test_list.append(keys)
37
36
  test_list, df = __process_df(test_list, df)
@@ -397,7 +397,7 @@ class StructureParser(Struct):
397
397
  """
398
398
  if json_fac is None:
399
399
  return
400
- for key, _value in json_fac.items():
400
+ for key in json_fac:
401
401
  if key == TEXT_TYPE and json_fac[TEXT_TYPE] in list(DataType):
402
402
  json_obj["dtype"] = DataType(json_fac[TEXT_TYPE])
403
403
 
@@ -907,9 +907,9 @@ class StructureParser(Struct):
907
907
  item_json_info = self.__format_name_description(item_json_info)
908
908
  if CONTACT in item_json_info and item_name_class == AGENCY:
909
909
  item_json_info[CONTACT] = add_list(item_json_info[CONTACT])
910
- contacts = []
911
- for e in item_json_info[CONTACT]:
912
- contacts.append(self.__format_contact(e))
910
+ contacts = [
911
+ self.__format_contact(e) for e in item_json_info[CONTACT]
912
+ ]
913
913
  item_json_info[CONTACT.lower() + "s"] = contacts
914
914
  del item_json_info[CONTACT]
915
915
 
@@ -949,9 +949,11 @@ class StructureParser(Struct):
949
949
  group_dimensions = [group_dimensions]
950
950
 
951
951
  group["dimensions"] = [
952
- d[DIM_REF]
953
- if isinstance(d[DIM_REF], str)
954
- else d[DIM_REF][REF][ID]
952
+ (
953
+ d[DIM_REF]
954
+ if isinstance(d[DIM_REF], str)
955
+ else d[DIM_REF][REF][ID]
956
+ )
955
957
  for d in group_dimensions
956
958
  ]
957
959
 
@@ -994,9 +996,12 @@ class StructureParser(Struct):
994
996
  items = []
995
997
  if item in element:
996
998
  element[item] = add_list(element[item])
997
- for item_elem in element[item]:
998
- # Dynamic
999
- items.append(self.__format_item(item_elem, item))
999
+ items.extend(
1000
+ [
1001
+ self.__format_item(item_elem, item)
1002
+ for item_elem in element[item]
1003
+ ]
1004
+ )
1000
1005
  del element[item]
1001
1006
  element["items"] = items
1002
1007
  element = self.__format_agency(element)
@@ -310,9 +310,9 @@ def __write_maintainable(
310
310
  f"{str(maintainable.is_external_reference).lower()!r}"
311
311
  )
312
312
  if not references_30 and not (isinstance(maintainable, AgencyScheme)):
313
- outfile["Attributes"] += (
314
- f" isFinal={str(maintainable.is_final).lower()!r}"
315
- )
313
+ outfile[
314
+ "Attributes"
315
+ ] += f" isFinal={str(maintainable.is_final).lower()!r}"
316
316
 
317
317
  if isinstance(maintainable.agency, str):
318
318
  outfile["Attributes"] += f" agencyID={maintainable.agency!r}"
@@ -462,7 +462,7 @@ def __write_components( # noqa: C901
462
462
  )
463
463
 
464
464
  position = 1
465
- for _, comps in components.items():
465
+ for comps in components.values():
466
466
  if comps:
467
467
  role_name = ROLE_MAPPING[comps[0].role]
468
468
  if role_name == MEASURE:
@@ -809,9 +809,9 @@ def __write_scheme( # noqa: C901
809
809
  DSD,
810
810
  DFW,
811
811
  ]:
812
- data["Attributes"] += (
813
- f" isPartial={str(item_scheme.is_partial).lower()!r}"
814
- )
812
+ data[
813
+ "Attributes"
814
+ ] += f" isPartial={str(item_scheme.is_partial).lower()!r}"
815
815
  if scheme in [
816
816
  RULE_SCHEME,
817
817
  UDO_SCHEME,
@@ -820,9 +820,9 @@ def __write_scheme( # noqa: C901
820
820
  CUSTOM_TYPE_SCHEME,
821
821
  NAME_PER_SCHEME,
822
822
  ]:
823
- data["Attributes"] += (
824
- f" {_write_vtl(item_scheme, indent, references_30)}"
825
- )
823
+ data[
824
+ "Attributes"
825
+ ] += f" {_write_vtl(item_scheme, indent, references_30)}"
826
826
 
827
827
  outfile = ""
828
828
 
@@ -1129,9 +1129,11 @@ def _write_vtl( # noqa: C901
1129
1129
  ref_codelist = (
1130
1130
  item_or_scheme.codelist
1131
1131
  if isinstance(item_or_scheme.codelist, Reference)
1132
- else parse_urn(item_or_scheme.codelist)
1133
- if isinstance(item_or_scheme.codelist, str)
1134
- else parse_short_urn(item_or_scheme.codelist.short_urn)
1132
+ else (
1133
+ parse_urn(item_or_scheme.codelist)
1134
+ if isinstance(item_or_scheme.codelist, str)
1135
+ else parse_short_urn(item_or_scheme.codelist.short_urn)
1136
+ )
1135
1137
  )
1136
1138
  if references_30:
1137
1139
  data += (
@@ -54,13 +54,15 @@ def writing_validation(dataset: PandasDataset) -> None:
54
54
  for comp in dataset.structure.components
55
55
  if comp.role in (Role.DIMENSION, Role.MEASURE)
56
56
  ]
57
- for att in dataset.structure.components.attributes:
57
+ required_components.extend(
58
+ att.id
59
+ for att in dataset.structure.components.attributes
58
60
  if (
59
61
  att.required
60
62
  and att.attachment_level is not None
61
63
  and att.attachment_level != "D"
62
- ):
63
- required_components.append(att.id)
64
+ )
65
+ )
64
66
  non_required = [
65
67
  comp.id
66
68
  for comp in dataset.structure.components
@@ -198,8 +198,12 @@ def __group_processing(
198
198
  .to_dict(orient="records")
199
199
  )
200
200
 
201
- for record in grouped_data:
202
- out_list.append(__format_group_str(record, group["group_id"]))
201
+ out_list.extend(
202
+ [
203
+ __format_group_str(record, group["group_id"])
204
+ for record in grouped_data
205
+ ]
206
+ )
203
207
 
204
208
  return "".join(out_list)
205
209
 
@@ -39,9 +39,7 @@ def validate_doc(input_str: str) -> None:
39
39
  doc = etree.parse(bytes_infile, parser=parser)
40
40
  if not xmlschema.validate(doc):
41
41
  log_errors = list(xmlschema.error_log) # type: ignore[call-overload]
42
- unhandled_errors = []
43
- for e in log_errors:
44
- unhandled_errors.append(e.message)
42
+ unhandled_errors = [e.message for e in log_errors]
45
43
  severe_errors = unhandled_errors.copy()
46
44
  for e in unhandled_errors:
47
45
  for allowed_error in ALLOWED_ERRORS_CONTENT:
@@ -327,10 +327,12 @@ def __group_processing(
327
327
  .to_dict(orient="records")
328
328
  )
329
329
 
330
- for record in grouped_data:
331
- out_list.append(
330
+ out_list.extend(
331
+ [
332
332
  __format_group_str(record, group_id, dimensions, attribute)
333
- )
333
+ for record in grouped_data
334
+ ]
335
+ )
334
336
 
335
337
  return "".join(out_list)
336
338
 
@@ -131,9 +131,7 @@ def decoders(type: Type, obj: Any) -> Any: # type: ignore[type-arg]
131
131
  target types
132
132
  """
133
133
  if type is Components:
134
- comps = []
135
- for item in obj:
136
- comps.append(msgspec.convert(item, Component))
134
+ comps = [msgspec.convert(item, Component) for item in obj]
137
135
  return Components(comps)
138
136
  else:
139
137
  raise NotImplementedError(f"Objects of type {type} are not supported")
@@ -486,9 +486,7 @@ class StructureMap(MaintainableArtefact, frozen=True, omit_defaults=True):
486
486
  """Return the number of mapping rules in the structure map."""
487
487
  return len(self.maps)
488
488
 
489
- def __getitem__(
490
- self, id_: str
491
- ) -> Optional[
489
+ def __getitem__(self, id_: str) -> Optional[
492
490
  Sequence[
493
491
  Union[
494
492
  ComponentMap,
@@ -500,12 +498,12 @@ class StructureMap(MaintainableArtefact, frozen=True, omit_defaults=True):
500
498
  ]
501
499
  ]:
502
500
  """Return the mapping rules for the supplied component."""
503
- out = []
504
- for m in self.maps:
505
- if (
506
- hasattr(m, "source") and (m.source == id_ or id_ in m.source)
507
- ) or (isinstance(m, FixedValueMap) and m.target == id_):
508
- out.append(m)
501
+ out = [
502
+ m
503
+ for m in self.maps
504
+ if (hasattr(m, "source") and (m.source == id_ or id_ in m.source))
505
+ or (isinstance(m, FixedValueMap) and m.target == id_)
506
+ ]
509
507
  if len(out) == 0:
510
508
  return None
511
509
  else:
@@ -168,10 +168,7 @@ class StructureMessage(Struct, repr_omit_defaults=True, frozen=True):
168
168
  raise NotFound(
169
169
  f"No {type_.__name__} found in message.",
170
170
  )
171
- structures = []
172
- for element in self.structures:
173
- if isinstance(element, type_):
174
- structures.append(element)
171
+ structures = [e for e in self.structures if isinstance(e, type_)]
175
172
  return structures
176
173
 
177
174
  def __get_enumerations(
@@ -53,15 +53,14 @@ def get_codes(
53
53
  dimension_code: str, structure: Schema, data: pd.DataFrame
54
54
  ) -> Tuple[List[str], List[str], List[Dict[str, Any]]]:
55
55
  """This function divides the components in Series and Obs."""
56
- series_codes = []
57
56
  groups = structure.groups
58
57
  group_codes = []
59
58
  obs_codes = [dimension_code, structure.components.measures[0].id]
60
59
 
61
60
  # Getting the series and obs codes
62
- for dim in structure.components.dimensions:
63
- if dim.id != dimension_code:
64
- series_codes.append(dim.id)
61
+ series_codes = [
62
+ d.id for d in structure.components.dimensions if d.id != dimension_code
63
+ ]
65
64
 
66
65
  # Adding the attributes based on the attachment level
67
66
  for att in structure.components.attributes:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes