Skip to content

Commit

Permalink
Adapt internal gateway (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
caspervdw authored Sep 12, 2023
1 parent ef76b33 commit f59dd18
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 44 deletions.
6 changes: 4 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Changelog of clean-python


0.4.4 (unreleased)
0.5.0 (unreleased)
------------------

- Nothing changed yet.
- Adapt `InternalGateway` so that it derives from `Gateway`.

- Renamed the old `InternalGateway` to `TypedInternalGateway`.


0.4.3 (2023-09-11)
Expand Down
1 change: 1 addition & 0 deletions clean_python/base/infrastructure/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .in_memory_gateway import * # NOQA
from .internal_gateway import * # NOQA
from .tmpdir_provider import * # NOQA
from .typed_internal_gateway import * # NOQA
44 changes: 21 additions & 23 deletions clean_python/base/infrastructure/internal_gateway.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# (c) Nelen & Schuurmans
from abc import abstractmethod
from abc import abstractproperty
from datetime import datetime
from typing import Generic
from typing import List
from typing import Optional
Expand All @@ -10,50 +11,44 @@
from clean_python.base.domain import BadRequest
from clean_python.base.domain import DoesNotExist
from clean_python.base.domain import Filter
from clean_python.base.domain import Gateway
from clean_python.base.domain import Id
from clean_python.base.domain import Json
from clean_python.base.domain import PageOptions
from clean_python.base.domain import RootEntity
from clean_python.base.domain import ValueObject

__all__ = ["InternalGateway"]


E = TypeVar("E", bound=RootEntity) # External
T = TypeVar("T", bound=ValueObject) # Internal
T = TypeVar("T", bound=RootEntity) # External


# don't subclass Gateway; Gateway makes Json objects
class InternalGateway(Generic[E, T]):
class InternalGateway(Gateway, Generic[T]):
@abstractproperty
def manage(self) -> Manage[E]:
def manage(self) -> Manage[T]:
raise NotImplementedError()

@abstractmethod
def _map(self, obj: E) -> T:
def to_internal(self, obj: T) -> Json:
raise NotImplementedError()

async def get(self, id: int) -> Optional[T]:
try:
result = await self.manage.retrieve(id)
except DoesNotExist:
return None
else:
return self._map(result)
def to_external(self, values: Json) -> Json:
return values

async def filter(
self, filters: List[Filter], params: Optional[PageOptions] = None
) -> List[T]:
) -> List[Json]:
page = await self.manage.filter(filters, params)
return [self._map(x) for x in page.items]
return [self.to_internal(x) for x in page.items]

async def add(self, item: T) -> T:
async def add(self, item: Json) -> Json:
try:
created = await self.manage.create(item.model_dump())
created = await self.manage.create(self.to_external(item))
except BadRequest as e:
raise ValueError(e)
return self._map(created)
return self.to_internal(created)

async def remove(self, id) -> bool:
async def remove(self, id: Id) -> bool:
return await self.manage.destroy(id)

async def count(self, filters: List[Filter]) -> int:
Expand All @@ -62,13 +57,16 @@ async def count(self, filters: List[Filter]) -> int:
async def exists(self, filters: List[Filter]) -> bool:
return await self.manage.exists(filters)

async def update(self, values: Json) -> T:
values = values.copy()
async def update(
self, item: Json, if_unmodified_since: Optional[datetime] = None
) -> Json:
assert if_unmodified_since is None # unsupported
values = self.to_external(item)
id_ = values.pop("id", None)
if id_ is None:
raise DoesNotExist("item", id_)
try:
updated = await self.manage.update(id_, values)
except BadRequest as e:
raise ValueError(e)
return self._map(updated)
return self.to_internal(updated)
74 changes: 74 additions & 0 deletions clean_python/base/infrastructure/typed_internal_gateway.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# (c) Nelen & Schuurmans
from abc import abstractmethod
from abc import abstractproperty
from typing import Generic
from typing import List
from typing import Optional
from typing import TypeVar

from clean_python.base.application.manage import Manage
from clean_python.base.domain import BadRequest
from clean_python.base.domain import DoesNotExist
from clean_python.base.domain import Filter
from clean_python.base.domain import Json
from clean_python.base.domain import PageOptions
from clean_python.base.domain import RootEntity
from clean_python.base.domain import ValueObject

__all__ = ["TypedInternalGateway"]


E = TypeVar("E", bound=RootEntity) # External
T = TypeVar("T", bound=ValueObject) # Internal


# don't subclass Gateway; Gateway makes Json objects
class TypedInternalGateway(Generic[E, T]):
@abstractproperty
def manage(self) -> Manage[E]:
raise NotImplementedError()

@abstractmethod
def _map(self, obj: E) -> T:
raise NotImplementedError()

async def get(self, id: int) -> Optional[T]:
try:
result = await self.manage.retrieve(id)
except DoesNotExist:
return None
else:
return self._map(result)

async def filter(
self, filters: List[Filter], params: Optional[PageOptions] = None
) -> List[T]:
page = await self.manage.filter(filters, params)
return [self._map(x) for x in page.items]

async def add(self, item: T) -> T:
try:
created = await self.manage.create(item.model_dump())
except BadRequest as e:
raise ValueError(e)
return self._map(created)

async def remove(self, id) -> bool:
return await self.manage.destroy(id)

async def count(self, filters: List[Filter]) -> int:
return await self.manage.count(filters)

async def exists(self, filters: List[Filter]) -> bool:
return await self.manage.exists(filters)

async def update(self, values: Json) -> T:
values = values.copy()
id_ = values.pop("id", None)
if id_ is None:
raise DoesNotExist("item", id_)
try:
updated = await self.manage.update(id_, values)
except BadRequest as e:
raise ValueError(e)
return self._map(updated)
29 changes: 10 additions & 19 deletions tests/test_internal_gateway.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import cast

import pytest
from pydantic import Field

Expand All @@ -13,7 +11,6 @@
from clean_python import Manage
from clean_python import Repository
from clean_python import RootEntity
from clean_python import ValueObject


# domain - other module
Expand All @@ -36,25 +33,19 @@ async def update(self, id: Id, values: Json) -> User:
return await self.repo.update(id, values)


# domain - this module
class UserObj(ValueObject):
id: int
name: str


# infrastructure - this module


class UserGateway(InternalGateway[User, UserObj]):
class UserGateway(InternalGateway[User]):
def __init__(self, manage: ManageUser):
self._manage = manage

@property
def manage(self) -> ManageUser:
return self._manage

def _map(self, obj: User) -> UserObj:
return UserObj(id=cast(int, obj.id), name=obj.name)
def to_internal(self, obj: User) -> Json:
return dict(id=obj.id, name=obj.name)


@pytest.fixture
Expand All @@ -67,23 +58,23 @@ async def test_get_not_existing(internal_gateway: UserGateway):


async def test_add(internal_gateway: UserGateway):
actual = await internal_gateway.add(UserObj(id=12, name="foo"))
actual = await internal_gateway.add(dict(id=12, name="foo"))

assert actual == UserObj(id=12, name="foo")
assert actual == dict(id=12, name="foo")


@pytest.fixture
async def internal_gateway_with_record(internal_gateway):
await internal_gateway.add(UserObj(id=12, name="foo"))
await internal_gateway.add(dict(id=12, name="foo"))
return internal_gateway


async def test_get(internal_gateway_with_record):
assert await internal_gateway_with_record.get(12) == UserObj(id=12, name="foo")
assert await internal_gateway_with_record.get(12) == dict(id=12, name="foo")


async def test_filter(internal_gateway_with_record: UserGateway):
assert await internal_gateway_with_record.filter([]) == [UserObj(id=12, name="foo")]
assert await internal_gateway_with_record.filter([]) == [dict(id=12, name="foo")]


async def test_filter_2(internal_gateway_with_record: UserGateway):
Expand All @@ -107,7 +98,7 @@ async def test_add_bad_request(internal_gateway: UserGateway):
# a 'bad request' should be reraised as a ValueError; errors in gateways
# are an internal affair.
with pytest.raises(ValueError):
await internal_gateway.add(UserObj(id=12, name=""))
await internal_gateway.add(dict(id=12, name=""))


async def test_count(internal_gateway_with_record: UserGateway):
Expand All @@ -134,7 +125,7 @@ async def test_exists_2(internal_gateway_with_record: UserGateway):
async def test_update(internal_gateway_with_record):
updated = await internal_gateway_with_record.update({"id": 12, "name": "bar"})

assert updated == UserObj(id=12, name="bar")
assert updated == dict(id=12, name="bar")


@pytest.mark.parametrize(
Expand Down
Loading

0 comments on commit f59dd18

Please sign in to comment.