pysdmx 1.8.1__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.
- {pysdmx-1.8.1 → pysdmx-1.9.0}/PKG-INFO +4 -1
- {pysdmx-1.8.1 → pysdmx-1.9.0}/pyproject.toml +4 -2
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/__extras_check.py +14 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/__init__.py +1 -1
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/category.py +69 -41
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/category.py +76 -43
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/core.py +2 -1
- pysdmx-1.9.0/src/pysdmx/io/json/sdmxjson2/reader/doc_validation.py +108 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/reader/metadata.py +8 -1
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/reader/structure.py +9 -2
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/reader.py +18 -4
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/__data_aux.py +9 -4
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/__parse_xml.py +2 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/__structure_aux_reader.py +70 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/__structure_aux_writer.py +63 -9
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/__tokens.py +3 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/__write_aux.py +35 -30
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/header.py +7 -1
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/__base.py +1 -1
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/category.py +6 -1
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/message.py +22 -6
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/util/_model_utils.py +40 -3
- {pysdmx-1.8.1 → pysdmx-1.9.0}/LICENSE +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/README.rst +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/dc/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/dc/_api.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/_model.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/_parsing_model.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/_parsing_util.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/_py_parser.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/_sql_parser.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/dc/query/util.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/fmr/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/fmr/maintenance.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/gds/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/qb/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/qb/availability.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/qb/data.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/qb/gds.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/qb/refmeta.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/qb/registration.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/qb/schema.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/qb/service.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/qb/structure.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/api/qb/util.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/errors.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/csv/__csv_aux_reader.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/csv/__csv_aux_writer.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/csv/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx10/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx10/reader/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx10/writer/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx20/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx20/reader/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx20/writer/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx21/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx21/reader/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/csv/sdmx21/writer/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/format.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/input_processor.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/code.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/concept.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/constraint.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/core.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/dataflow.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/dsd.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/map.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/metadataflow.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/mpa.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/msd.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/org.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/pa.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/report.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/schema.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/messages/vtl.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/fusion/reader/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/messages/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/messages/agencies.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/messages/catalog.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/messages/sdmx_api.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/messages/services.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/messages/urn_resolver.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/gds/reader/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/agency.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/code.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/concept.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/constraint.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/dataflow.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/dsd.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/map.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/metadataflow.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/mpa.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/msd.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/pa.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/provider.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/report.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/schema.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/structure.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/messages/vtl.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/reader/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/writer/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/writer/metadata.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/json/sdmxjson2/writer/structure.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/pd.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/serde.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/writer.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/__allowed_lxml_errors.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/__ss_aux_reader.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/__write_data_aux.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/__write_structure_specific_aux.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/config.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/doc_validation.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/reader/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/reader/error.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/reader/generic.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/reader/structure.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/reader/structure_specific.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/reader/submission.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/writer/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/writer/error.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/writer/generic.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/writer/structure.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx21/writer/structure_specific.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/reader/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/reader/structure.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/reader/structure_specific.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/writer/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/writer/structure.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx30/writer/structure_specific.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/reader/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/reader/structure.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/reader/structure_specific.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/writer/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/writer/structure.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/sdmx31/writer/structure_specific.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/io/xml/utils.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/code.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/concept.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/dataflow.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/dataset.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/gds.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/map.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/metadata.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/organisation.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/submission.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/model/vtl.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/py.typed +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/toolkit/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/toolkit/pd/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/toolkit/pd/_data_utils.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/toolkit/vtl/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/toolkit/vtl/_validations.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/toolkit/vtl/script_generation.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/toolkit/vtl/validation.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/util/__init__.py +0 -0
- {pysdmx-1.8.1 → pysdmx-1.9.0}/src/pysdmx/util/_date_pattern_map.py +0 -0
- {pysdmx-1.8.1 → 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.
|
|
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.
|
|
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"
|
|
@@ -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,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
|
|
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=[
|
|
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
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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):
|
|
@@ -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
|
|
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=[
|
|
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
|
|
130
|
-
|
|
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
|
|
220
|
-
|
|
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=
|
|
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/"
|
|
@@ -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
|
|
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
|
|
@@ -26,11 +26,18 @@ def read_sdmx( # noqa: C901
|
|
|
26
26
|
|
|
27
27
|
Check the :ref:`formats supported <io-reader-formats-supported>`
|
|
28
28
|
|
|
29
|
+
.. important::
|
|
30
|
+
When reading a SDMX-JSON structure message, you can read it
|
|
31
|
+
without installing the pysdmx[json] extra
|
|
32
|
+
by passing the parameter ``validate=False`` to this function.
|
|
33
|
+
Otherwise, if not installed, an error will be raised
|
|
34
|
+
if using ``validate=True``, as it is the default value.
|
|
35
|
+
|
|
29
36
|
Args:
|
|
30
37
|
sdmx_document: Path to file
|
|
31
38
|
(`pathlib.Path <https://docs.python.org/3/library/pathlib.html>`_),
|
|
32
39
|
URL, or string.
|
|
33
|
-
validate: Validate the input file (only for SDMX-ML).
|
|
40
|
+
validate: Validate the input file (only for SDMX-ML and SDMX-JSON).
|
|
34
41
|
pem: When using a URL, in case the service exposed
|
|
35
42
|
a certificate created by an unknown certificate
|
|
36
43
|
authority, you can pass a PEM file for this
|
|
@@ -78,7 +85,7 @@ def read_sdmx( # noqa: C901
|
|
|
78
85
|
read as read_struct,
|
|
79
86
|
)
|
|
80
87
|
|
|
81
|
-
struct_msg = read_struct(input_str)
|
|
88
|
+
struct_msg = read_struct(input_str, validate=validate)
|
|
82
89
|
header = struct_msg.header
|
|
83
90
|
result_structures = (
|
|
84
91
|
struct_msg.structures if struct_msg.structures else []
|
|
@@ -88,7 +95,7 @@ def read_sdmx( # noqa: C901
|
|
|
88
95
|
read as read_refmeta,
|
|
89
96
|
)
|
|
90
97
|
|
|
91
|
-
ref_msg = read_refmeta(input_str)
|
|
98
|
+
ref_msg = read_refmeta(input_str, validate=validate)
|
|
92
99
|
header = ref_msg.header
|
|
93
100
|
reports = ref_msg.get_reports()
|
|
94
101
|
elif read_format == Format.DATA_SDMX_ML_2_1_GEN:
|
|
@@ -230,6 +237,13 @@ def get_datasets(
|
|
|
230
237
|
not equal to AllDimensions or Generic formats
|
|
231
238
|
- Execution of VTL scripts over PandasDataset
|
|
232
239
|
|
|
240
|
+
.. important::
|
|
241
|
+
When reading a SDMX-JSON structure message (as the structure argument),
|
|
242
|
+
you can read it without installing the pysdmx[json] extra
|
|
243
|
+
by passing the parameter ``validate=False`` to this function.
|
|
244
|
+
Otherwise, if not installed, an error will be raised
|
|
245
|
+
if using ``validate=True``, as it is the default value.
|
|
246
|
+
|
|
233
247
|
Args:
|
|
234
248
|
data: Path to file
|
|
235
249
|
(`pathlib.Path <https://docs.python.org/3/library/pathlib.html>`_),
|
|
@@ -238,7 +252,7 @@ def get_datasets(
|
|
|
238
252
|
Path to file
|
|
239
253
|
(`pathlib.Path <https://docs.python.org/3/library/pathlib.html>`_),
|
|
240
254
|
URL, or string for the structure message, if needed.
|
|
241
|
-
validate: Validate the input file (only for SDMX-ML).
|
|
255
|
+
validate: Validate the input file (only for SDMX-ML and SDMX-JSON).
|
|
242
256
|
pem: When using a URL, in case the service exposed
|
|
243
257
|
a certificate created by an unknown certificate
|
|
244
258
|
authority, you can pass a PEM file for this
|