Source code for sdmx.reader
from pathlib import Path
from typing import List, Mapping, Type
from sdmx.reader import json, xml
#: Reader classes
READERS: List[Type] = []
#: Mapping from HTTP content type to reader class.
CTYPE_READER: Mapping[str, Type] = {}
#: Mapping from file path suffix to reader class.
SUFFIX_READER: Mapping[str, Type] = {}
[docs]def detect_content_reader(content):
"""Return a reader class for `content`.
The :meth:`.BaseReader.detect` method for each class in :data:`READERS` is called;
if a reader signals that it is compatible with `content`, then that class is
returned.
Raises
------
ValueError
If no reader class matches.
"""
for cls in READERS:
if cls.detect(content):
return cls
raise ValueError(f"{repr(content)} not recognized by any of {READERS}")
[docs]def get_reader_for_content_type(ctype):
"""Return a reader class for HTTP content type `content`.
Raises
------
ValueError
If no reader class matches.
See also
--------
CTYPE_READER
"""
# Split off e.g. "; version=2.1"
ctype = str(ctype).split(";")[0].strip()
try:
return CTYPE_READER[ctype]
except KeyError:
raise ValueError(f"Unsupported content type: {ctype}") from None
[docs]def get_reader_for_path(path):
"""Return a reader class for file `path`.
Raises
------
ValueError
If no reader class matches.
See also
--------
SUFFIX_READER
"""
path = Path(path)
try:
return SUFFIX_READER[path.suffix.lower()]
except KeyError:
raise ValueError(f"Unsupported file suffix: {path.suffix}") from None
[docs]def register(reader_cls):
"""Register `reader_cls`."""
global READERS, CTYPE_READER, SUFFIX_READER
READERS.append(reader_cls)
for ctype in reader_cls.content_types:
CTYPE_READER[ctype] = reader_cls
for suffix in reader_cls.suffixes:
SUFFIX_READER[suffix] = reader_cls
# Register built-in readers
register(json.Reader)
register(xml.Reader)
[docs]def read_sdmx(filename_or_obj, format=None, **kwargs):
"""Load a SDMX-ML or SDMX-JSON message from a file or file-like object.
Parameters
----------
filename_or_obj : str or :class:`~os.PathLike` or file
format : 'XML' or 'JSON', optional
Other Parameters
----------------
dsd : :class:`~.DataStructureDefinition`
For “structure-specific” `format`=``XML`` messages only.
"""
reader = None
try:
path = Path(filename_or_obj)
# Open the file
obj = open(path, "rb")
except TypeError:
# Not path-like → opened file
path = None
obj = filename_or_obj
if path:
try:
# Use the file extension to guess the reader
reader = get_reader_for_path(filename_or_obj)
except ValueError:
pass
if not reader:
try:
reader = get_reader_for_path(Path(f"dummy.{format.lower()}"))
except (AttributeError, ValueError):
pass
if not reader:
# Read a line and then return the cursor to the initial position
pos = obj.tell()
first_line = obj.readline().strip()
obj.seek(pos)
try:
reader = detect_content_reader(first_line)
except ValueError:
pass
if not reader:
raise RuntimeError(
f"cannot infer SDMX message format from path {repr(path)}, "
f"format={format}, or content '{first_line[:5].decode()}..'"
)
return reader().read_message(obj, **kwargs)