Skip to content

Commit 55c45fb

Browse files
aboddiekhaeru
andauthored
Use more durable IDs for IMF_Beta and IMF_Beta3 (#225)
- Update sources.json - Add .source.imf_data{,3} modules. - Add headers for all requests. - Update doc/sources.rst for IMF_DATA{,3}. - Copyedit IMF_DATA{,3} documentation. - Update test_sources.py. - Add tests for other artifacts. - Fix handling of <str:Codelist>URN… in SDMX-ML 3.0 - Add #225 to doc/whatsnew --------- Co-authored-by: Paul Natsuo Kishimoto <[email protected]>
1 parent 5a6ea56 commit 55c45fb

File tree

8 files changed

+172
-89
lines changed

8 files changed

+172
-89
lines changed

.github/workflows/sources.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ jobs:
3838
- GROW
3939
- ILO
4040
- IMF
41-
- IMF_beta
42-
- IMF_beta3
41+
- IMF_DATA
42+
- IMF_DATA3
4343
- INEGI
4444
- INSEE
4545
- ISTAT

doc/sources.rst

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -312,13 +312,15 @@ API documentation `1 <https://datahelp.imf.org/knowledgebase/articles/1952905-sd
312312
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
313313

314314
SDMX-ML —
315-
`Website <https://sdmxcentral.imf.org/>`__
315+
`Website <https://sdmxcentral.imf.org/>`__ —
316+
`API documentation <https://dsbb.imf.org/content/Pdfs/IMF%20SDMX%20Central%20Web%20Services%20Guide%20Published%2010_17_2019.pdf>`__
316317

317-
- This appears to be an instance of the “Fusion Metadata Registry” software.
318+
- This source does not contain data and should only be used to query structures.
319+
- This is an instance of the “Fusion Metadata Registry” software.
318320
Such instances also expose SDMX 2.1 and 3.0 APIs.
319-
- No API documentation appears to be available.
320321
- The :mod:`sdmx` source with ID ``IMF`` corresponds to the SDMX 2.1 (SDMX-REST 1.x) API with base URL https://sdmxcentral.imf.org/ws/public/sdmxapi/rest.
321-
The web interface suggests URLs for the SDMX 3.0.0 (SDMX-REST 2.x) API with base URL https://sdmxcentral.imf.org/sdmx/v2.
322+
323+
The web interface suggests URLs for an SDMX 3.0.0 (SDMX-REST 2.x) API with base URL https://sdmxcentral.imf.org/sdmx/v2.
322324
This API can be accessed by modifying the :attr:`.Source.url` and :attr:`~.Source.versions` attributes, or by constructing a new Source.
323325
For example:
324326

@@ -331,32 +333,30 @@ SDMX-ML —
331333
client.source.url = "https://sdmxcentral.imf.org/sdmx/v2"
332334
client.source.versions = {Version["3.0.0"]}
333335
334-
# Retrieve an SDMX-ML 3.0.0 structure message
335-
message = client.dataflow("01R")
336-
337-
- The source appears to provide a subset of the data available on https://data.imf.org.
338-
- Supports series-key-only and hence dataset-based key validation and construction.
339-
340-
``IMF_beta``, ``IMF_beta3``: api.imf.org
336+
``IMF_DATA``, ``IMF_DATA3``: api.imf.org
341337
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
342338

343339
SDMX-ML —
344-
`Website <https://betadata.imf.org>`__ —
345-
`API documentation <https://betadata.imf.org/en/Resource-Pages/IMF-API>`__
340+
Website `(main) <https://data.imf.org>`__, `(beta) <https://betadata.imf.org>`__ —
341+
API documentation `(main) <https://data.imf.org/en/Resource-Pages/IMF-API>`__, `(beta) <https://betadata.imf.org/en/Resource-Pages/IMF-API>`__.
346342

347-
.. warning:: As of 2025-01-10, this source carries a banner:
343+
.. note:: As of **2025-01-10**, this source carries a banner:
348344

349-
We're in Beta!
350-
Help us improve by `testing <https://datasupport.imf.org/knowledge?id=kb_article_view&sys_kb_id=372b9c5493019610102cf4647aba1015&category_id=4e49be7c1b6391903dba646fbd4bcb00>`__ and sharing `feedback <https://forms.office.com/pages/responsepage.aspx?id=Q_qFgC4wvUWxcaZkjDtr54N7EnsUWMNKll1Zs-zgwh9UODA5MTFBVlA1MDFaWEpIMFVaSE83TzJYTy4u&route=shorturl>`__.
351-
This is a beta version; the data is not final and should not be used for actual work.
345+
We're in Beta!
346+
Help us improve by `testing <https://datasupport.imf.org/knowledge?id=kb_article_view&sys_kb_id=372b9c5493019610102cf4647aba1015&category_id=4e49be7c1b6391903dba646fbd4bcb00>`__ and sharing `feedback <https://forms.office.com/pages/responsepage.aspx?id=Q_qFgC4wvUWxcaZkjDtr54N7EnsUWMNKll1Zs-zgwh9UODA5MTFBVlA1MDFaWEpIMFVaSE83TzJYTy4u&route=shorturl>`__.
347+
This is a beta version; the data is not final and should not be used for actual work.
352348

353349
Users should heed this message.
354-
The source IDs used in :mod:`sdmx` may change if and when this source exits beta and enters production, or is designated as the recommended, primary, or sole IMF source.
355350

356-
- The API documentation indicates "Our data are available through SDMX 2.1 and SDMX 3.0 APIs," but the documentation pages mention only the SDMX 2.1 (SDMX-REST 1.x) base URL, https://api.imf.org/external/sdmx/2.1.
357-
The base URL used by :mod:`sdmx` for the SDMX 3.0 (SDMX-REST 2.x) API is inferred.
358-
- :mod:`sdmx` provides access to both versions of the API with IDs ``IMF_beta`` and ``IMF_beta3``.
359-
As of 2025-01-10, both return HTTP **403 Forbidden** to every request except the SDMX 2.1 data query illustrated in the API documentation.
351+
`This documentation page <https://datasupport.imf.org/knowledge?id=knowledge_category&sys_kb_id=967337049388ea50102cf4647aba1024&category_id=4e49be7c1b6391903dba646fbd4bcb00>`__ states that the “IMF Data portal” associated with the above URL endpoints “will launch at the end of Q1 2025,” thus **on or about 2025-03-31**.
352+
At that point, the ‘(main)’ links above will point to the new portal, and the ‘(beta)’ links will no longer be accessible.
353+
354+
- The ``IMF_DATA`` source points to an SDMX-REST 1.x API endpoint that serves SDMX-ML 2.1.
355+
This endpoint does not support access of :class:`.v21.MetadataSet`, :class:`.v21.MetadataflowDefinition`, or :class:`.v21.MetadataStructureDefinition`.
356+
357+
- The ``IMF_DATA3`` source points to an SDMX-REST 2.x API endpoint that serves SDMX-ML 3.0.0 and SDMX-JSON 2.0.0.
358+
Only the former is supported by :mod:`sdmx`.
359+
This endpoint does not support access of :class:`.HierarchicalCodelist`.
360360

361361
.. _INEGI:
362362

doc/whatsnew.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ All changes
6363
- Write annotations associated with :class:`DataSet <.BaseDataSet>`, :class:`MetadataSet <.BaseMetadataSet>`, and :class:`.MetadataReport`.
6464
- Pending resolution of :issue:`228`, ignore :xml:`<com:Link>` in SDMX-ML 3.0.0 .
6565

66+
- Rename :ref:`IMF_beta, IMF_beta3 <IMF>` data sources to :ref:`IMF_DATA, IMF_DATA3 <IMF>` and update documentation on 3 distinct IMF-run web services (thanks :gh-user:`aboddie` for :pull:`225` and :issue:`224`).
6667
- Update and expand :ref:`sdmx-version-policy` in the documentation (:pull:`227`).
6768
A table is now included showing the correspondence of versions of component SDMX standards.
6869

sdmx/reader/xml/v30.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,22 @@ def _link(reader, elem) -> None:
9292

9393
@end("str:Codelist")
9494
def _cl(reader, elem):
95-
try:
96-
sdmx.urn.match(elem.text)
97-
except ValueError:
98-
result = v21._itemscheme(reader, elem)
99-
result.extends = reader.pop_all(v30.CodelistExtension)
100-
return result
101-
else:
102-
reader.push(elem, elem.text)
95+
# Handle <str:Codelist>urn:…</str:Codelist>; occurs within <str:CodelistExtension>
96+
if len(elem) == 0 and elem.text:
97+
try:
98+
# Validate the contained URN, return to str
99+
urn = str(sdmx.urn.URN(elem.text))
100+
except Exception:
101+
pass # Malformed, or not a URN
102+
else:
103+
reader.push(elem, urn)
104+
return
105+
106+
# Use the .v21 reader function
107+
result = v21._itemscheme(reader, elem)
108+
# Attach CodelistExtensions
109+
result.extends = reader.pop_all(v30.CodelistExtension)
110+
return result
103111

104112

105113
@end("str:CodelistExtension")

sdmx/source/imf_data.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from . import Source as BaseSource
2+
3+
4+
class Source(BaseSource):
5+
"""Hooks for the ``IMF_DATA`` SDMX-REST API."""
6+
7+
_id = "IMF_DATA"
8+
9+
def modify_request_args(self, kwargs):
10+
"""Modify arguments used to build query URL.
11+
12+
Set default provider agency ID ``all``.
13+
"""
14+
super().modify_request_args(kwargs)
15+
16+
# Supply 'all' as the default agency_id
17+
# NB this is an indirect test for resource_type != 'data'; because of the way
18+
# the hook is called, resource_type is not available directly.
19+
if "key" not in kwargs:
20+
kwargs.setdefault("agency_id", "all")

sdmx/source/imf_data3.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from requests.structures import CaseInsensitiveDict
2+
3+
from sdmx.format import MediaType
4+
5+
from . import Source as BaseSource
6+
7+
8+
class Source(BaseSource):
9+
"""Hooks for the ``IMF_DATA3`` SDMX-REST API."""
10+
11+
_id = "IMF_DATA3"
12+
13+
def modify_request_args(self, kwargs):
14+
"""Modify arguments used to build query URL.
15+
16+
1. Set default provider agency ID ``all``.
17+
2. Set media-type in header.
18+
"""
19+
super().modify_request_args(kwargs)
20+
21+
# Supply 'all' as the default agency_id
22+
# NB this is an indirect test for resource_type != 'data'; because of the way
23+
# the hook is called, resource_type is not available directly.
24+
if "key" not in kwargs:
25+
kwargs.setdefault("agency_id", "all")
26+
27+
# Retrieve SDMX-ML by default
28+
# TODO Choose between the data, metadata, or structure media-type according to
29+
# other `kwargs`
30+
kwargs.setdefault("headers", CaseInsensitiveDict())
31+
mt = [MediaType(x, "xml", "3.0.0") for x in ("data", "metadata", "structure")]
32+
kwargs["headers"].setdefault("Accept", ", ".join(map(str, mt)))

sdmx/sources.json

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,38 @@
229229
}
230230
},
231231
{
232-
"id": "IMF_beta",
232+
"id": "IMF_DATA",
233233
"url": "https://api.imf.org/external/sdmx/2.1",
234-
"name": "International Monetary Fund"
234+
"name": "International Monetary Fund",
235+
"supports": {
236+
"actualconstraint": false,
237+
"agencyscheme": false,
238+
"allowedconstraint": false,
239+
"dataconsumerscheme": false,
240+
"dataproviderscheme": false,
241+
"organisationscheme": false,
242+
"provisionagreement": false,
243+
"registration": false,
244+
"structure": false,
245+
"structureset": false
246+
}
235247
},
236248
{
237-
"id": "IMF_beta3",
249+
"id": "IMF_DATA3",
238250
"url": "https://api.imf.org/external/sdmx/3.0",
239-
"name": "International Monetary Fund",
251+
"name": "International Monetary Fund (SDMX 3.0)",
252+
"supports": {
253+
"actualconstraint": false,
254+
"agencyscheme": false,
255+
"allowedconstraint": false,
256+
"dataconsumerscheme": false,
257+
"dataproviderscheme": false,
258+
"organisationscheme": false,
259+
"provisionagreement": false,
260+
"registration": false,
261+
"structure": false,
262+
"structureset": false
263+
},
240264
"versions": [
241265
"3.0.0"
242266
]

sdmx/tests/test_sources.py

Lines changed: 51 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -368,73 +368,71 @@ class TestIMF(DataSourceTest):
368368
source_id = "IMF"
369369

370370

371-
# As of 2025-01-10, all endpoints aside from SDMX 2.1 /data/ return 403
372-
IMF_BETA_XFAIL: dict[str, Union[type[Exception], tuple[type[Exception], str]]] = {
373-
k: HTTPError
374-
for k in """
375-
actualconstraint
376-
agencyscheme
377-
allowedconstraint
378-
categorisation
379-
categoryscheme
380-
codelist
381-
conceptscheme
382-
contentconstraint
383-
dataconsumerscheme
384-
dataflow
385-
dataproviderscheme
386-
datastructure
387-
hierarchicalcodelist
388-
metadataflow
389-
metadatastructure
390-
organisationscheme
391-
provisionagreement
392-
registration
393-
structure
394-
structureset
395-
""".split()
396-
}
397-
371+
class TestIMF_DATA(DataSourceTest):
372+
source_id = "IMF_DATA"
398373

399-
class TestIMF_beta(DataSourceTest):
400-
source_id = "IMF_beta"
401-
402-
endpoint_args = dict(
374+
endpoint_args = {
403375
# As indicated in the API documentation
404-
data=dict(
376+
"data": dict(
405377
resource_id="CPI",
406378
key="111.CPI.CP01.IX.M",
407379
params=dict(startPeriod=2018),
408-
# Does not appear to affect 403
409-
# headers={"User-Agent": "idata-script-client"},
410-
)
411-
)
380+
),
381+
"codelist": dict(resource_id="CL_COUNTRY"),
382+
"structure": dict(resource_id="DSD_CPI"),
383+
"conceptscheme": dict(resource_id="CS_MASTER_SYSTEM"),
384+
}
412385

413-
xfail = IMF_BETA_XFAIL | dict(
414-
metadata=NotImplementedError,
415-
registration=ValueError,
416-
)
386+
xfail = {
387+
"actualconstraint": HTTPError,
388+
"agencyscheme": HTTPError,
389+
"allowedconstraint": HTTPError,
390+
"dataconsumerscheme": HTTPError,
391+
"dataproviderscheme": HTTPError,
392+
"metadata": NotImplementedError,
393+
"metadataflow": HTTPError,
394+
"metadatastructure": HTTPError,
395+
"organisationscheme": HTTPError,
396+
"provisionagreement": HTTPError,
397+
"registration": HTTPError,
398+
"structureset": HTTPError,
399+
}
417400

418401

419-
class TestIMF_beta3(DataSourceTest):
420-
source_id = "IMF_beta3"
402+
class TestIMF_DATA3(DataSourceTest):
403+
source_id = "IMF_DATA3"
421404

422-
endpoint_args = dict(
423-
data=dict(
405+
endpoint_args = {
406+
"data": dict(
424407
context="dataflow",
425-
agency_id="IMF",
408+
agency_id="IMF.STA",
426409
resource_id="CPI",
427410
key="111.CPI.CP01.IX.M",
428-
# Not yet supported
429-
# params={"c[TIME_PERIOD]": "ge:2018"},
411+
params={"c[TIME_PERIOD]": "ge:2018"},
430412
),
431-
metadata=dict(provider_id="IMF"),
432-
)
413+
"codelist": dict(resource_id="CL_COUNTRY"),
414+
"structure": dict(resource_id="DSD_CPI"),
415+
"conceptscheme": dict(resource_id="CS_MASTER_SYSTEM"),
416+
}
433417

434-
xfail = IMF_BETA_XFAIL | dict(
435-
data=HTTPError, # 403
436-
metadata=HTTPError, # 403
437-
)
418+
xfail = {
419+
"actualconstraint": HTTPError,
420+
"agencyscheme": HTTPError,
421+
"allowedconstraint": HTTPError,
422+
"codelist": NotImplementedError,
423+
"conceptscheme": NotImplementedError,
424+
"contentconstraint": HTTPError,
425+
"dataconsumerscheme": HTTPError,
426+
"dataflow": NotImplementedError,
427+
"dataproviderscheme": HTTPError,
428+
"datastructure": NotImplementedError,
429+
"hierarchicalcodelist": HTTPError,
430+
"metadatastructure": NotImplementedError,
431+
"organisationscheme": HTTPError,
432+
"provisionagreement": HTTPError,
433+
"registration": HTTPError,
434+
"structureset": HTTPError,
435+
}
438436

439437

440438
class TestINEGI(DataSourceTest):

0 commit comments

Comments
 (0)