Bläddra i källkod

Add Mapper for ApiGateway (#18)

Casper van der Wel 1 år sedan
förälder
incheckning
ca89cb99cc

+ 1 - 1
CHANGES.md

@@ -4,7 +4,7 @@
 0.6.3 (unreleased)
 ------------------
 
-- Nothing changed yet.
+- Add `Mapper` type use it in `SyncApiGateway.mapper`.
 
 
 0.6.2 (2023-10-02)

+ 9 - 4
clean_python/api_client/api_gateway.py

@@ -7,6 +7,7 @@ import inject
 from clean_python import DoesNotExist
 from clean_python import Id
 from clean_python import Json
+from clean_python import Mapper
 
 from .. import SyncGateway
 from .api_provider import SyncApiProvider
@@ -17,6 +18,7 @@ __all__ = ["SyncApiGateway"]
 
 class SyncApiGateway(SyncGateway):
     path: str
+    mapper = Mapper()
 
     def __init__(self, provider_override: Optional[SyncApiProvider] = None):
         self.provider_override = provider_override
@@ -33,16 +35,19 @@ class SyncApiGateway(SyncGateway):
 
     def get(self, id: Id) -> Optional[Json]:
         try:
-            return self.provider.request("GET", self.path.format(id=id))
+            result = self.provider.request("GET", self.path.format(id=id))
+            assert result is not None
+            return self.mapper.to_internal(result)
         except ApiException as e:
             if e.status is HTTPStatus.NOT_FOUND:
                 return None
             raise e
 
     def add(self, item: Json) -> Json:
+        item = self.mapper.to_external(item)
         result = self.provider.request("POST", self.path.format(id=""), json=item)
         assert result is not None
-        return result
+        return self.mapper.to_internal(result)
 
     def remove(self, id: Id) -> bool:
         try:
@@ -59,14 +64,14 @@ class SyncApiGateway(SyncGateway):
     ) -> Json:
         if if_unmodified_since is not None:
             raise NotImplementedError("if_unmodified_since not implemented")
-        item = item.copy()
+        item = self.mapper.to_external(item)
         id_ = item.pop("id", None)
         if id_ is None:
             raise DoesNotExist("resource", id_)
         try:
             result = self.provider.request("PATCH", self.path.format(id=id_), json=item)
             assert result is not None
-            return result
+            return self.mapper.to_internal(result)
         except ApiException as e:
             if e.status is HTTPStatus.NOT_FOUND:
                 raise DoesNotExist("resource", id_)

+ 1 - 0
clean_python/base/infrastructure/__init__.py

@@ -1,4 +1,5 @@
 from .in_memory_gateway import *  # NOQA
 from .internal_gateway import *  # NOQA
+from .mapper import *  # NOQA
 from .tmpdir_provider import *  # NOQA
 from .typed_internal_gateway import *  # NOQA

+ 9 - 0
clean_python/base/infrastructure/mapper.py

@@ -0,0 +1,9 @@
+from ..domain import Json
+
+
+class Mapper:
+    def to_internal(self, external: Json) -> Json:
+        return external
+
+    def to_external(self, internal: Json) -> Json:
+        return internal

+ 53 - 0
tests/api_client/test_sync_api_gateway.py

@@ -4,6 +4,8 @@ from unittest import mock
 import pytest
 
 from clean_python import DoesNotExist
+from clean_python import Json
+from clean_python import Mapper
 from clean_python.api_client import ApiException
 from clean_python.api_client import SyncApiGateway
 from clean_python.api_client import SyncApiProvider
@@ -78,3 +80,54 @@ def test_update_does_not_exist(api_gateway: SyncApiGateway):
     )
     with pytest.raises(DoesNotExist):
         api_gateway.update({"id": 2, "foo": "bar"})
+
+
+class TstMapper(Mapper):
+    def to_external(self, internal: Json) -> Json:
+        result = {}
+        if internal.get("id") is not None:
+            result["id"] = internal["id"]
+        if internal.get("name") is not None:
+            result["name"] = internal["name"].upper()
+        return result
+
+    def to_internal(self, external: Json) -> Json:
+        return {"id": external["id"], "name": external["name"].lower()}
+
+
+class TstMappedSyncApiGateway(SyncApiGateway, path="foo/{id}"):
+    mapper = TstMapper()
+
+
+@pytest.fixture
+def mapped_api_gateway(api_provider) -> SyncApiGateway:
+    return TstMappedSyncApiGateway(api_provider)
+
+
+def test_get_with_mapper(mapped_api_gateway: SyncApiGateway):
+    mapped_api_gateway.provider.request.return_value = {"id": 14, "name": "FOO"}
+
+    assert mapped_api_gateway.get(14) == {"id": 14, "name": "foo"}
+
+
+def test_add_with_mapper(mapped_api_gateway: SyncApiGateway):
+    mapped_api_gateway.provider.request.return_value = {"id": 3, "name": "FOO"}
+
+    assert mapped_api_gateway.add({"name": "foo"}) == {"id": 3, "name": "foo"}
+
+    mapped_api_gateway.provider.request.assert_called_once_with(
+        "POST", "foo/", json={"name": "FOO"}
+    )
+
+
+def test_update_with_mapper(mapped_api_gateway: SyncApiGateway):
+    mapped_api_gateway.provider.request.return_value = {"id": 2, "name": "BAR"}
+
+    assert mapped_api_gateway.update({"id": 2, "name": "bar"}) == {
+        "id": 2,
+        "name": "bar",
+    }
+
+    mapped_api_gateway.provider.request.assert_called_once_with(
+        "PATCH", "foo/2", json={"name": "BAR"}
+    )