فهرست منبع

Clean imports

Casper van der Wel 1 سال پیش
والد
کامیت
757bfd9284
56فایلهای تغییر یافته به همراه176 افزوده شده و 374 حذف شده
  1. 1 29
      clean_python/__init__.py
  2. 4 0
      clean_python/base/__init__.py
  3. 1 0
      clean_python/base/application/__init__.py
  4. 6 7
      clean_python/base/application/manage.py
  5. 11 0
      clean_python/base/domain/__init__.py
  6. 0 0
      clean_python/base/domain/child_entity.py
  7. 0 1
      clean_python/base/domain/domain_event.py
  8. 4 0
      clean_python/base/domain/domain_service.py
  9. 0 1
      clean_python/base/domain/exceptions.py
  10. 0 1
      clean_python/base/domain/pagination.py
  11. 7 8
      clean_python/base/domain/repository.py
  12. 13 3
      clean_python/base/domain/root_entity.py
  13. 5 0
      clean_python/base/domain/value.py
  14. 5 0
      clean_python/base/domain/value_object.py
  15. 3 0
      clean_python/base/infrastructure/__init__.py
  16. 0 131
      clean_python/base/infrastructure/gateway.py
  17. 6 7
      clean_python/base/infrastructure/internal_gateway.py
  18. 0 7
      clean_python/base/infrastructure/now.py
  19. 2 0
      clean_python/base/infrastructure/tmpdir_provider.py
  20. 1 0
      clean_python/base/presentation/__init__.py
  21. 4 2
      clean_python/base/presentation/link.py
  22. 1 0
      clean_python/celery/__init__.py
  23. 3 4
      clean_python/celery/celery_rmq_broker.py
  24. 3 0
      clean_python/dramatiq/__init__.py
  25. 0 3
      clean_python/dramatiq/async_actor.py
  26. 5 3
      clean_python/dramatiq/dramatiq_task_logger.py
  27. 0 1
      clean_python/dramatiq/sleep_task.py
  28. 6 0
      clean_python/fastapi/__init__.py
  29. 0 1
      clean_python/fastapi/context.py
  30. 8 6
      clean_python/fastapi/error_responses.py
  31. 4 2
      clean_python/fastapi/fastapi_access_logger.py
  32. 3 4
      clean_python/fastapi/request_query.py
  33. 3 1
      clean_python/fastapi/resource.py
  34. 9 7
      clean_python/fastapi/service.py
  35. 1 0
      clean_python/fluentbit/__init__.py
  36. 3 6
      clean_python/fluentbit/fluentbit_gateway.py
  37. 1 0
      clean_python/oauth2/__init__.py
  38. 7 11
      clean_python/oauth2/oauth2.py
  39. 2 0
      clean_python/sql/__init__.py
  40. 9 8
      clean_python/sql/sql_gateway.py
  41. 3 59
      clean_python/sql/sql_provider.py
  42. 2 0
      clean_python/testing/__init__.py
  43. 3 0
      clean_python/testing/attr_dict.py
  44. 2 0
      clean_python/testing/profilers.py
  45. 0 34
      clean_python/testing/testing.py
  46. 3 3
      tests/test_async_actor.py
  47. 1 1
      tests/test_celery_rmq_broker.py
  48. 2 2
      tests/test_dramatiq_task_logger.py
  49. 3 3
      tests/test_exceptions.py
  50. 1 1
      tests/test_fastapi_access_logger.py
  51. 0 1
      tests/test_manage.py
  52. 1 2
      tests/test_oauth2.py
  53. 3 3
      tests/test_request_query.py
  54. 5 5
      tests/test_resource.py
  55. 3 3
      tests/test_service.py
  56. 3 3
      tests/test_sql_gateway.py

+ 1 - 29
clean_python/__init__.py

@@ -1,34 +1,6 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
-from .base.application.manage import Manage  # NOQA
-from .base.domain.domain_event import *  # NOQA
-from .base.domain.domain_service import DomainService  # NOQA
-from .base.domain.exceptions import *  # NOQA
-from .base.domain.pagination import *  # NOQA
-from .base.domain.repository import Repository  # NOQA
-from .base.domain.root_entity import RootEntity  # NOQA
-from .base.domain.value import Value  # NOQA
-from .base.domain.value_object import ValueObject  # NOQA
-from .base.domain.value_object import ValueObjectWithId  # NOQA
-from .base.infrastructure.gateway import *  # NOQA
-from .base.infrastructure.internal_gateway import InternalGateway  # NOQA
-from .base.infrastructure.now import now  # NOQA
-from .base.infrastructure.tmpdir_provider import *  # NOQA
-from .base.presentation.link import Link  # NOQA
-from .celery.celery_rmq_broker import *  # NOQA
-from .dramatiq.async_actor import *  # NOQA
-from .dramatiq.dramatiq_task_logger import *  # NOQA
-from .fastapi.context import *  # NOQA
-from .fastapi.fastapi_access_logger import *  # NOQA
-from .fastapi.request_query import *  # NOQA
-from .fastapi.resource import *  # NOQA
-from .fastapi.service import Service  # NOQA
-from .fluentbit.fluentbit_gateway import FluentbitGateway  # NOQA
-from .oauth2.oauth2 import *  # NOQA
-from .sql.sql_gateway import SQLGateway  # NOQA
-from .sql.sql_provider import *  # NOQA
-from .testing.attr_dict import AttrDict  # NOQA
+from .base import *  # NOQA
 
 
 # fmt: off
 # fmt: off
 __version__ = '0.0.1.dev0'
 __version__ = '0.0.1.dev0'

+ 4 - 0
clean_python/base/__init__.py

@@ -0,0 +1,4 @@
+from .application import *  # NOQA
+from .domain import *  # NOQA
+from .infrastructure import *  # NOQA
+from .presentation import *  # NOQA

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

@@ -0,0 +1 @@
+from .manage import *  # NOQA

+ 6 - 7
clean_python/base/application/manage.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
 from typing import Any
 from typing import Any
@@ -8,12 +7,12 @@ from typing import Optional
 from typing import Type
 from typing import Type
 from typing import TypeVar
 from typing import TypeVar
 
 
-from clean_python.base.domain.pagination import Page
-from clean_python.base.domain.pagination import PageOptions
-from clean_python.base.domain.repository import Repository
-from clean_python.base.domain.root_entity import RootEntity
-from clean_python.base.infrastructure.gateway import Filter
-from clean_python.base.infrastructure.gateway import Json
+from clean_python.base.domain import Filter
+from clean_python.base.domain import Json
+from clean_python.base.domain import Page
+from clean_python.base.domain import PageOptions
+from clean_python.base.domain import Repository
+from clean_python.base.domain import RootEntity
 
 
 T = TypeVar("T", bound=RootEntity)
 T = TypeVar("T", bound=RootEntity)
 
 

+ 11 - 0
clean_python/base/domain/__init__.py

@@ -0,0 +1,11 @@
+from .domain_event import *  # NOQA
+from .domain_service import *  # NOQA
+from .exceptions import *  # NOQA
+from .filter import *  # NOQA
+from .gateway import *  # NOQA
+from .json import *  # NOQA
+from .pagination import *  # NOQA
+from .repository import *  # NOQA
+from .root_entity import *  # NOQA
+from .value import *  # NOQA
+from .value_object import *  # NOQA

+ 0 - 0
clean_python/base/domain/child_entity.py


+ 0 - 1
clean_python/base/domain/domain_event.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
 from abc import ABC
 from abc import ABC

+ 4 - 0
clean_python/base/domain/domain_service.py

@@ -1,5 +1,9 @@
+# (c) Nelen & Schuurmans
+
 from pydantic import BaseModel
 from pydantic import BaseModel
 
 
+__all__ = ["DomainService"]
+
 
 
 class DomainService(BaseModel):
 class DomainService(BaseModel):
     class Config:
     class Config:

+ 0 - 1
clean_python/base/domain/exceptions.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
 from typing import Any
 from typing import Any

+ 0 - 1
clean_python/base/domain/pagination.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
 from typing import Generic
 from typing import Generic

+ 7 - 8
clean_python/base/domain/repository.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
 from typing import Any
 from typing import Any
@@ -9,13 +8,13 @@ from typing import Type
 from typing import TypeVar
 from typing import TypeVar
 from typing import Union
 from typing import Union
 
 
-from clean_python.base.domain.exceptions import DoesNotExist
-from clean_python.base.domain.pagination import Page
-from clean_python.base.domain.pagination import PageOptions
-from clean_python.base.domain.root_entity import RootEntity
-from clean_python.base.infrastructure.gateway import Filter
-from clean_python.base.infrastructure.gateway import Gateway
-from clean_python.base.infrastructure.gateway import Json
+from .exceptions import DoesNotExist
+from .filter import Filter
+from .gateway import Gateway
+from .json import Json
+from .pagination import Page
+from .pagination import PageOptions
+from .root_entity import RootEntity
 
 
 T = TypeVar("T", bound=RootEntity)
 T = TypeVar("T", bound=RootEntity)
 
 

+ 13 - 3
clean_python/base/domain/root_entity.py

@@ -1,14 +1,24 @@
+# (c) Nelen & Schuurmans
+
 from datetime import datetime
 from datetime import datetime
+from datetime import timezone
 from typing import Optional
 from typing import Optional
 from typing import Type
 from typing import Type
 from typing import TypeVar
 from typing import TypeVar
 
 
-from clean_python.base.domain.exceptions import BadRequest
-from clean_python.base.domain.value_object import ValueObject
-from clean_python.base.infrastructure.now import now
+from .exceptions import BadRequest
+from .value_object import ValueObject
+
+
+def now():
+    # this function is there so that we can mock it in tests
+    return datetime.now(timezone.utc)
+
 
 
 T = TypeVar("T", bound="RootEntity")
 T = TypeVar("T", bound="RootEntity")
 
 
+__all__ = ["RootEntity", "now"]
+
 
 
 class RootEntity(ValueObject):
 class RootEntity(ValueObject):
     id: Optional[int] = None
     id: Optional[int] = None

+ 5 - 0
clean_python/base/domain/value.py

@@ -1,3 +1,8 @@
+# (c) Nelen & Schuurmans
+
+__all__ = ["Value"]
+
+
 class Value:
 class Value:
     @classmethod
     @classmethod
     def __get_validators__(cls):
     def __get_validators__(cls):

+ 5 - 0
clean_python/base/domain/value_object.py

@@ -1,3 +1,5 @@
+# (c) Nelen & Schuurmans
+
 from typing import Optional
 from typing import Optional
 from typing import Type
 from typing import Type
 from typing import TypeVar
 from typing import TypeVar
@@ -7,6 +9,9 @@ from pydantic import ValidationError
 
 
 from .exceptions import BadRequest
 from .exceptions import BadRequest
 
 
+__all__ = ["ValueObject"]
+
+
 T = TypeVar("T", bound="ValueObject")
 T = TypeVar("T", bound="ValueObject")
 
 
 
 

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

@@ -0,0 +1,3 @@
+from .in_memory_gateway import *  # NOQA
+from .internal_gateway import *  # NOQA
+from .tmpdir_provider import *  # NOQA

+ 0 - 131
clean_python/base/infrastructure/gateway.py

@@ -1,131 +0,0 @@
-# -*- coding: utf-8 -*-
-# (c) Nelen & Schuurmans
-
-from copy import deepcopy
-from datetime import datetime
-from typing import Any
-from typing import Callable
-from typing import Dict
-from typing import List
-from typing import Optional
-
-from clean_python.base.domain.exceptions import AlreadyExists
-from clean_python.base.domain.exceptions import Conflict
-from clean_python.base.domain.exceptions import DoesNotExist
-from clean_python.base.domain.pagination import PageOptions
-from clean_python.base.domain.value_object import ValueObject
-
-__all__ = ["Gateway", "Json", "Filter", "InMemoryGateway"]
-Json = Dict[str, Any]
-
-
-class Filter(ValueObject):
-    field: str
-    values: List[Any]
-
-
-class Gateway:
-    async def filter(
-        self, filters: List[Filter], params: Optional[PageOptions] = None
-    ) -> List[Json]:
-        raise NotImplementedError()
-
-    async def count(self, filters: List[Filter]) -> int:
-        return len(await self.filter(filters, params=None))
-
-    async def exists(self, filters: List[Filter]) -> bool:
-        return len(await self.filter(filters, params=PageOptions(limit=1))) > 0
-
-    async def get(self, id: int) -> Optional[Json]:
-        result = await self.filter([Filter(field="id", values=[id])], params=None)
-        return result[0] if result else None
-
-    async def add(self, item: Json) -> Json:
-        raise NotImplementedError()
-
-    async def update(
-        self, item: Json, if_unmodified_since: Optional[datetime] = None
-    ) -> Json:
-        raise NotImplementedError()
-
-    async def update_transactional(self, id: int, func: Callable[[Json], Json]) -> Json:
-        existing = await self.get(id)
-        if existing is None:
-            raise DoesNotExist("record", id)
-        return await self.update(
-            func(existing), if_unmodified_since=existing["updated_at"]
-        )
-
-    async def upsert(self, item: Json) -> Json:
-        try:
-            return await self.update(item)
-        except DoesNotExist:
-            return await self.add(item)
-
-    async def remove(self, id: int) -> bool:
-        raise NotImplementedError()
-
-
-class InMemoryGateway(Gateway):
-    """For testing purposes"""
-
-    def __init__(self, data: List[Json]):
-        self.data = {x["id"]: deepcopy(x) for x in data}
-
-    def _get_next_id(self) -> int:
-        if len(self.data) == 0:
-            return 1
-        else:
-            return max(self.data) + 1
-
-    def _paginate(self, objs: List[Json], params: PageOptions) -> List[Json]:
-        objs = sorted(
-            objs,
-            key=lambda x: (x.get(params.order_by) is None, x.get(params.order_by)),
-            reverse=not params.ascending,
-        )
-        return objs[params.offset : params.offset + params.limit]
-
-    async def filter(
-        self, filters: List[Filter], params: Optional[PageOptions] = None
-    ) -> List[Json]:
-        result = []
-        for x in self.data.values():
-            for filter in filters:
-                if x.get(filter.field) not in filter.values:
-                    break
-            else:
-                result.append(deepcopy(x))
-        if params is not None:
-            result = self._paginate(result, params)
-        return result
-
-    async def add(self, item: Json) -> Json:
-        item = item.copy()
-        id_ = item.pop("id", None)
-        # autoincrement (like SQL does)
-        if id_ is None:
-            id_ = self._get_next_id()
-        elif id_ in self.data:
-            raise AlreadyExists(id_)
-
-        self.data[id_] = {"id": id_, **item}
-        return deepcopy(self.data[id_])
-
-    async def update(
-        self, item: Json, if_unmodified_since: Optional[datetime] = None
-    ) -> Json:
-        _id = item.get("id")
-        if _id is None or _id not in self.data:
-            raise DoesNotExist("item", _id)
-        existing = self.data[_id]
-        if if_unmodified_since and existing.get("updated_at") != if_unmodified_since:
-            raise Conflict()
-        existing.update(item)
-        return deepcopy(existing)
-
-    async def remove(self, id: int) -> bool:
-        if id not in self.data:
-            return False
-        del self.data[id]
-        return True

+ 6 - 7
clean_python/base/infrastructure/internal_gateway.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 from abc import abstractmethod
 from abc import abstractmethod
 from abc import abstractproperty
 from abc import abstractproperty
@@ -8,12 +7,12 @@ from typing import Optional
 from typing import TypeVar
 from typing import TypeVar
 
 
 from clean_python.base.application.manage import Manage
 from clean_python.base.application.manage import Manage
-from clean_python.base.domain.exceptions import BadRequest
-from clean_python.base.domain.exceptions import DoesNotExist
-from clean_python.base.domain.pagination import PageOptions
-from clean_python.base.domain.root_entity import RootEntity
-from clean_python.base.domain.value_object import ValueObject
-from clean_python.base.infrastructure.gateway import Filter
+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 PageOptions
+from clean_python.base.domain import RootEntity
+from clean_python.base.domain import ValueObject
 
 
 E = TypeVar("E", bound=RootEntity)  # External
 E = TypeVar("E", bound=RootEntity)  # External
 T = TypeVar("T", bound=ValueObject)  # Internal
 T = TypeVar("T", bound=ValueObject)  # Internal

+ 0 - 7
clean_python/base/infrastructure/now.py

@@ -1,7 +0,0 @@
-from datetime import datetime
-from datetime import timezone
-
-
-def now():
-    # this function is there so that we can mock it in tests
-    return datetime.now(timezone.utc)

+ 2 - 0
clean_python/base/infrastructure/tmpdir_provider.py

@@ -1,3 +1,5 @@
+# (c) Nelen & Schuurmans
+
 from tempfile import TemporaryDirectory
 from tempfile import TemporaryDirectory
 from typing import Optional
 from typing import Optional
 
 

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

@@ -0,0 +1 @@
+from .link import *  # NOQA

+ 4 - 2
clean_python/base/presentation/link.py

@@ -1,8 +1,10 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
+from typing import TypedDict
+
 from pydantic import AnyHttpUrl
 from pydantic import AnyHttpUrl
-from typing_extensions import TypedDict
+
+__all__ = ["Link"]
 
 
 
 
 class Link(TypedDict):
 class Link(TypedDict):

+ 1 - 0
clean_python/celery/__init__.py

@@ -0,0 +1 @@
+from .celery_rmq_broker import *  # NOQA

+ 3 - 4
clean_python/celery/celery_rmq_broker.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
 import json
 import json
@@ -9,9 +8,9 @@ import pika
 from asgiref.sync import sync_to_async
 from asgiref.sync import sync_to_async
 from pydantic import AnyUrl
 from pydantic import AnyUrl
 
 
-from clean_python.base.domain.value_object import ValueObject
-from clean_python.base.infrastructure.gateway import Gateway
-from clean_python.base.infrastructure.gateway import Json
+from clean_python import Gateway
+from clean_python import Json
+from clean_python import ValueObject
 
 
 __all__ = ["CeleryRmqBroker"]
 __all__ = ["CeleryRmqBroker"]
 
 

+ 3 - 0
clean_python/dramatiq/__init__.py

@@ -0,0 +1,3 @@
+from .async_actor import *  # NOQA
+from .dramatiq_task_logger import *  # NOQA
+from .sleep_task import *  # NOQA

+ 0 - 3
clean_python/dramatiq/async_actor.py

@@ -1,8 +1,5 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
-"""Dramatiq configuration"""
-
 import asyncio
 import asyncio
 import logging
 import logging
 import threading
 import threading

+ 5 - 3
clean_python/dramatiq/dramatiq_task_logger.py

@@ -1,3 +1,5 @@
+# (c) Nelen & Schuurmans
+
 import os
 import os
 import threading
 import threading
 import time
 import time
@@ -11,10 +13,10 @@ from dramatiq.errors import Retry
 from dramatiq.message import Message
 from dramatiq.message import Message
 from dramatiq.middleware import SkipMessage
 from dramatiq.middleware import SkipMessage
 
 
-from clean_python.base.infrastructure.gateway import Gateway
-from clean_python.fluentbit.fluentbit_gateway import FluentbitGateway
+from clean_python import Gateway
+from clean_python.fluentbit import FluentbitGateway
 
 
-__all__ = ["AsyncLoggingMiddleware"]
+__all__ = ["AsyncLoggingMiddleware", "DramatiqTaskLogger"]
 
 
 
 
 class AsyncLoggingMiddleware(Middleware):
 class AsyncLoggingMiddleware(Middleware):

+ 0 - 1
clean_python/dramatiq/sleep_task.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
 import asyncio
 import asyncio

+ 6 - 0
clean_python/fastapi/__init__.py

@@ -0,0 +1,6 @@
+from .context import *  # NOQA
+from .error_responses import *  # NOQA
+from .fastapi_access_logger import *  # NOQA
+from .request_query import *  # NOQA
+from .resource import *  # NOQA
+from .service import *  # NOQA

+ 0 - 1
clean_python/fastapi/context.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
 from contextvars import ContextVar
 from contextvars import ContextVar

+ 8 - 6
clean_python/fastapi/error_responses.py

@@ -1,3 +1,5 @@
+# (c) Nelen & Schuurmans
+
 from typing import List
 from typing import List
 from typing import Union
 from typing import Union
 
 
@@ -6,12 +8,12 @@ from fastapi.requests import Request
 from fastapi.responses import JSONResponse
 from fastapi.responses import JSONResponse
 from starlette import status
 from starlette import status
 
 
-from clean_python.base.domain.exceptions import BadRequest
-from clean_python.base.domain.exceptions import Conflict
-from clean_python.base.domain.exceptions import DoesNotExist
-from clean_python.base.domain.exceptions import PermissionDenied
-from clean_python.base.domain.exceptions import Unauthorized
-from clean_python.base.domain.value_object import ValueObject
+from clean_python import BadRequest
+from clean_python import Conflict
+from clean_python import DoesNotExist
+from clean_python import PermissionDenied
+from clean_python import Unauthorized
+from clean_python import ValueObject
 
 
 __all__ = [
 __all__ = [
     "ValidationErrorResponse",
     "ValidationErrorResponse",

+ 4 - 2
clean_python/fastapi/fastapi_access_logger.py

@@ -1,3 +1,5 @@
+# (c) Nelen & Schuurmans
+
 import os
 import os
 import time
 import time
 from datetime import datetime
 from datetime import datetime
@@ -10,8 +12,8 @@ from starlette.background import BackgroundTasks
 from starlette.requests import Request
 from starlette.requests import Request
 from starlette.responses import Response
 from starlette.responses import Response
 
 
-from clean_python.base.infrastructure.gateway import Gateway
-from clean_python.fluentbit.fluentbit_gateway import FluentbitGateway
+from clean_python import Gateway
+from clean_python.fluentbit import FluentbitGateway
 
 
 __all__ = ["FastAPIAccessLogger"]
 __all__ = ["FastAPIAccessLogger"]
 
 

+ 3 - 4
clean_python/fastapi/request_query.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
 from typing import List
 from typing import List
@@ -6,9 +5,9 @@ from typing import List
 from fastapi import Query
 from fastapi import Query
 from pydantic import validator
 from pydantic import validator
 
 
-from clean_python.base.domain.pagination import PageOptions
-from clean_python.base.domain.value_object import ValueObject
-from clean_python.base.infrastructure.gateway import Filter
+from clean_python import Filter
+from clean_python import PageOptions
+from clean_python import ValueObject
 
 
 __all__ = ["RequestQuery"]
 __all__ = ["RequestQuery"]
 
 

+ 3 - 1
clean_python/fastapi/resource.py

@@ -1,3 +1,5 @@
+# (c) Nelen & Schuurmans
+
 from enum import Enum
 from enum import Enum
 from functools import partial
 from functools import partial
 from typing import Any
 from typing import Any
@@ -10,7 +12,7 @@ from typing import Type
 
 
 from fastapi.routing import APIRouter
 from fastapi.routing import APIRouter
 
 
-from clean_python.base.domain.value_object import ValueObject
+from clean_python import ValueObject
 
 
 __all__ = [
 __all__ = [
     "Resource",
     "Resource",

+ 9 - 7
clean_python/fastapi/service.py

@@ -1,3 +1,5 @@
+# (c) Nelen & Schuurmans
+
 import logging
 import logging
 from typing import Any
 from typing import Any
 from typing import Callable
 from typing import Callable
@@ -17,13 +19,13 @@ from starlette.status import HTTP_401_UNAUTHORIZED
 from starlette.status import HTTP_403_FORBIDDEN
 from starlette.status import HTTP_403_FORBIDDEN
 from starlette.types import ASGIApp
 from starlette.types import ASGIApp
 
 
-from clean_python.base.domain.exceptions import Conflict
-from clean_python.base.domain.exceptions import DoesNotExist
-from clean_python.base.domain.exceptions import PermissionDenied
-from clean_python.base.domain.exceptions import Unauthorized
-from clean_python.base.infrastructure.gateway import Gateway
-from clean_python.oauth2.oauth2 import OAuth2AccessTokenVerifier
-from clean_python.oauth2.oauth2 import OAuth2Settings
+from clean_python import Conflict
+from clean_python import DoesNotExist
+from clean_python import Gateway
+from clean_python import PermissionDenied
+from clean_python import Unauthorized
+from clean_python.oauth2 import OAuth2AccessTokenVerifier
+from clean_python.oauth2 import OAuth2Settings
 
 
 from .context import RequestMiddleware
 from .context import RequestMiddleware
 from .error_responses import BadRequest
 from .error_responses import BadRequest

+ 1 - 0
clean_python/fluentbit/__init__.py

@@ -0,0 +1 @@
+from .fluentbit_gateway import *  # NOQA

+ 3 - 6
clean_python/fluentbit/fluentbit_gateway.py

@@ -1,15 +1,12 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
-from typing import Any
-from typing import Dict
-
 from asgiref.sync import sync_to_async
 from asgiref.sync import sync_to_async
 from fluent.sender import FluentSender
 from fluent.sender import FluentSender
 
 
-from clean_python.base.infrastructure.gateway import Gateway
+from clean_python import Gateway
+from clean_python import Json
 
 
-Json = Dict[str, Any]
+__all__ = ["FluentbitGateway"]
 
 
 
 
 class FluentbitGateway(Gateway):
 class FluentbitGateway(Gateway):

+ 1 - 0
clean_python/oauth2/__init__.py

@@ -0,0 +1 @@
+from .oauth2 import *  # NOQA

+ 7 - 11
clean_python/oauth2/oauth2.py

@@ -1,7 +1,5 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
-import logging
 from typing import Dict
 from typing import Dict
 from typing import List
 from typing import List
 
 
@@ -16,8 +14,6 @@ from clean_python.base.domain.exceptions import Unauthorized
 
 
 __all__ = ["OAuth2Settings", "OAuth2AccessTokenVerifier"]
 __all__ = ["OAuth2Settings", "OAuth2AccessTokenVerifier"]
 
 
-logger = logging.getLogger(__name__)
-
 
 
 class OAuth2Settings(BaseModel):
 class OAuth2Settings(BaseModel):
     client_id: str
     client_id: str
@@ -61,8 +57,8 @@ class OAuth2AccessTokenVerifier:
         # jwt.get_unverified_header will raise a JWTError if the structure is wrong.
         # jwt.get_unverified_header will raise a JWTError if the structure is wrong.
         try:
         try:
             key = self.get_key(token)  # JSON Web Key
             key = self.get_key(token)  # JSON Web Key
-        except PyJWTError as e:
-            logger.info("Token is invalid: %s", e)
+        except PyJWTError:
+            # logger.info("Token is invalid: %s", e)
             raise Unauthorized()
             raise Unauthorized()
         # Step 2: Validate the JWT signature and standard claims
         # Step 2: Validate the JWT signature and standard claims
         try:
         try:
@@ -76,8 +72,8 @@ class OAuth2AccessTokenVerifier:
                     "require": ["exp", "iss", "sub", "scope", "token_use"],
                     "require": ["exp", "iss", "sub", "scope", "token_use"],
                 },
                 },
             )
             )
-        except PyJWTError as e:
-            logger.info("Token is invalid: %s", e)
+        except PyJWTError:
+            # logger.info("Token is invalid: %s", e)
             raise Unauthorized()
             raise Unauthorized()
         # Step 3: Verify additional claims. At this point, we have passed
         # Step 3: Verify additional claims. At this point, we have passed
         # verification, so unverified claims may be used safely.
         # verification, so unverified claims may be used safely.
@@ -95,7 +91,7 @@ class OAuth2AccessTokenVerifier:
     def verify_token_use(self, claims):
     def verify_token_use(self, claims):
         """Check the token_use claim."""
         """Check the token_use claim."""
         if claims["token_use"] != "access":
         if claims["token_use"] != "access":
-            logger.info("Token has invalid token_use claim: %s", claims["token_use"])
+            # logger.info("Token has invalid token_use claim: %s", claims["token_use"])
             raise Unauthorized()
             raise Unauthorized()
 
 
     def verify_scope(self, claims):
     def verify_scope(self, claims):
@@ -106,11 +102,11 @@ class OAuth2AccessTokenVerifier:
            raster.lizard.net/*.readwrite
            raster.lizard.net/*.readwrite
         """
         """
         if f"{self.resource_server_id}{self.scope}" not in claims["scope"].split(" "):
         if f"{self.resource_server_id}{self.scope}" not in claims["scope"].split(" "):
-            logger.info("Token has invalid scope claim: %s", claims["scope"])
+            # logger.info("Token has invalid scope claim: %s", claims["scope"])
             raise Unauthorized()
             raise Unauthorized()
 
 
     def authorize(self, claims):
     def authorize(self, claims):
         """The subject (sub) claim should be in a hard-coded whitelist."""
         """The subject (sub) claim should be in a hard-coded whitelist."""
         if claims.get("sub") not in self.admin_users:
         if claims.get("sub") not in self.admin_users:
-            logger.info("User with sub %s is not authorized", claims.get("sub"))
+            # logger.info("User with sub %s is not authorized", claims.get("sub"))
             raise PermissionDenied()
             raise PermissionDenied()

+ 2 - 0
clean_python/sql/__init__.py

@@ -0,0 +1,2 @@
+from .sql_gateway import *  # NOQA
+from .sql_provider import *  # NOQA

+ 9 - 8
clean_python/sql/sql_gateway.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 from contextlib import asynccontextmanager
 from contextlib import asynccontextmanager
 from datetime import datetime
 from datetime import datetime
@@ -23,17 +22,19 @@ from sqlalchemy.sql import Executable
 from sqlalchemy.sql.expression import ColumnElement
 from sqlalchemy.sql.expression import ColumnElement
 from sqlalchemy.sql.expression import false
 from sqlalchemy.sql.expression import false
 
 
-from clean_python.base.domain.exceptions import AlreadyExists
-from clean_python.base.domain.exceptions import Conflict
-from clean_python.base.domain.exceptions import DoesNotExist
-from clean_python.base.domain.pagination import PageOptions
-from clean_python.base.infrastructure.gateway import Filter
-from clean_python.base.infrastructure.gateway import Gateway
-from clean_python.base.infrastructure.gateway import Json
+from clean_python import AlreadyExists
+from clean_python import Conflict
+from clean_python import DoesNotExist
+from clean_python import Filter
+from clean_python import Gateway
+from clean_python import Json
+from clean_python import PageOptions
 
 
 from .sql_provider import SQLDatabase
 from .sql_provider import SQLDatabase
 from .sql_provider import SQLProvider
 from .sql_provider import SQLProvider
 
 
+__all__ = ["SQLGateway"]
+
 
 
 def _is_unique_violation_error_id(e: IntegrityError, id: int):
 def _is_unique_violation_error_id(e: IntegrityError, id: int):
     # sqlalchemy wraps the asyncpg error
     # sqlalchemy wraps the asyncpg error

+ 3 - 59
clean_python/sql/sql_provider.py

@@ -3,19 +3,17 @@ from abc import abstractmethod
 from contextlib import asynccontextmanager
 from contextlib import asynccontextmanager
 from typing import AsyncIterator
 from typing import AsyncIterator
 from typing import List
 from typing import List
-from unittest import mock
 
 
-from sqlalchemy.dialects import postgresql
 from sqlalchemy.exc import DBAPIError
 from sqlalchemy.exc import DBAPIError
 from sqlalchemy.ext.asyncio import AsyncConnection
 from sqlalchemy.ext.asyncio import AsyncConnection
 from sqlalchemy.ext.asyncio import AsyncEngine
 from sqlalchemy.ext.asyncio import AsyncEngine
 from sqlalchemy.ext.asyncio import create_async_engine
 from sqlalchemy.ext.asyncio import create_async_engine
 from sqlalchemy.sql import Executable
 from sqlalchemy.sql import Executable
 
 
-from clean_python.base.domain.exceptions import Conflict
-from clean_python.base.infrastructure.gateway import Json
+from clean_python import Conflict
+from clean_python import Json
 
 
-__all__ = ["SQLProvider", "SQLDatabase", "FakeSQLDatabase", "assert_query_equal"]
+__all__ = ["SQLProvider", "SQLDatabase"]
 
 
 
 
 def is_serialization_error(e: DBAPIError) -> bool:
 def is_serialization_error(e: DBAPIError) -> bool:
@@ -56,13 +54,6 @@ class SQLDatabase(SQLProvider):
             async with connection.begin():
             async with connection.begin():
                 yield SQLTransaction(connection)
                 yield SQLTransaction(connection)
 
 
-    @asynccontextmanager
-    async def testing_transaction(self) -> AsyncIterator[SQLProvider]:
-        async with self.engine.connect() as connection:
-            async with connection.begin() as transaction:
-                yield SQLTestTransaction(connection)
-                await transaction.rollback()
-
 
 
 class SQLTransaction(SQLProvider):
 class SQLTransaction(SQLProvider):
     def __init__(self, connection: AsyncConnection):
     def __init__(self, connection: AsyncConnection):
@@ -80,54 +71,7 @@ class SQLTransaction(SQLProvider):
         # https://docs.python.org/3/library/collections.html#collections.somenamedtuple._asdict
         # https://docs.python.org/3/library/collections.html#collections.somenamedtuple._asdict
         return [x._asdict() for x in result.fetchall()]
         return [x._asdict() for x in result.fetchall()]
 
 
-
-class SQLTestTransaction(SQLTransaction):
     @asynccontextmanager
     @asynccontextmanager
     async def transaction(self) -> AsyncIterator[SQLProvider]:
     async def transaction(self) -> AsyncIterator[SQLProvider]:
         async with self.connection.begin_nested():
         async with self.connection.begin_nested():
             yield self
             yield self
-
-
-class FakeSQLDatabase(SQLProvider):
-    def __init__(self):
-        self.queries: List[List[Executable]] = []
-        self.result = mock.Mock(return_value=[])
-
-    async def execute(self, query: Executable) -> List[Json]:
-        self.queries.append([query])
-        return self.result()
-
-    @asynccontextmanager
-    async def transaction(self) -> AsyncIterator["SQLProvider"]:
-        x = FakeSQLTransaction(result=self.result)
-        self.queries.append(x.queries)
-        yield x
-
-
-class FakeSQLTransaction(SQLProvider):
-    def __init__(self, result: mock.Mock):
-        self.queries: List[Executable] = []
-        self.result = result
-
-    async def execute(self, query: Executable) -> List[Json]:
-        self.queries.append(query)
-        return self.result()
-
-
-def assert_query_equal(q: Executable, expected: str, literal_binds: bool = True):
-    """There are two ways of 'binding' parameters (for testing!):
-
-    literal_binds=True: use the built-in sqlalchemy way, which fails on some datatypes (Range)
-    literal_binds=False: do it yourself using %, there is no 'mogrify' so don't expect quotes.
-    """
-    assert isinstance(q, Executable)
-    compiled = q.compile(
-        compile_kwargs={"literal_binds": literal_binds},
-        dialect=postgresql.dialect(),
-    )
-    if not literal_binds:
-        actual = str(compiled) % compiled.params
-    else:
-        actual = str(compiled)
-    actual = actual.replace("\n", "").replace("  ", " ")
-    assert actual == expected

+ 2 - 0
clean_python/testing/__init__.py

@@ -0,0 +1,2 @@
+from .attr_dict import *  # NOQA
+from .profilers import *  # NOQA

+ 3 - 0
clean_python/testing/attr_dict.py

@@ -1,3 +1,6 @@
+# (c) Nelen & Schuurmans
+
+
 class AttrDict(dict):
 class AttrDict(dict):
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)

+ 2 - 0
clean_python/testing/profilers.py

@@ -1,3 +1,5 @@
+# (c) Nelen & Schuurmans
+
 from pathlib import Path
 from pathlib import Path
 
 
 import dramatiq
 import dramatiq

+ 0 - 34
clean_python/testing/testing.py

@@ -1,34 +0,0 @@
-from contextlib import contextmanager
-from typing import Type
-from unittest import mock
-
-from .manage import Manage
-
-
-@contextmanager
-def mock_manage(manage_cls: Type[Manage], skip=()):
-    """Mock all 'manage_' properties of a Manage class"""
-    manager = manage_cls()
-
-    mocks = {}
-    for attr_name in dir(manage_cls):
-        if not attr_name.startswith("manage_") or attr_name in skip:
-            continue
-        other_manager = getattr(manager, attr_name)
-        if not isinstance(other_manager, Manage):
-            continue
-        mocks[attr_name] = mock.MagicMock(other_manager)
-
-    patchers = [
-        mock.patch.object(
-            manage_cls,
-            name,
-            new_callable=mock.PropertyMock(return_value=x),
-        )
-        for name, x in mocks.items()
-    ]
-    for p in patchers:
-        p.start()
-    yield
-    for p in patchers:
-        p.stop()

+ 3 - 3
tests/test_async_actor.py

@@ -4,9 +4,9 @@ from unittest import mock
 
 
 import pytest
 import pytest
 
 
-from clean_python.dramatiq.async_actor import async_actor
-from clean_python.dramatiq.async_actor import AsyncActor
-from clean_python.dramatiq.async_actor import AsyncMiddleware
+from clean_python.dramatiq import async_actor
+from clean_python.dramatiq import AsyncActor
+from clean_python.dramatiq import AsyncMiddleware
 from clean_python.dramatiq.async_actor import EventLoopThread
 from clean_python.dramatiq.async_actor import EventLoopThread
 
 
 
 

+ 1 - 1
tests/test_celery_rmq_broker.py

@@ -2,7 +2,7 @@ from unittest import mock
 
 
 import pytest
 import pytest
 
 
-from clean_python.celery.celery_rmq_broker import CeleryRmqBroker
+from clean_python.celery import CeleryRmqBroker
 
 
 
 
 @pytest.fixture
 @pytest.fixture

+ 2 - 2
tests/test_dramatiq_task_logger.py

@@ -5,8 +5,8 @@ import pytest
 from dramatiq.errors import Retry
 from dramatiq.errors import Retry
 from dramatiq.message import Message
 from dramatiq.message import Message
 
 
-from clean_python.base.infrastructure.gateway import InMemoryGateway
-from clean_python.dramatiq.dramatiq_task_logger import DramatiqTaskLogger
+from clean_python import InMemoryGateway
+from clean_python.dramatiq import DramatiqTaskLogger
 
 
 
 
 @pytest.fixture
 @pytest.fixture

+ 3 - 3
tests/test_exceptions.py

@@ -1,8 +1,8 @@
 from pydantic import ValidationError
 from pydantic import ValidationError
 
 
-from clean_python.base.domain.exceptions import BadRequest
-from clean_python.base.domain.exceptions import DoesNotExist
-from clean_python.base.domain.value_object import ValueObject
+from clean_python import BadRequest
+from clean_python import DoesNotExist
+from clean_python import ValueObject
 
 
 
 
 def test_bad_request_short_str():
 def test_bad_request_short_str():

+ 1 - 1
tests/test_fastapi_access_logger.py

@@ -6,8 +6,8 @@ from starlette.requests import Request
 from starlette.responses import JSONResponse
 from starlette.responses import JSONResponse
 from starlette.responses import StreamingResponse
 from starlette.responses import StreamingResponse
 
 
-from clean_python import FastAPIAccessLogger
 from clean_python import InMemoryGateway
 from clean_python import InMemoryGateway
+from clean_python.fastapi import FastAPIAccessLogger
 
 
 
 
 @pytest.fixture
 @pytest.fixture

+ 0 - 1
tests/test_manage.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
 from unittest import mock
 from unittest import mock

+ 1 - 2
tests/test_oauth2.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # (c) Nelen & Schuurmans
 # (c) Nelen & Schuurmans
 
 
 import time
 import time
@@ -7,9 +6,9 @@ from unittest import mock
 import jwt
 import jwt
 import pytest
 import pytest
 
 
-from clean_python import OAuth2AccessTokenVerifier
 from clean_python import PermissionDenied
 from clean_python import PermissionDenied
 from clean_python import Unauthorized
 from clean_python import Unauthorized
+from clean_python.oauth2 import OAuth2AccessTokenVerifier
 
 
 
 
 @pytest.fixture
 @pytest.fixture

+ 3 - 3
tests/test_request_query.py

@@ -3,9 +3,9 @@ from typing import Optional
 import pytest
 import pytest
 from pydantic import ValidationError
 from pydantic import ValidationError
 
 
-from clean_python.base.domain.pagination import PageOptions
-from clean_python.base.infrastructure.gateway import Filter
-from clean_python.fastapi.request_query import RequestQuery
+from clean_python import Filter
+from clean_python import PageOptions
+from clean_python.fastapi import RequestQuery
 
 
 
 
 class SomeQuery(RequestQuery):
 class SomeQuery(RequestQuery):

+ 5 - 5
tests/test_resource.py

@@ -1,11 +1,11 @@
 import pytest
 import pytest
 from fastapi.routing import APIRouter
 from fastapi.routing import APIRouter
 
 
-from clean_python.fastapi.resource import APIVersion
-from clean_python.fastapi.resource import get
-from clean_python.fastapi.resource import Resource
-from clean_python.fastapi.resource import Stability
-from clean_python.fastapi.resource import v
+from clean_python.fastapi import APIVersion
+from clean_python.fastapi import get
+from clean_python.fastapi import Resource
+from clean_python.fastapi import Stability
+from clean_python.fastapi import v
 
 
 
 
 def test_subclass():
 def test_subclass():

+ 3 - 3
tests/test_service.py

@@ -1,8 +1,8 @@
 import pytest
 import pytest
 
 
-from clean_python import Resource
-from clean_python import Service
-from clean_python import v
+from clean_python.fastapi import Resource
+from clean_python.fastapi import Service
+from clean_python.fastapi import v
 
 
 
 
 class V1Foo(Resource, version=v(1), name="foo"):
 class V1Foo(Resource, version=v(1), name="foo"):

+ 3 - 3
tests/test_sql_gateway.py

@@ -11,13 +11,13 @@ from sqlalchemy import MetaData
 from sqlalchemy import Table
 from sqlalchemy import Table
 from sqlalchemy import Text
 from sqlalchemy import Text
 
 
-from clean_python import assert_query_equal
 from clean_python import Conflict
 from clean_python import Conflict
 from clean_python import DoesNotExist
 from clean_python import DoesNotExist
-from clean_python import FakeSQLDatabase
 from clean_python import Filter
 from clean_python import Filter
 from clean_python import PageOptions
 from clean_python import PageOptions
-from clean_python import SQLGateway
+from clean_python.sql import SQLGateway
+from clean_python.sql.testing import assert_query_equal
+from clean_python.sql.testing import FakeSQLDatabase
 
 
 writer = Table(
 writer = Table(
     "writer",
     "writer",