Skip to content

Commit

Permalink
Merge pull request #150 from khaeru/issue/149
Browse files Browse the repository at this point in the history
Fix #149
  • Loading branch information
khaeru authored Dec 20, 2023
2 parents c56574a + fc8fc0e commit cd3ecb2
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 178 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ See https://sdmx1.readthedocs.io/en/latest/ for the latest docs per the ``main``
License
-------

Copyright 2014–2023, `sdmx1 developers <https://github.com/khaeru/sdmx/graphs/contributors>`_
Copyright 2014–2024, `sdmx1 developers <https://github.com/khaeru/sdmx/graphs/contributors>`_

Licensed under the Apache License, Version 2.0 (the “License”); you may not use these files except in compliance with the License.
You may obtain a copy of the License:
Expand Down
152 changes: 76 additions & 76 deletions doc/api/model-common-list.rst
Original file line number Diff line number Diff line change
@@ -1,78 +1,78 @@
.. This file is auto-generated by doc/conf.py.
:obj:`~.Agency`
:obj:`~.AgencyScheme`
:obj:`~.AnnotableArtefact`
:obj:`~.Annotation`
:obj:`~.AttributeDescriptor`
:obj:`~.AttributeRelationship`
:obj:`~.AttributeValue`
:obj:`~.Categorisation`
:obj:`~.Category`
:obj:`~.CategoryScheme`
:obj:`~.Code`
:obj:`~.Codelist`
:obj:`~.Component`
:obj:`~.ComponentList`
:obj:`~.Concept`
:obj:`~.ConceptScheme`
:obj:`~.ConstrainableArtefact`
:obj:`~.ConstraintRole`
:obj:`~.Contact`
:obj:`~.CubeRegion`
:obj:`~.CustomType`
:obj:`~.CustomTypeScheme`
:obj:`~.DEFAULT_LOCALE`
:obj:`~.DataAttribute`
:obj:`~.DataConsumer`
:obj:`~.DataConsumerScheme`
:obj:`~.DataProvider`
:obj:`~.DataProviderScheme`
:obj:`~.Datasource`
:obj:`~.Dimension`
:obj:`~.DimensionComponent`
:obj:`~.DimensionDescriptor`
:obj:`~.DimensionRelationship`
:obj:`~.EndPeriod`
:obj:`~.Facet`
:obj:`~.FacetType`
:obj:`~.FromVTLSpaceKey`
:obj:`~.GroupDimensionDescriptor`
:obj:`~.GroupKey`
:obj:`~.GroupRelationship`
:obj:`~.ISOConceptReference`
:obj:`~.IdentifiableArtefact`
:obj:`~.InternationalString`
:obj:`~.Item`
:obj:`~.ItemScheme`
:obj:`~.Key`
:obj:`~.KeyValue`
:obj:`~.MaintainableArtefact`
:obj:`~.MetadataTargetRegion`
:obj:`~.NamePersonalisation`
:obj:`~.NamePersonalisationScheme`
:obj:`~.NameableArtefact`
:obj:`~.Organisation`
:obj:`~.OrganisationScheme`
:obj:`~.ProvisionAgreement`
:obj:`~.QueryDatasource`
:obj:`~.RESTDatasource`
:obj:`~.Representation`
:obj:`~.Ruleset`
:obj:`~.RulesetScheme`
:obj:`~.SeriesKey`
:obj:`~.SimpleDatasource`
:obj:`~.StartPeriod`
:obj:`~.Structure`
:obj:`~.StructureUsage`
:obj:`~.TimeDimension`
:obj:`~.TimeKeyValue`
:obj:`~.ToVTLSpaceKey`
:obj:`~.Transformation`
:obj:`~.TransformationScheme`
:obj:`~.UserDefinedOperator`
:obj:`~.UserDefinedOperatorScheme`
:obj:`~.VTLConceptMapping`
:obj:`~.VTLDataflowMapping`
:obj:`~.VTLMappingScheme`
:obj:`~.VersionableArtefact`
:obj:`~.common.Agency`
:obj:`~.common.AgencyScheme`
:obj:`~.common.AnnotableArtefact`
:obj:`~.common.Annotation`
:obj:`~.common.AttributeDescriptor`
:obj:`~.common.AttributeRelationship`
:obj:`~.common.AttributeValue`
:obj:`~.common.Categorisation`
:obj:`~.common.Category`
:obj:`~.common.CategoryScheme`
:obj:`~.common.Code`
:obj:`~.common.Codelist`
:obj:`~.common.Component`
:obj:`~.common.ComponentList`
:obj:`~.common.Concept`
:obj:`~.common.ConceptScheme`
:obj:`~.common.ConstrainableArtefact`
:obj:`~.common.ConstraintRole`
:obj:`~.common.Contact`
:obj:`~.common.CubeRegion`
:obj:`~.common.CustomType`
:obj:`~.common.CustomTypeScheme`
:obj:`~.common.DEFAULT_LOCALE`
:obj:`~.common.DataAttribute`
:obj:`~.common.DataConsumer`
:obj:`~.common.DataConsumerScheme`
:obj:`~.common.DataProvider`
:obj:`~.common.DataProviderScheme`
:obj:`~.common.Datasource`
:obj:`~.common.Dimension`
:obj:`~.common.DimensionComponent`
:obj:`~.common.DimensionDescriptor`
:obj:`~.common.DimensionRelationship`
:obj:`~.common.EndPeriod`
:obj:`~.common.Facet`
:obj:`~.common.FacetType`
:obj:`~.common.FromVTLSpaceKey`
:obj:`~.common.GroupDimensionDescriptor`
:obj:`~.common.GroupKey`
:obj:`~.common.GroupRelationship`
:obj:`~.common.ISOConceptReference`
:obj:`~.common.IdentifiableArtefact`
:obj:`~.common.InternationalString`
:obj:`~.common.Item`
:obj:`~.common.ItemScheme`
:obj:`~.common.Key`
:obj:`~.common.KeyValue`
:obj:`~.common.MaintainableArtefact`
:obj:`~.common.MetadataTargetRegion`
:obj:`~.common.NamePersonalisation`
:obj:`~.common.NamePersonalisationScheme`
:obj:`~.common.NameableArtefact`
:obj:`~.common.Organisation`
:obj:`~.common.OrganisationScheme`
:obj:`~.common.ProvisionAgreement`
:obj:`~.common.QueryDatasource`
:obj:`~.common.RESTDatasource`
:obj:`~.common.Representation`
:obj:`~.common.Ruleset`
:obj:`~.common.RulesetScheme`
:obj:`~.common.SeriesKey`
:obj:`~.common.SimpleDatasource`
:obj:`~.common.StartPeriod`
:obj:`~.common.Structure`
:obj:`~.common.StructureUsage`
:obj:`~.common.TimeDimension`
:obj:`~.common.TimeKeyValue`
:obj:`~.common.ToVTLSpaceKey`
:obj:`~.common.Transformation`
:obj:`~.common.TransformationScheme`
:obj:`~.common.UserDefinedOperator`
:obj:`~.common.UserDefinedOperatorScheme`
:obj:`~.common.VTLConceptMapping`
:obj:`~.common.VTLDataflowMapping`
:obj:`~.common.VTLMappingScheme`
:obj:`~.common.VersionableArtefact`
18 changes: 16 additions & 2 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# -- Project information ---------------------------------------------------------------

project = "sdmx"
copyright = "2014–2023 sdmx1 developers"
copyright = "2014–2024 sdmx1 developers"


# -- General configuration -------------------------------------------------------------
Expand All @@ -24,6 +24,15 @@
"IPython.sphinxext.ipython_directive",
]

nitpicky = True

rst_prolog = """
.. role:: py(code)
:language: python
.. role:: xml(code)
:language: xml
"""

# -- Options for HTML output -----------------------------------------------------------

# The theme to use for HTML and HTML Help pages.
Expand Down Expand Up @@ -69,6 +78,10 @@ def linkcode_resolve(domain, info):
return f"https://github.com/khaeru/sdmx/tree/main/{filename}.py"


# -- Options for sphinx.ext.napoleon ---------------------------------------------------

napolean_preprocess_types = True

# -- Options for sphinx.ext.todo -------------------------------------------------------

# If True, todo and todolist produce output, else they produce nothing
Expand All @@ -90,7 +103,8 @@ def setup(app):

# Update files containing lists of classes
for mod in "common", "v21", "v30":
prefix = f"{mod}." if mod != "common" else ""
# prefix = f"{mod}." if mod != "common" else ""
prefix = f"{mod}."
path = Path(__file__).parent.joinpath("api", f"model-{mod}-list.rst")
with open(path, "w") as f:
f.write(".. This file is auto-generated by doc/conf.py.\n\n")
Expand Down
2 changes: 1 addition & 1 deletion doc/license.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
License
=======

Copyright 2014–2023, `sdmx1 developers <https://github.com/khaeru/sdmx/graphs/contributors>`_.
Copyright 2014–2024, `sdmx1 developers <https://github.com/khaeru/sdmx/graphs/contributors>`_.

Licensed under the Apache License, Version 2.0 (the “License”); you may not use
these files except in compliance with the License. You may obtain a copy of the
Expand Down
8 changes: 7 additions & 1 deletion doc/whatsnew.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ Next release

- Python 3.12 (released 2023-10-02) is fully supported (:pull:`145`).
- Bugfix: :py:`dsd=...` argument supplied to the SDMX-ML reader ignored in v2.11.0 and later, causing a warning (:pull:`147`; thanks :gh-user:`miccoli` for :issue:`146`).
- Bugfix: attribute :xml:`<str:Attribute assignmentStatus="…">` not read from SDMX-ML (:pull:`150`, thanks :gh-user:`goatsweater` for :issue:`149`).
- Bugfix: items not written by :mod:`.writer.xml` (:pull:`150`, thanks :gh-user:`goatsweater` for :issue:`149`).

- :attr:`.Annotation.title` → :xml:`<com:AnnotationTitle>…<com:AnnotationTitle/>`.
- :attr:`.DimensionComponent.order` → :xml:`<str:Dimension position="…">`.
- :class:`.PrimaryMeasureRelationship` → specific reference to the :attr:`~.IdentifiableArtefact.id` of the :class:`.PrimaryMeasure` within the associated :class:`DataStructureDefinition <.BaseDataStructureDefinition>`.

v2.12.0 (2023-10-11)
====================
Expand Down Expand Up @@ -297,7 +303,7 @@ v2.1.0 (2021-02-22)
- :mod:`.reader.xml` internals reworked for significant speedups in parsing of SDMX-ML (:pull:`58`).
- New convenience method :meth:`.StructureMessage.get` to retrieve objects by ID across the multiple collections in StructureMessage (:pull:`58`).
- New convenience method :meth:`.AnnotableArtefact.pop_annotation` to locate, remove, and return a Annotation, e.g. by its ID (:pull:`58`).
- :func:`len` of a :class:`.DataKeySet` gives the length of :attr:`.DataKeySet.keys` (:pull:`58`).
- :func:`len` of a :class:`DataKeySet <.BaseDataKeySet>` gives the length of :attr:`.DataKeySet.keys` (:pull:`58`).

v2.0.1 (2021-01-31)
===================
Expand Down
7 changes: 7 additions & 0 deletions sdmx/model/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,13 @@ class DimensionDescriptor(ComponentList[DimensionComponent]):

_Component = Dimension

def __post_init__(self):
try:
# Sort components by already assigned 'order' attributes
self.components = sorted(self.components, key=lambda c: c.order)
except TypeError:
pass # Some or all of the order attributes are None

def assign_order(self):
"""Assign the :attr:`.DimensionComponent.order` attribute.
Expand Down
10 changes: 7 additions & 3 deletions sdmx/reader/xml/v21.py
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,11 @@ def _component(reader: Reader, elem):
args["order"] = int(elem.attrib["position"])
except KeyError:
pass
# DataAttributeOnly
us = elem.attrib.get("assignmentStatus")
if us:
args["usage_status"] = model.UsageStatus[us.lower()]

cr = reader.pop_resolved_ref("ConceptRole")
if cr:
args["concept_role"] = cr
Expand Down Expand Up @@ -1175,9 +1180,8 @@ def _cl(reader: Reader, elem):
for ref in reader.pop_all("DimensionReference")
]
else:
# SDMX-ML spec for, e.g. DimensionList: "The id attribute is
# provided in this case for completeness. However, its value is
# fixed to 'DimensionDescriptor'."
# SDMX-ML spec for, e.g. DimensionList: "The id attribute is provided in this
# case for completeness. However, its value is fixed to 'DimensionDescriptor'."
cls = reader.class_for_tag(elem.tag)
args["id"] = elem.attrib.get("id", cls.__name__)

Expand Down
1 change: 1 addition & 0 deletions sdmx/testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ def __init__(self, base_path):
("UNSD", "codelist_partial.xml"),
("SGR", "common-structure.xml"),
("TEST", "gh-142.xml"),
("TEST", "gh-149.xml"),
]
)

Expand Down
46 changes: 36 additions & 10 deletions sdmx/tests/writer/test_writer_xml.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import io
import logging

import pytest
from lxml import etree

import sdmx
import sdmx.writer.xml
from sdmx import message
from sdmx.model import v21 as m
from sdmx.model.v21 import DataSet, DataStructureDefinition, Dimension, Key, Observation
Expand Down Expand Up @@ -83,13 +86,14 @@ def test_ContentConstraint(dsd, dks):
)


def test_ds(dsd, obs):
def test_ds(dsd, obs) -> None:
# Write DataSet with Observations not in Series
ds = DataSet(structured_by=dsd)
ds.obs.append(obs)

result = sdmx.to_xml(ds, pretty_print=True)
print(result.decode())
# print(result.decode())
del result


def test_ds_structurespecific(dsd):
Expand Down Expand Up @@ -138,6 +142,24 @@ def test_obs(obs):
XMLWriter.recurse(obs, struct_spec=True)


def test_reference() -> None:
cl = m.Codelist(id="FOO", version="1.0")
c = m.Code(id="BAR")
cl.append(c)

# <Ref …> to Item has maintainableParentVersion, but no version
result = sdmx.writer.xml.reference(c, style="Ref")
result_str = etree.tostring(result).decode()
assert 'maintainableParentVersion="1.0"' in result_str
assert 'version="1.0"' not in result_str

# <Ref …> to ItemScheme has version, but not maintainableParentVersion
result = sdmx.writer.xml.reference(cl, style="Ref")
result_str = etree.tostring(result).decode()
assert 'maintainableParentVersion="1.0"' not in result_str
assert 'version="1.0"' in result_str


def test_Footer(footer):
""":class:`.Footer` can be written."""
sdmx.to_xml(footer)
Expand Down Expand Up @@ -241,23 +263,27 @@ def test_data_roundtrip(pytestconfig, specimen, data_id, structure_id, tmp_path)
("INSEE/dataflow.xml", False),
("SGR/common-structure.xml", True),
("UNSD/codelist_partial.xml", True),
("TEST/gh-149.xml", False),
],
)
def test_structure_roundtrip(pytestconfig, specimen, specimen_id, strict, tmp_path):
def test_structure_roundtrip(specimen, specimen_id, strict, tmp_path):
"""Test that SDMX-ML StructureMessages can be 'round-tripped'."""

# Read a specimen file
with specimen(specimen_id) as f:
msg0 = sdmx.read_sdmx(f)

# Write to file
path = tmp_path / "output.xml"
path.write_bytes(sdmx.to_xml(msg0, pretty_print=True))
# Write to a bytes buffer
data = io.BytesIO(sdmx.to_xml(msg0, pretty_print=True))

# Read again
msg1 = sdmx.read_sdmx(path)
msg1 = sdmx.read_sdmx(data)

# Contents are identical
assert msg0.compare(msg1, strict), (
path.read_text() if pytestconfig.getoption("verbose") else path
)
try:
assert msg0.compare(msg1, strict)
except AssertionError: # pragma: no cover
path = tmp_path.joinpath("output.xml")
path.write_bytes(data.getbuffer())
log.error(f"compare() = False; see {path}")
raise
Loading

0 comments on commit cd3ecb2

Please sign in to comment.