"""Information related to the SDMX-REST web service standard."""
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, Optional
from warnings import warn
if TYPE_CHECKING: # pragma: no cover
import sdmx.source
# Mapping from Resource value to class name.
CLASS_NAME = {
"dataflow": "DataflowDefinition",
"datastructure": "DataStructureDefinition",
}
# Inverse of :data:`CLASS_NAME`.
VALUE = {v: k for k, v in CLASS_NAME.items()}
#: Response codes defined by the SDMX-REST standard.
RESPONSE_CODE = {
200: "OK",
304: "No changes",
400: "Bad syntax",
401: "Unauthorized",
403: "Semantic error", # or "Forbidden"
404: "Not found",
406: "Not acceptable",
413: "Request entity too large",
414: "URI too long",
500: "Internal server error",
501: "Not implemented",
503: "Unavailable",
}
[docs]class Resource(str, Enum):
"""Enumeration of SDMX-REST API resources.
============================= ======================================================
:class:`Enum` member :mod:`sdmx.model` class
============================= ======================================================
``actualconstraint`` :class:`.ContentConstraint`
``agencyscheme`` :class:`.AgencyScheme`
``allowedconstraint`` :class:`.ContentConstraint`
``attachementconstraint`` :class:`.AttachmentConstraint`
``categorisation`` :class:`.Categorisation`
``categoryscheme`` :class:`.CategoryScheme`
``codelist`` :class:`.Codelist`
``conceptscheme`` :class:`.ConceptScheme`
``contentconstraint`` :class:`.ContentConstraint`
``data`` :class:`.DataSet`
``dataflow`` :class:`.DataflowDefinition`
``dataconsumerscheme`` :class:`.DataConsumerScheme`
``dataproviderscheme`` :class:`.DataProviderScheme`
``datastructure`` :class:`.DataStructureDefinition`
``metadataflow`` :class:`.MetadataflowDefinition`
``metadatastructure`` :class:`.MetadataStructureDefinition`
``organisationscheme`` :class:`.OrganisationScheme`
``provisionagreement`` :class:`.ProvisionAgreement`
``structure`` Mixed.
----------------------------- ------------------------------------------------------
``customtypescheme`` Not implemented.
``hierarchicalcodelist`` Not implemented.
``metadata`` Not implemented.
``namepersonalisationscheme`` Not implemented.
``organisationunitscheme`` Not implemented.
``process`` Not implemented.
``reportingtaxonomy`` Not implemented.
``rulesetscheme`` Not implemented.
``schema`` Not implemented.
``structureset`` Not implemented.
``transformationscheme`` Not implemented.
``userdefinedoperatorscheme`` Not implemented.
``vtlmappingscheme`` Not implemented.
============================= ======================================================
"""
actualconstraint = "actualconstraint"
agencyscheme = "agencyscheme"
allowedconstraint = "allowedconstraint"
attachementconstraint = "attachementconstraint"
categorisation = "categorisation"
categoryscheme = "categoryscheme"
codelist = "codelist"
conceptscheme = "conceptscheme"
contentconstraint = "contentconstraint"
customtypescheme = "customtypescheme"
data = "data"
dataconsumerscheme = "dataconsumerscheme"
dataflow = "dataflow"
dataproviderscheme = "dataproviderscheme"
datastructure = "datastructure"
hierarchicalcodelist = "hierarchicalcodelist"
metadata = "metadata"
metadataflow = "metadataflow"
metadatastructure = "metadatastructure"
namepersonalisationscheme = "namepersonalisationscheme"
organisationscheme = "organisationscheme"
organisationunitscheme = "organisationunitscheme"
process = "process"
provisionagreement = "provisionagreement"
reportingtaxonomy = "reportingtaxonomy"
rulesetscheme = "rulesetscheme"
schema = "schema"
structure = "structure"
structureset = "structureset"
transformationscheme = "transformationscheme"
userdefinedoperatorscheme = "userdefinedoperatorscheme"
vtlmappingscheme = "vtlmappingscheme"
[docs] @classmethod
def from_obj(cls, obj):
"""Return an enumeration value based on the class of `obj`."""
value = obj.__class__.__name__
return cls[VALUE.get(value, value)]
[docs] @classmethod
def class_name(cls, value: "Resource", default=None) -> str:
"""Return the name of a :mod:`sdmx.model` class from an enum value.
Values are returned in lower case.
"""
return CLASS_NAME.get(value.value, value.value)
@classmethod
def describe(cls):
return "{" + " ".join(v.name for v in cls._member_map_.values()) + "}"
[docs]@dataclass
class URL:
"""Utility class to build SDMX REST URLs.
See also
--------
https://github.com/sdmx-twg/sdmx-rest/blob/v1.5.0/v2_1/ws/rest/src/sdmx-rest.yaml
"""
source: "sdmx.source.Source"
resource_type: Resource
resource_id: str
provider: Optional[str] = None
# Data provider ID to use in the URL
agencyID: Optional[str] = None
version: Optional[str] = None
key: Optional[str] = None
def __post_init__(self):
if self.resource_type == Resource.data:
# Requests for data do not specific an agency in the URL
if self.provider is not None:
warn(f"'provider' argument is redundant for {self.resource_type!r}")
self.agency_id = None
else:
self.agency_id = (
self.provider if self.provider else getattr(self.source, "id", None)
)
[docs] def join(self) -> str:
"""Join the URL parts, returning a complete URL."""
resource_id = "all" if self.resource_id is None else self.resource_id
version = "latest" if self.version is None else self.version
parts = [self.source.url, self.resource_type.name]
if self.resource_type == Resource.data:
parts.append(self.resource_id)
if self.key:
parts.append(self.key)
else:
parts.extend([self.agency_id, resource_id, version])
assert None not in parts, parts
return "/".join(parts)