Skip to content

Commit

Permalink
Replace tmdsclient with bssclient (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
hf-kklein authored Feb 26, 2024
1 parent 59fb91a commit 629d698
Show file tree
Hide file tree
Showing 16 changed files with 55 additions and 57 deletions.
33 changes: 17 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
# tmdsclient.py
# bssclient.py
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
![Python Versions (officially) supported](https://img.shields.io/pypi/pyversions/tmdsclient.svg)
![Pypi status badge](https://img.shields.io/pypi/v/tmdsclient)
![Unittests status badge](https://github.com/Hochfrequenz/tmdsclient.py/workflows/Unittests/badge.svg)
![Coverage status badge](https://github.com/Hochfrequenz/tmdsclient.py/workflows/Coverage/badge.svg)
![Linting status badge](https://github.com/Hochfrequenz/tmdsclient.py/workflows/Linting/badge.svg)
![Black status badge](https://github.com/Hochfrequenz/tmdsclient.py/workflows/Formatting/badge.svg)
![Python Versions (officially) supported](https://img.shields.io/pypi/pyversions/bssclient.svg)
![Pypi status badge](https://img.shields.io/pypi/v/bssclient)
![Unittests status badge](https://github.com/Hochfrequenz/bssclient.py/workflows/Unittests/badge.svg)
![Coverage status badge](https://github.com/Hochfrequenz/bssclient.py/workflows/Coverage/badge.svg)
![Linting status badge](https://github.com/Hochfrequenz/bssclient.py/workflows/Linting/badge.svg)
![Black status badge](https://github.com/Hochfrequenz/bssclient.py/workflows/Formatting/badge.svg)

This repository contains the package `tmdsclient`.
It is an async, fully typed Python client for the Technical Master Data Service (TMDS).
This repository contains the package `bssclient`.
It is an async, fully typed Python client for the Basic Supply Service (BSS).

Its features are handwritten and extended as we need them.
So it is _not_ autogenerated from the TMDS OpenAPI.

## Installation
Install it [from PyPI](https://pypi.org/project/tmdsclient/):
Install it [from PyPI](https://pypi.org/project/bssclient/):
```bash
pip install tmdsclient
pip install bssclient
```

## Usage

```python
from yarl import URL
from tmdsclient import TmdsClient, TmdsConfig
from bssclient import bssclient, BssConfig

tmds_config = TmdsConfig(
tmds_config = BssConfig(
server_url=URL("https://my-tmds.xtk-test.org/"),
usr="my-usr",
pwd="my-pwd",
)
client = TmdsClient(tmds_config)
client = bssclient(tmds_config)
netzvertrage = await client.get_netzvertraege_for_melo("DE1234567890123456789012345678901")
```

Even though we did not fully replicate the TMDS data model (mainly [BO4E.net](https://github.com/Hochfrequenz/BO4E-dotnet/) + some wrapper classes),
Even though we did not fully replicate the BSS data model,
we tried to make the client as flexible as possible.
You can use any unmapped field returned by TMDS by using the [`model_extra` property of pydantic](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra).

Expand All @@ -43,7 +44,7 @@ tl;dr: `tox -e dev` will set up a development environment for you.

### Release (CI/CD)

To release a new version of this library, [create a new release](https://github.com/Hochfrequenz/tmdsclient.py/releases/new) in GitHub.
To release a new version of this library, [create a new release](https://github.com/Hochfrequenz/bssclient.py/releases/new) in GitHub.
Make sure its tag starts with `v` and the version number, e.g. `v1.2.3`.
Releases are not restricted to the main branch, but we prefer them to happen there.

Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[project]
name = "tmdsclient"
name = "bssclient"
description = "Fully typed, async client library for Technical Master Data Service (TMDS)"
license = { text = "MIT" }
requires-python = ">=3.11"
Expand All @@ -25,7 +25,7 @@ dynamic = ["readme", "version"]

[project.urls]
Changelog = "https://github.com/Hochfrequenz/tmddsclient.py/releases"
Homepage = "https://github.com/Hochfrequenz/tmdsclient.py"
Homepage = "https://github.com/Hochfrequenz/bssclient.py"

[tool.black]
line-length = 120
Expand Down Expand Up @@ -61,7 +61,7 @@ fragments = [{ path = "README.md" }]
source = "vcs"

[tool.hatch.build.hooks.vcs]
version-file = "src/_tmdsclient_version.py"
version-file = "src/_bssclient_version.py"
template = '''
version = "{version}"
'''
Expand Down
8 changes: 3 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
# pip-compile pyproject.toml
#
aiohttp[speedups]==3.9.3
# via
# aiohttp
# tmdsclient (pyproject.toml)
# via bssclient (pyproject.toml)
aiosignal==1.3.1
# via aiohttp
annotated-types==0.6.0
Expand All @@ -23,15 +21,15 @@ frozenlist==1.4.1
idna==3.6
# via yarl
jsonpatch==1.33
# via tmdsclient (pyproject.toml)
# via bssclient (pyproject.toml)
jsonpointer==2.4
# via jsonpatch
multidict==6.0.5
# via
# aiohttp
# yarl
pydantic==2.6.2
# via tmdsclient (pyproject.toml)
# via bssclient (pyproject.toml)
pydantic-core==2.16.3
# via pydantic
typing-extensions==4.10.0
Expand Down
7 changes: 3 additions & 4 deletions src/tmdsclient/__init__.py → src/bssclient/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
"""
tmdsclient is a fully typed, async client for the TMDS API.
bssclient is a fully typed, async client for the TMDS API.
The client is _not_ autogenerated.
Find the TMDS Open API description here: https://techmasterdata.xtk-dev.de/index.html
"""

from .client.config import TmdsConfig

# convenience exports
from .client.tmdsclient import TmdsClient
from .client.bssclient import BssClient
from .client.config import BssConfig
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@

from aiohttp import BasicAuth, ClientSession, ClientTimeout

from tmdsclient.client.config import TmdsConfig
from tmdsclient.models.netzvertrag import Netzvertrag, _ListOfNetzvertraege
from tmdsclient.models.patches import build_json_patch_document
from bssclient.client.config import BssConfig
from bssclient.models.netzvertrag import Netzvertrag, _ListOfNetzvertraege
from bssclient.models.patches import build_json_patch_document

_logger = logging.getLogger(__name__)


class TmdsClient:
class BssClient:
"""
an async wrapper around the TMDS API
"""

def __init__(self, config: TmdsConfig):
def __init__(self, config: BssConfig):
self._config = config
self._auth = BasicAuth(login=self._config.usr, password=self._config.pwd)
self._session_lock = asyncio.Lock()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
"""
contains a class with which the TMDS client is instantiated/configured
contains a class with which the BSS client is instantiated/configured
"""

from pydantic import BaseModel, ConfigDict, field_validator
from yarl import URL


class TmdsConfig(BaseModel):
class BssConfig(BaseModel):
"""
A class to hold the configuration for the TMDS client
A class to hold the configuration for the BSS client
"""

model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)

server_url: URL
"""
e.g. URL("https://techmasterdata.xtk-dev.de")
e.g. URL("https://basicsupply.xtk-stage.de/")
"""
usr: str
"""
Expand Down Expand Up @@ -48,5 +48,5 @@ def validate_url(cls, value):
if not isinstance(value, URL):
raise ValueError("Invalid URL type")
if len(value.parts) > 2:
raise ValueError("You must provide a base_url without any parts, e.g. https://techmasterdata.xtk-prod.de")
raise ValueError("You must provide a base_url without any parts, e.g. https://basicsupply.xtk-prod.de/")
return value
3 changes: 3 additions & 0 deletions src/bssclient/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
'models' contains the BSS data model (or at least the part which we use in this client)
"""
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 0 additions & 3 deletions src/tmdsclient/models/__init__.py

This file was deleted.

4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ deps =
# add your fixtures like e.g. pytest_datafiles here
setenv = PYTHONPATH = {toxinidir}/src
commands =
pylint tmdsclient
pylint bssclient
pylint unittests --rcfile=unittests/.pylintrc
# add single files (ending with .py) or packages here

Expand All @@ -37,7 +37,7 @@ deps =
{[testenv:tests]deps}
-r dev_requirements/requirements-type_check.txt
commands =
mypy --show-error-codes src/tmdsclient
mypy --show-error-codes src/bssclient
mypy --show-error-codes unittests
# add single files (ending with .py) or packages here

Expand Down
10 changes: 5 additions & 5 deletions unittests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@
import pytest
from yarl import URL

from tmdsclient import TmdsClient, TmdsConfig
from bssclient import BssClient, BssConfig


@pytest.fixture
async def tmds_client_with_default_auth() -> AsyncGenerator[tuple[TmdsClient, TmdsConfig], None]:
async def bss_client_with_default_auth() -> AsyncGenerator[tuple[BssClient, BssConfig], None]:
"""
"mention" this fixture in the signature of your test to run the code up to yield before the respective test
(and the code after yield the test execution)
:return:
"""
tmds_config = TmdsConfig(
server_url=URL("https://tmds.inv/"),
tmds_config = BssConfig(
server_url=URL("https://bss.inv/"),
usr="my-usr",
pwd="my-pwd",
)
client = TmdsClient(tmds_config)
client = BssClient(tmds_config)
yield client, tmds_config
await client.close_session()
14 changes: 7 additions & 7 deletions unittests/test_netzvertraege.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,32 @@
from aioresponses import CallbackResult, aioresponses
from jsonpatch import JsonPatch # type:ignore[import]

from tmdsclient.models.netzvertrag import Bo4eVertrag, Netzvertrag, Vertragsstatus
from bssclient.models.netzvertrag import Bo4eVertrag, Netzvertrag, Vertragsstatus


class TestGetNetzvertraege:
"""
A class with pytest unit tests.
"""

async def test_get_netzvertrag_by_id(self, tmds_client_with_default_auth):
async def test_get_netzvertrag_by_id(self, bss_client_with_default_auth):
netzvertrag_json_file = Path(__file__).parent / "example_data" / "single_netzvertrag.json"
with open(netzvertrag_json_file, "r", encoding="utf-8") as infile:
netzvertrag_json = json.load(infile)
nv_id = uuid.UUID("3e15bf73-ea1b-4f50-8f18-3288074a4fec")
client, tmds_config = tmds_client_with_default_auth
client, tmds_config = bss_client_with_default_auth
with aioresponses() as mocked_tmds:
mocked_get_url = f"{tmds_config.server_url}api/Netzvertrag/{nv_id}"
mocked_tmds.get(mocked_get_url, status=200, payload=netzvertrag_json)
actual = await client.get_netzvertrag_by_id(nv_id)
assert isinstance(actual, Netzvertrag)

async def test_get_netzvertraege_by_melo(self, tmds_client_with_default_auth):
async def test_get_netzvertraege_by_melo(self, bss_client_with_default_auth):
netzvertraege_json_file = Path(__file__).parent / "example_data" / "list_of_netzvertraege.json"
with open(netzvertraege_json_file, "r", encoding="utf-8") as infile:
netzvertraege_json = json.load(infile)
melo_id = "DE0011122233344455566677788899900"
client, tmds_config = tmds_client_with_default_auth
client, tmds_config = bss_client_with_default_auth
with aioresponses() as mocked_tmds:
mocked_get_url = f"{tmds_config.server_url}api/Netzvertrag/find?messlokation={melo_id}"
mocked_tmds.get(mocked_get_url, status=200, payload=netzvertraege_json)
Expand All @@ -42,13 +42,13 @@ async def test_get_netzvertraege_by_melo(self, tmds_client_with_default_auth):
assert any(actual[0].model_extra), "Unmapped properties should be stored in model_extra (Netzvertrag)"
assert any(actual[0].bo_model.model_extra), "Unmapped properties should be stored in model_extra (Bo4eVertrag)"

async def test_update_netzvertrag(self, tmds_client_with_default_auth):
async def test_update_netzvertrag(self, bss_client_with_default_auth):
netzvertrag_json_file = Path(__file__).parent / "example_data" / "single_netzvertrag.json"
with open(netzvertrag_json_file, "r", encoding="utf-8") as infile:
netzvertrag_json = json.load(infile)

nv_id = uuid.UUID("3e15bf73-ea1b-4f50-8f18-3288074a4fec")
client, tmds_config = tmds_client_with_default_auth
client, tmds_config = bss_client_with_default_auth

def set_status_to_storniert(nv: Netzvertrag) -> None:
assert nv.bo_model is not None
Expand Down
4 changes: 2 additions & 2 deletions unittests/test_patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import pytest
from jsonpatch import JsonPatch # type:ignore[import]

from tmdsclient.models.netzvertrag import Bo4eVertrag, Netzvertrag, Vertragsstatus
from tmdsclient.models.patches import build_json_patch_document
from bssclient.models.netzvertrag import Bo4eVertrag, Netzvertrag, Vertragsstatus
from bssclient.models.patches import build_json_patch_document


def _set_netzvertrag_vertragsbeginn(nv: Netzvertrag, vertragsbeginn: datetime) -> None:
Expand Down

0 comments on commit 629d698

Please sign in to comment.