-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fafe05c
commit 0f5ca81
Showing
25 changed files
with
615 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
4 changes: 4 additions & 0 deletions
4
src/country_workspace/contrib/dedup_engine/adapters/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from .deduplication_set import DeduplicationSetAdapter # noqa | ||
from .duplicate import DuplicateAdapter # noqa | ||
from .ignored import IgnoredAdapter # noqa | ||
from .image import ImageAdapter # noqa |
72 changes: 72 additions & 0 deletions
72
src/country_workspace/contrib/dedup_engine/adapters/base.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
from enum import Enum | ||
from typing import Any, Generic, Type | ||
|
||
from requests import Session | ||
|
||
from country_workspace.contrib.dedup_engine.adapters.mixins import URLMixin, ValidationMixin | ||
from country_workspace.contrib.dedup_engine.endpoints import Endpoint | ||
from country_workspace.contrib.dedup_engine.types import TCreate, TModel | ||
|
||
|
||
class HTTPMethod(Enum): | ||
GET = "GET" | ||
POST = "POST" | ||
DELETE = "DELETE" | ||
|
||
|
||
class BaseAdapter(Generic[TModel, TCreate], URLMixin, ValidationMixin): | ||
def __init__( | ||
self, session: Session, endpoints: Endpoint, model_class: Type[TModel], create_class: Type[TCreate] = None | ||
) -> None: | ||
self.session = session | ||
self.endpoints = endpoints | ||
self.model_class = model_class | ||
self.create_class = create_class or model_class | ||
|
||
def list(self, url_path: str, **kwargs) -> list[TModel]: | ||
url = self.prepare_url(url_path, **kwargs) | ||
response = self._request(HTTPMethod.GET.value, url) | ||
return [self.model_class(**item) for item in response.json()] | ||
|
||
def retrieve(self, url_path: str, **kwargs) -> TModel: | ||
url = self.prepare_url(url_path, **kwargs) | ||
response = self._request(HTTPMethod.GET.value, url) | ||
return self.model_class(**response.json()) | ||
|
||
def create(self, url_path: str, data: TCreate, **kwargs) -> TModel: | ||
url = self.prepare_url(url_path, **kwargs) | ||
response = self._request( | ||
HTTPMethod.POST.value, url, json=self.validate_data(data, self.create_class).model_dump(by_alias=True) | ||
) | ||
print(f"{response.json()=}") | ||
return self.model_class(**response.json()) | ||
|
||
def destroy(self, url_path: str, **kwargs) -> None: | ||
url = self.prepare_url(url_path, **kwargs) | ||
response = self._request(HTTPMethod.DELETE.value, url) | ||
if response.status_code != 204: | ||
response.raise_for_status() | ||
|
||
def update(self, url_path: str, data: TModel, **kwargs) -> TModel: | ||
raise NotImplementedError("Update method is not implemented") | ||
|
||
def _request(self, method: str, url: str, **kwargs) -> Any: | ||
print(f"{method} {url=}") | ||
response = self.session.request(method, url, **kwargs) | ||
response.raise_for_status() | ||
return response | ||
|
||
def _action( | ||
self, | ||
method: HTTPMethod, | ||
url_path: str, | ||
*, | ||
path_params: dict[str, Any] = None, | ||
data: Any = None, | ||
) -> Any: | ||
path_params = path_params or {} | ||
url = self.prepare_url(url_path, **path_params) | ||
response = self._request(method.value, url, json=data) | ||
if response.content: | ||
return response.json() | ||
return None |
39 changes: 39 additions & 0 deletions
39
src/country_workspace/contrib/dedup_engine/adapters/deduplication_set.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from typing import override | ||
from uuid import UUID | ||
|
||
from country_workspace.contrib.dedup_engine.adapters.base import BaseAdapter, HTTPMethod | ||
from country_workspace.contrib.dedup_engine.models import DeduplicationSet, DeduplicationSetCreate | ||
|
||
|
||
class DeduplicationSetAdapter(BaseAdapter[DeduplicationSet, DeduplicationSetCreate]): | ||
@override | ||
def list(self) -> list[DeduplicationSet]: | ||
return super().list(url_path=self.endpoints.deduplication_set) | ||
|
||
@override | ||
def retrieve(self, data: DeduplicationSet | UUID) -> DeduplicationSet: | ||
return super().retrieve( | ||
url_path=self.endpoints.deduplication_set_detail, | ||
deduplication_set_id=self.get_entity_id(data), | ||
) | ||
|
||
@override | ||
def create(self, data: DeduplicationSetCreate) -> DeduplicationSet: | ||
return super().create( | ||
url_path=self.endpoints.deduplication_set, | ||
data=data, | ||
) | ||
|
||
@override | ||
def destroy(self, data: DeduplicationSet | UUID) -> None: | ||
super().destroy( | ||
url_path=self.endpoints.deduplication_set_detail, | ||
deduplication_set_id=self.get_entity_id(data), | ||
) | ||
|
||
def process(self, data: DeduplicationSet | UUID) -> None: | ||
self._action( | ||
HTTPMethod.POST, | ||
self.endpoints.process, | ||
path_params={"deduplication_set_id": self.get_entity_id(data)}, | ||
) |
26 changes: 26 additions & 0 deletions
26
src/country_workspace/contrib/dedup_engine/adapters/duplicate.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from typing import override | ||
from uuid import UUID | ||
|
||
from country_workspace.contrib.dedup_engine.adapters.base import BaseAdapter | ||
from country_workspace.contrib.dedup_engine.models import DeduplicationSet, Duplicate | ||
|
||
|
||
class DuplicateAdapter(BaseAdapter[Duplicate, None]): | ||
@override | ||
def list(self, deduplication_set: DeduplicationSet | UUID) -> list[Duplicate]: | ||
return super().list( | ||
url_path=self.endpoints.duplicate, | ||
deduplication_set_id=self.get_entity_id(deduplication_set), | ||
) | ||
|
||
@override | ||
def retrieve(self, *args, **kwargs) -> None: | ||
raise NotImplementedError("Retrieval of Duplicate objects is not supported.") | ||
|
||
@override | ||
def create(self, *args, **kwargs) -> None: | ||
raise NotImplementedError("Creation of Duplicate objects is not supported.") | ||
|
||
@override | ||
def destroy(self, *args, **kwargs) -> None: | ||
raise NotImplementedError("Deletion of Duplicate objects is not supported.") |
45 changes: 45 additions & 0 deletions
45
src/country_workspace/contrib/dedup_engine/adapters/ignored.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from typing import Type, override | ||
from uuid import UUID | ||
|
||
from requests import Session | ||
|
||
from country_workspace.contrib.dedup_engine.adapters.base import BaseAdapter, Endpoint | ||
from country_workspace.contrib.dedup_engine.models import DeduplicationSet, Ignored, IgnoredCreate | ||
|
||
|
||
class IgnoredAdapter(BaseAdapter[Ignored, IgnoredCreate]): | ||
def __init__( | ||
self, | ||
session: Session, | ||
endpoints: Endpoint, | ||
model_class: Type[Ignored], | ||
create_class: Type[IgnoredCreate], | ||
resource_type: str, | ||
) -> None: | ||
super().__init__(session, endpoints, model_class, create_class) | ||
self.resource_type = resource_type | ||
|
||
@override | ||
def list(self, deduplication_set: DeduplicationSet | UUID) -> list[Ignored]: | ||
return super().list( | ||
url_path=self.endpoints.ignored, | ||
deduplication_set_id=self.get_entity_id(deduplication_set), | ||
resource_type=self.resource_type, | ||
) | ||
|
||
@override | ||
def create(self, deduplication_set: DeduplicationSet | UUID, data: IgnoredCreate) -> Ignored: | ||
return super().create( | ||
url_path=self.endpoints.ignored, | ||
deduplication_set_id=self.get_entity_id(deduplication_set), | ||
resource_type=self.resource_type, | ||
data=data, | ||
) | ||
|
||
@override | ||
def retrieve(self, *args, **kwargs) -> None: | ||
raise NotImplementedError("Retrieval of Ignored objects is not supported.") | ||
|
||
@override | ||
def destroy(self, *args, **kwargs) -> None: | ||
raise NotImplementedError("Deletion of Ignored objects is not supported.") |
53 changes: 53 additions & 0 deletions
53
src/country_workspace/contrib/dedup_engine/adapters/image.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from __future__ import annotations | ||
|
||
from typing import override | ||
from uuid import UUID | ||
|
||
from country_workspace.contrib.dedup_engine.adapters.base import BaseAdapter, HTTPMethod | ||
from country_workspace.contrib.dedup_engine.models import DeduplicationSet, Image, ImageCreate | ||
|
||
|
||
class ImageAdapter(BaseAdapter[Image, ImageCreate]): | ||
|
||
@override | ||
def list(self, deduplication_set: DeduplicationSet | UUID) -> list[Image]: | ||
return super().list( | ||
url_path=self.endpoints.image, | ||
deduplication_set_id=self.get_entity_id(deduplication_set), | ||
) | ||
|
||
@override | ||
def create(self, deduplication_set: DeduplicationSet | UUID, data: ImageCreate) -> Image: | ||
return super().create( | ||
url_path=self.endpoints.image, | ||
deduplication_set_id=self.get_entity_id(deduplication_set), | ||
data=data, | ||
) | ||
|
||
@override | ||
def destroy(self, deduplication_set: DeduplicationSet | UUID, image: Image | UUID) -> None: | ||
return super().destroy( | ||
url_path=self.endpoints.image_detail, | ||
deduplication_set_id=self.get_entity_id(deduplication_set), | ||
image_id=self.get_entity_id(image), | ||
) | ||
|
||
def create_bulk(self, deduplication_set: DeduplicationSet | UUID, data: list[ImageCreate]) -> list[Image]: | ||
validated_data = [self.validate_data(item, ImageCreate).model_dump(by_alias=True) for item in data] | ||
response_data = self._action( | ||
HTTPMethod.POST, | ||
self.endpoints.image_bulk, | ||
path_params={"deduplication_set_id": self.get_entity_id(deduplication_set)}, | ||
data=validated_data, | ||
) | ||
return [Image(**item) for item in response_data] | ||
|
||
def destroy_bulk(self, deduplication_set: DeduplicationSet | UUID) -> None: | ||
self._action( | ||
HTTPMethod.DELETE, | ||
self.endpoints.image_bulk_clear, | ||
path_params={"deduplication_set_id": self.get_entity_id(deduplication_set)}, | ||
) | ||
|
||
def retrieve(self, *args, **kwargs) -> None: | ||
raise NotImplementedError("Retrieval of Image objects is not supported.") |
44 changes: 44 additions & 0 deletions
44
src/country_workspace/contrib/dedup_engine/adapters/mixins.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from typing import Any, Type | ||
from urllib.parse import urljoin | ||
from uuid import UUID | ||
|
||
from country_workspace.contrib.dedup_engine.models import DeduplicationSet, Image | ||
from country_workspace.contrib.dedup_engine.types import TModel | ||
|
||
|
||
class URLMixin: | ||
def prepare_url(self, path: str, **kwargs) -> str: | ||
try: | ||
formatted_path = path.format(**kwargs) | ||
except KeyError as e: | ||
raise ValueError(f"Missing placeholder '{e.args[0]}' in kwargs for path: '{path}'") | ||
return urljoin(self.endpoints.base, formatted_path) | ||
|
||
|
||
class ValidationMixin: | ||
@staticmethod | ||
def get_entity_id(entity: DeduplicationSet | Image | UUID, id_field: str = "id") -> UUID: | ||
match entity: | ||
case UUID(): | ||
return entity | ||
case _ if isinstance(entity, (DeduplicationSet, Image)): | ||
try: | ||
return getattr(entity, id_field) | ||
except AttributeError: | ||
raise AttributeError(f"'{type(entity).__name__}' does not have '{id_field}' attribute") | ||
case _: | ||
raise TypeError( | ||
f"Invalid type for entity: {type(entity).__name__}. Expected UUID, DeduplicationSet, or Image." | ||
) | ||
|
||
@staticmethod | ||
def validate_data(data: Any, model_class: Type[TModel]) -> TModel: | ||
match data: | ||
case model_class(): | ||
return data | ||
case dict(): | ||
return model_class.model_validate(data) | ||
case _: | ||
raise TypeError( | ||
f"Expected data to be of type {model_class.__name__} or dict, but got {type(data).__name__}" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class Config(AppConfig): | ||
name = __name__.rpartition(".")[0] | ||
verbose_name = "Country Workspace | Dedup Engine Client" |
Oops, something went wrong.