Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added schema support for Provision Agreements that are referenced by Hierarchical Associations #23

Merged
merged 6 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/release.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
Release notes
=============

1.0.0-beta-10
------------

Added
^^^^^

- Schema generation supports Hierarchical Associations
referencing Provision Agreements


1.0.0-beta-1
------------

Expand Down
508 changes: 234 additions & 274 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pysdmx"
version = "1.0.0-beta-9"
version = "1.0.0-beta-10"
description = "Your opinionated Python SDMX library"
authors = [
"Xavier Sosnovsky <xavier.sosnovsky@bis.org>",
Expand Down
2 changes: 1 addition & 1 deletion src/pysdmx/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Your opinionated Python SDMX library."""

__version__ = "1.0.0-beta-8"
__version__ = "1.0.0-beta-10"
53 changes: 37 additions & 16 deletions src/pysdmx/fmr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Retrieve metadata from an FMR instance."""

from enum import Enum
from typing import (
Any,
Expand Down Expand Up @@ -83,6 +84,10 @@ class Context(Enum):
"structure/dataflow/{0}/{1}/{2}"
"?references=all&detail=referencepartial"
),
"ha_pra": (
"structure/provisionagreement/{0}/{1}/{2}"
"?references=all&detail=referencepartial"
),
"hierarchy": (
"structure/hierarchy/{0}/{1}/{2}"
"?detail=referencepartial&references=codelist"
Expand Down Expand Up @@ -219,9 +224,9 @@ def __fetch(self, url: str, is_ref_meta: bool = False) -> bytes:
try:
if is_ref_meta and self.format == Format.SDMX_JSON:
h = self.headers.copy()
h[
"Accept"
] = "application/vnd.sdmx.metadata+json;version=2.0.0"
h["Accept"] = (
"application/vnd.sdmx.metadata+json;version=2.0.0"
)
else:
h = self.headers
r = client.get(url, headers=h)
Expand All @@ -236,6 +241,12 @@ def __get_hierarchies_for_flow(
out = self.__fetch(super()._url("ha", agency, flow, version))
return super()._out(out, self.deser.hier_assoc)

def __get_hierarchies_for_pra(
self, agency: str, pra: str, version: str
) -> Sequence[HierarchyAssociation]:
out = self.__fetch(super()._url("ha_pra", agency, pra, version))
return super()._out(out, self.deser.hier_assoc)

def get_agencies(self, agency: str) -> Sequence[Organisation]:
"""Get the list of **sub-agencies** for the supplied agency.

Expand Down Expand Up @@ -359,11 +370,13 @@ def get_schema(
The requested schema.
"""
c = context.value if isinstance(context, Context) else context
ha = (
self.__get_hierarchies_for_flow(agency, id, version)
if c == "dataflow"
else ()
)
if context == "dataflow":
ha = self.__get_hierarchies_for_flow(agency, id, version)
elif context == "provisionagreement":
ha = self.__get_hierarchies_for_pra(agency, id, version)
else:
ha = ()

out = self.__fetch(super()._url("schema", c, agency, id, version))
return super()._out(out, self.deser.schema, c, agency, id, version, ha)

Expand Down Expand Up @@ -543,9 +556,9 @@ async def __fetch(self, url: str, is_ref_meta: bool = False) -> bytes:
try:
if is_ref_meta and self.format == Format.SDMX_JSON:
h = self.headers.copy()
h[
"Accept"
] = "application/vnd.sdmx.metadata+json;version=2.0.0"
h["Accept"] = (
"application/vnd.sdmx.metadata+json;version=2.0.0"
)
else:
h = self.headers
r = await client.get(url, headers=h)
Expand All @@ -560,6 +573,12 @@ async def __get_hierarchies_for_flow(
out = await self.__fetch(super()._url("ha", agency, flow, version))
return super()._out(out, self.deser.hier_assoc)

async def __get_hierarchies_for_pra(
self, agency: str, pra: str, version: str
) -> Sequence[HierarchyAssociation]:
out = await self.__fetch(super()._url("ha_pra", agency, pra, version))
return super()._out(out, self.deser.hier_assoc)

async def get_agencies(self, agency: str) -> Sequence[Organisation]:
"""Get the list of **sub-agencies** for the supplied agency.

Expand Down Expand Up @@ -680,11 +699,13 @@ async def get_schema(
Returns:
The requested schema.
"""
ha = (
await self.__get_hierarchies_for_flow(agency, id, version)
if context != "datastructure"
else ()
)
if context == "dataflow":
ha = await self.__get_hierarchies_for_flow(agency, id, version)
elif context == "provisionagreement":
ha = await self.__get_hierarchies_for_pra(agency, id, version)
else:
ha = ()

c = context.value if isinstance(context, Context) else context
r = await self.__fetch(super()._url("schema", c, agency, id, version))
return super()._out(r, self.deser.schema, c, agency, id, version, ha)
Expand Down
1 change: 1 addition & 0 deletions src/pysdmx/fmr/fusion/dsd.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Collection of Fusion-JSON schemas for SDMX-REST schema queries."""

from typing import Dict, List, Optional, Sequence, Tuple

from msgspec import Struct
Expand Down
1 change: 1 addition & 0 deletions src/pysdmx/fmr/fusion/schema.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Collection of Fusion-JSON schemas for SDMX-REST schema queries."""

from typing import List, Sequence

import msgspec
Expand Down
1 change: 1 addition & 0 deletions src/pysdmx/fmr/sdmx/schema.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Collection of SDMX-JSON schemas for SDMX-REST schema queries."""

from typing import Sequence

import msgspec
Expand Down
4 changes: 1 addition & 3 deletions src/pysdmx/model/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,9 +421,7 @@ def __len__(self) -> int:
"""Return the number of mapping rules in the structure map."""
return len(self.maps)

def __getitem__(
self, id_: str
) -> Optional[
def __getitem__(self, id_: str) -> Optional[
Sequence[
Union[
ComponentMap,
Expand Down
144 changes: 144 additions & 0 deletions tests/fmr/fusion/test_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ def query(fmr):
return f"{fmr.api_endpoint}{res}{agency}/{id}/{version}"


@pytest.fixture()
def query_pra(fmr):
res = "schema/provisionagreement/"
agency = "BIS.CBS"
id = "CBS_BIS_GR2"
version = "1.0"
return f"{fmr.api_endpoint}{res}{agency}/{id}/{version}"


@pytest.fixture()
def no_hca_query(fmr):
res = "structure/dataflow/"
Expand All @@ -43,6 +52,18 @@ def no_hca_query(fmr):
)


@pytest.fixture()
def no_hca_pra_query(fmr):
res = "structure/provisionagreement/"
agency = "BIS.CBS"
id = "CBS_BIS_GR2"
version = "1.0"
return (
f"{fmr.api_endpoint}{res}{agency}/{id}/{version}"
"?references=all&detail=referencepartial"
)


@pytest.fixture()
def hierarchy_hca_query(fmr):
res = "structure/dataflow/"
Expand All @@ -55,6 +76,18 @@ def hierarchy_hca_query(fmr):
)


@pytest.fixture()
def hierarchy_hca_query_pra(fmr):
res = "structure/provisionagreement/"
agency = "BIS.CBS"
id = "CBS_BIS_TEST"
version = "1.0"
return (
f"{fmr.api_endpoint}{res}{agency}/{id}/{version}"
"?references=all&detail=referencepartial"
)


@pytest.fixture()
def hierarchy_query(fmr):
res = "schema/dataflow/"
Expand All @@ -64,6 +97,15 @@ def hierarchy_query(fmr):
return f"{fmr.api_endpoint}{res}{agency}/{id}/{version}"


@pytest.fixture()
def hierarchy_query_pra(fmr):
res = "schema/provisionagreement/"
agency = "BIS.CBS"
id = "CBS_BIS_TEST"
version = "1.0"
return f"{fmr.api_endpoint}{res}{agency}/{id}/{version}"


@pytest.fixture()
def no_const_query(fmr):
res = "schema/datastructure/"
Expand All @@ -79,6 +121,12 @@ def body():
return f.read()


@pytest.fixture()
def body_from_pra():
with open("tests/fmr/samples/pra/schema.fusion.json", "rb") as f:
return f.read()


@pytest.fixture()
def no_const_body():
with open("tests/fmr/samples/df/no_const.fusion.json", "rb") as f:
Expand Down Expand Up @@ -115,12 +163,30 @@ def hier_assoc_body():
return f.read()


@pytest.fixture()
def hierarchy_pra_body():
with open("tests/fmr/samples/pra/hierarchy_schema.fusion.json", "rb") as f:
return f.read()


@pytest.fixture()
def hier_assoc_pra_body():
with open("tests/fmr/samples/pra/hierarchy_hca.fusion.json", "rb") as f:
return f.read()


@pytest.fixture()
def no_hca_body():
with open("tests/fmr/samples/df/no_hca.fusion.json", "rb") as f:
return f.read()


@pytest.fixture()
def no_hca_pra_body():
with open("tests/fmr/samples/pra/no_hca.fusion.json", "rb") as f:
return f.read()


def test_returns_validation_context(
respx_mock, fmr, query, no_hca_query, body, no_hca_body
):
Expand All @@ -130,6 +196,25 @@ def test_returns_validation_context(
)


def test_returns_pra_validation_context(
respx_mock,
fmr,
query_pra,
no_hca_pra_query,
body_from_pra,
no_hca_pra_body,
):
"""get_validation_context() should return a schema."""
checks.check_schema_from_pra(
respx_mock,
fmr,
query_pra,
no_hca_pra_query,
body_from_pra,
no_hca_pra_body,
)


@pytest.mark.asyncio()
async def test_codes(
respx_mock, async_fmr, query, no_hca_query, body, no_hca_body
Expand All @@ -140,6 +225,46 @@ async def test_codes(
)


@pytest.mark.asyncio()
async def test_codes_pra(
respx_mock,
async_fmr,
query_pra,
no_hca_pra_query,
body_from_pra,
no_hca_pra_body,
):
"""Components have the expected number of codes."""
await checks.check_coded_pra_components(
respx_mock,
async_fmr,
query_pra,
no_hca_pra_query,
body_from_pra,
no_hca_pra_body,
)


@pytest.mark.asyncio()
async def test_core_local_repr_async(
respx_mock,
async_fmr,
no_const_query,
no_hca_query,
no_const_body,
no_hca_body,
):
"""Components have the expected representation (local or core)."""
await checks.check_core_local_repr_async(
respx_mock,
async_fmr,
no_const_query,
no_hca_query,
no_const_body,
no_hca_body,
)


def test_codes_no_const(
respx_mock, fmr, no_const_query, no_hca_query, no_const_body, no_hca_body
):
Expand Down Expand Up @@ -279,3 +404,22 @@ def test_has_hierarchy(
hierarchy_body,
hier_assoc_body,
)


def test_has_hierarchy_pra(
respx_mock,
fmr,
hierarchy_query_pra,
hierarchy_hca_query_pra,
hierarchy_pra_body,
hier_assoc_pra_body,
):
"""Components may reference a hierarchy."""
checks.check_hierarchy_pra(
respx_mock,
fmr,
hierarchy_query_pra,
hierarchy_hca_query_pra,
hierarchy_pra_body,
hier_assoc_pra_body,
)
Loading