Skip to content

Commit 7a033e0

Browse files
hf-kkleinKonstantin
and
Konstantin
authored
feat: add possibility to PATCH zaehlers via v2 API w/ optional keydate (#235)
* feat: add possibility to PATCH zaehlers via v2 API w/ optional keydate * mypy * pylint * copilot suggestion nitpick] There is a redundant check for '_zaehler.boModel' immediately after asserting that it is not None. Removing this redundant check could streamline the code. --------- Co-authored-by: Konstantin <[email protected]>
1 parent 99eaea1 commit 7a033e0

File tree

2 files changed

+74
-2
lines changed

2 files changed

+74
-2
lines changed

src/tmdsclient/client/tmdsclient.py

+37-1
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,40 @@ async def update_marktlokation(
383383
result = Marktlokation.model_validate(response_json)
384384
return result
385385

386+
async def update_zaehler(
387+
self,
388+
zaehler_id: uuid.UUID,
389+
changes: list[Callable[[Zaehler], None]] | JsonPatch,
390+
keydate: AwareDatetime | None = None,
391+
) -> Zaehler:
392+
"""
393+
patch the given zaehler using the changes
394+
"""
395+
session = await self._get_session()
396+
zaehler = await self.get_zaehler(zaehler_id, keydate)
397+
if zaehler is None:
398+
raise ValueError(f"Zaehler with id '{zaehler_id}' not found")
399+
patch_document: jsonpatch.JsonPatch
400+
if isinstance(changes, list) and len(changes) > 0 and not isinstance(changes[0], dict):
401+
# we assume that "not isinstance(changes[0], dict)" == isinstance(changes[0], Callable)
402+
patch_document = build_json_patch_document(zaehler, changes) # type:ignore[arg-type]
403+
else:
404+
# assume it's the patch itself
405+
patch_document = jsonpatch.JsonPatch(changes)
406+
request_url = self._config.server_url / "api" / "v2" / "Zaehler" / str(zaehler_id)
407+
if keydate is not None: # if it's None it defaults to now(UTC) on serverside anyway
408+
request_url = request_url % {"aenderungsDatum": keydate.isoformat()}
409+
request_uuid = uuid.uuid4()
410+
_logger.debug("[%s] patching %s with body %s", str(request_uuid), request_url, str(patch_document))
411+
async with session.patch(
412+
request_url, json=patch_document.patch, headers={"Content-Type": "application/json-patch+json"}
413+
) as response:
414+
response.raise_for_status()
415+
_logger.debug("[%s] response status: %s", str(request_uuid), response.status)
416+
response_json = await response.json()
417+
result = Zaehler.model_validate(response_json)
418+
return result
419+
386420
async def get_messlokation(self, messlokation_id: str) -> Messlokation | None:
387421
"""
388422
provide a Messlokation-ID, get the matching MeLo in return (or None, if 404)
@@ -402,12 +436,14 @@ async def get_messlokation(self, messlokation_id: str) -> Messlokation | None:
402436
result = Messlokation.model_validate(response_json)
403437
return result
404438

405-
async def get_zaehler(self, zaehler_id: uuid.UUID) -> Zaehler | None:
439+
async def get_zaehler(self, zaehler_id: uuid.UUID, keydate: AwareDatetime | None = None) -> Zaehler | None:
406440
"""
407441
provide a Zaehler-ID, get the matching Zaehler in return (or None, if 404)
408442
"""
409443
session = await self._get_session()
410444
request_url = self._config.server_url / "api" / "Zaehler" / str(zaehler_id)
445+
if keydate is not None:
446+
request_url = request_url / keydate.isoformat()
411447
request_uuid = uuid.uuid4()
412448
_logger.debug("[%s] requesting %s", str(request_uuid), request_url)
413449
async with session.get(request_url) as response:

unittests/test_zaehler.py

+37-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import uuid
2+
from datetime import UTC, datetime
23

3-
from aioresponses import aioresponses
4+
from aioresponses import CallbackResult, aioresponses
5+
from bo4e import Sparte
6+
from jsonpatch import JsonPatch # type:ignore[import-untyped]
47

58
from tmdsclient.models.zaehler import Zaehler
9+
from tmdsclient.models.zaehler_bo_model import Zaehlertyp
610

711

812
def _get_zaehler_model() -> Zaehler:
@@ -99,3 +103,35 @@ async def test_set_zaehler_schmutzwasser_relevanz(self, tmds_client_with_default
99103
)
100104
was_set_successfully = await client.set_schmutzwasser_relevanz(zaehler_id, True)
101105
assert was_set_successfully is True
106+
107+
async def test_update_zaehler(self, tmds_client_with_default_auth) -> None:
108+
zaehler = _get_zaehler_model()
109+
zaehler_json = zaehler.model_dump(mode="json", by_alias=True)
110+
zaehler.boModel.sparte = Sparte.WASSER
111+
zaehler.boModel.zaehlertyp = Zaehlertyp.DREHSTROMZAEHLER
112+
client, tmds_config = tmds_client_with_default_auth
113+
114+
def change_zaehlertyp_to_wasserzaehler(_zaehler: Zaehler) -> None:
115+
assert _zaehler.boModel is not None
116+
_zaehler.boModel.zaehlertyp = Zaehlertyp.WASSERZAEHLER
117+
118+
def patch_endpoint_callback(url, **kwargs): # pylint:disable=unused-argument
119+
request_body = kwargs["json"]
120+
json_patch = JsonPatch(request_body)
121+
modified_zaehler_json = zaehler_json.copy()
122+
result = json_patch.apply(modified_zaehler_json)
123+
return CallbackResult(status=200, payload=result)
124+
125+
with aioresponses() as mocked_tmds:
126+
mocked_get_url = f"{tmds_config.server_url}api/Zaehler/{zaehler.id}/2025-01-01T00:00:00+00:00"
127+
mocked_tmds.get(mocked_get_url, status=200, payload=zaehler_json)
128+
# pylint:disable=line-too-long
129+
mocked_patch_url = f"{tmds_config.server_url}api/v2/Zaehler/{zaehler.id}?aenderungsDatum=2025-01-01T00%253A00%253A00%252B00%253A00"
130+
mocked_tmds.patch(mocked_patch_url, callback=patch_endpoint_callback)
131+
actual = await client.update_zaehler(
132+
zaehler.id, [change_zaehlertyp_to_wasserzaehler], datetime(2025, 1, 1, 0, 0, 0).replace(tzinfo=UTC)
133+
)
134+
assert isinstance(actual, Zaehler)
135+
assert actual.boModel is not None
136+
assert actual.boModel.zaehlertyp is not None
137+
assert actual.boModel.zaehlertyp == Zaehlertyp.WASSERZAEHLER

0 commit comments

Comments
 (0)