diff --git a/doc/conf.py b/doc/conf.py index d46644fcf..d4a71f8c5 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -6,7 +6,8 @@ # -- Project information --------------------------------------------------------------- project = "sdmx" -copyright = "2014–2024 sdmx1 developers" +copyright = "2014–%Y sdmx1 developers" +author = "sdmx1 developers" # -- General configuration ------------------------------------------------------------- diff --git a/doc/whatsnew.rst b/doc/whatsnew.rst index 5db8e6996..53c06c7e9 100644 --- a/doc/whatsnew.rst +++ b/doc/whatsnew.rst @@ -13,6 +13,9 @@ Next release Previously such invalid references caused a :class:`KeyError`. Prompted by an example in :ref:`INSEE `. - Update the base URL of the :ref:`WB ` source to use HTTPS instead of plain HTTP (:pull:`207`). +- Bugfix for writing :class:`.NameableArtefact` to SDMX-ML (:pull:`211`; thanks :gh-user:`3nz01` for :issue:`210`). + Up to v2.19.1, the :xml:`` element was written *after* elements such as :xml:``, which is opposite the order given in the XSD schemas for SDMX-ML. + :mod:`sdmx.reader.xml` tolerates non-standard element order, but some other implementations do not. v2.19.1 (2024-10-23) ==================== diff --git a/sdmx/format/xml/__init__.py b/sdmx/format/xml/__init__.py index e69de29bb..cec6ec32a 100644 --- a/sdmx/format/xml/__init__.py +++ b/sdmx/format/xml/__init__.py @@ -0,0 +1,5 @@ +from .common import validate_xml + +__all__ = [ + "validate_xml", +] diff --git a/sdmx/tests/writer/test_writer_xml.py b/sdmx/tests/writer/test_writer_xml.py index db3371c05..c82ef6657 100644 --- a/sdmx/tests/writer/test_writer_xml.py +++ b/sdmx/tests/writer/test_writer_xml.py @@ -1,5 +1,6 @@ import io import logging +from datetime import datetime import pytest from lxml import etree @@ -7,7 +8,8 @@ import sdmx import sdmx.writer.xml from sdmx import message -from sdmx.model import common +from sdmx.format.xml import validate_xml +from sdmx.model import common, v21 from sdmx.model import v21 as m from sdmx.model.v21 import DataSet, DataStructureDefinition, Dimension, Key, Observation from sdmx.writer.xml import writer as XMLWriter @@ -17,6 +19,18 @@ # Fixtures +@pytest.fixture +def structure_message() -> message.StructureMessage: + """A StructureMessage that serializes to XSD-valid SDMX-XML.""" + return message.StructureMessage( + header=message.Header( + id="N_A", + prepared=datetime.now(), + sender=common.Agency(id="N_A"), + ) + ) + + @pytest.fixture def dsd(): dsd = DataStructureDefinition() @@ -49,6 +63,42 @@ def dks(dsd): # Test specific methods associated with specific classes +class TestNameableArtefact: + def test_xsd(self, structure_message): + """Annotations for a NameableArtefact are output in the correct order. + + In https://github.com/khaeru/sdmx/issues/210 it was reported that + incorrectly appeared before . + """ + # Common arguments + args = dict( + # Identifiable Artefact + id="FOO", + # Nameable Artefact + name="foo", + description="bar", + # VersionableArtefact + version="1", + # MaintainableArtefact + maintainer=common.Agency(id="N_A"), + is_external_reference=False, + is_final=True, + ) + dsd = v21.DataStructureDefinition(**args) + na = v21.DataflowDefinition( + annotations=[common.Annotation(id="baz", text="qux")], # Annotable Artefact + **args, # Identifiable, Nameable, Versionable, Maintainable + structure=dsd, # Dataflow-specific attributes + ) + structure_message.dataflow[na.id] = na + + # Write to SDMX-ML + buf = io.BytesIO(sdmx.to_xml(structure_message)) + + # Validate using XSD. Fails with v2.19.1. + assert validate_xml(buf), buf.getvalue().decode() + + def test_contact() -> None: c = m.Contact( name="John Smith", diff --git a/sdmx/writer/xml.py b/sdmx/writer/xml.py index 79559b2a9..36d4af211 100644 --- a/sdmx/writer/xml.py +++ b/sdmx/writer/xml.py @@ -300,7 +300,7 @@ def annotable(obj: common.AnnotableArtefact, *args, **kwargs) -> etree._Element: # Write Annotations e_anno = Element("com:Annotations", *[writer.recurse(a) for a in obj.annotations]) if len(e_anno): - args = args + (e_anno,) + args = (e_anno,) + args try: return Element(tag, *args, **kwargs)