Skip to content

Commit

Permalink
Feature location impl (#42)
Browse files Browse the repository at this point in the history
- Refactors codebase in order to implement requirements for #26 
- Restructures some of the dependencies to maintain project structure
around domain driven design independent of the framework.
  • Loading branch information
codecakes authored Aug 25, 2024
1 parent 0afa40d commit 178bef5
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 122 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ pip-install:

test:
pytest -s xcov19/tests/

todos:
@grep -rn "TODO:" xcov19/ --exclude-dir=node_modules --include="*.py"
3 changes: 2 additions & 1 deletion xcov19/app/controllers/diagnose.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from blacksheep.server.controllers import APIController
from xcov19.app.controllers import post

from xcov19.app.dto import DiagnosisQueryJSON, FromOriginMatchHeader
from xcov19.dto import DiagnosisQueryJSON
from xcov19.app.settings import FromOriginMatchHeader


class DiagnosisController(APIController):
Expand Down
5 changes: 3 additions & 2 deletions xcov19/app/controllers/geolocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
from blacksheep.server.controllers import APIController

from xcov19.app.controllers import post
from xcov19.app.dto import FromOriginMatchHeader, LocationQueryJSON
from xcov19.dto import LocationQueryJSON
from xcov19.app.settings import FromOriginMatchHeader

from xcov19.app.services import LocationQueryServiceInterface
from xcov19.services.geolocation import LocationQueryServiceInterface


class GeolocationController(APIController):
Expand Down
2 changes: 1 addition & 1 deletion xcov19/app/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from blacksheep import Application, Request, Response, bad_request

from xcov19.app.dto import FromOriginMatchHeader
from xcov19.app.settings import FromOriginMatchHeader


def configure_middleware(app: Application, *middlewares):
Expand Down
68 changes: 7 additions & 61 deletions xcov19/app/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
https://github.com/Neoteroi/rodi/tree/main/examples
"""

import abc
from collections.abc import Callable
from typing import Protocol, Tuple, List
from __future__ import annotations

from typing import Tuple

from rodi import Container

from xcov19.app.dto import Address, LocationQueryJSON, FacilitiesResult
from xcov19.app.settings import Settings
from xcov19.utils.mixins import InterfaceProtocolCheckMixin
from xcov19.services.geolocation import (
LocationQueryServiceInterface,
GeolocationQueryService,
)


def configure_services(settings: Settings) -> Tuple[Container, Settings]:
Expand All @@ -26,59 +28,3 @@ def configure_services(settings: Settings) -> Tuple[Container, Settings]:
container.add_singleton(LocationQueryServiceInterface, GeolocationQueryService)

return container, settings


class LocationQueryServiceInterface[T: LocationQueryJSON](Protocol):
"""Location aware service for listing faciltiies.
1. Searches and fetches existing processed results by query_id for a cust_id
2. Resolves coordinates from a given geolocation.
3. Fetches all facilities from a given set of records for a
given radius from geolocation.
Radius is default for now.
# TODO: Filter to be added
"""

@classmethod
@abc.abstractmethod
async def resolve_coordinates(
cls, reverse_geo_lookup_svc: Callable[[T], dict], query: T
) -> Address:
raise NotImplementedError

@classmethod
@abc.abstractmethod
async def fetch_facilities(
cls,
reverse_geo_lookup_svc: Callable[[T], dict],
patient_query_lookup_svc: Callable[[Address, T], List[FacilitiesResult]],
query: T,
) -> List[FacilitiesResult] | None:
raise NotImplementedError


# TODO: make hard-coded response functional
class GeolocationQueryService(
LocationQueryServiceInterface, InterfaceProtocolCheckMixin
):
@classmethod
async def resolve_coordinates(
cls,
reverse_geo_lookup_svc: Callable[[LocationQueryJSON], dict],
query: LocationQueryJSON,
) -> Address:
"""Resolves to address by geo reverse lookup."""
return Address(**reverse_geo_lookup_svc(query))

@classmethod
async def fetch_facilities(
cls,
reverse_geo_lookup_svc: Callable[[LocationQueryJSON], dict],
patient_query_lookup_svc: Callable[
[Address, LocationQueryJSON], List[FacilitiesResult]
],
query: LocationQueryJSON,
) -> List[FacilitiesResult] | None:
"""Fetches facilities for a query location for a query id for a customer."""
patient_address = await cls.resolve_coordinates(reverse_geo_lookup_svc, query)
return patient_query_lookup_svc(patient_address, query) or None
6 changes: 6 additions & 0 deletions xcov19/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
https://docs.pydantic.dev/latest/usage/settings/
"""

from blacksheep import FromHeader
from pydantic import BaseModel
from pydantic_settings import BaseSettings, SettingsConfigDict

Expand Down Expand Up @@ -38,3 +39,8 @@ class Settings(BaseSettings):

def load_settings() -> Settings:
return Settings()


class FromOriginMatchHeader(FromHeader[str]):
name = "X-Origin-Match-Header"
secret = "secret"
24 changes: 0 additions & 24 deletions xcov19/domain/repository.py

This file was deleted.

32 changes: 32 additions & 0 deletions xcov19/domain/repository_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import Protocol, TypeVar, List
import abc

from xcov19.domain.models.patient import Patient
from xcov19.domain.models.provider import Provider

PatientT = TypeVar("PatientT", bound=Patient)
ProviderT = TypeVar("ProviderT", bound=Patient)


class IPatientStore[PatientT: Patient](Protocol):
@classmethod
@abc.abstractmethod
def enqueue_diagnosis_query(cls, patient: PatientT):
raise NotImplementedError

@classmethod
@abc.abstractmethod
def enqueue_geolocation_query(cls, patient: PatientT):
raise NotImplementedError


class IProviderRepository[ProviderT: Provider](Protocol):
@abc.abstractmethod
def fetch_by_providers(self, **address: dict[str, str]) -> List[ProviderT]:
raise NotImplementedError

@abc.abstractmethod
def fetch_by_query(
self, query_id: str, filtered_providers: List[ProviderT]
) -> List[ProviderT]:
raise NotImplementedError
6 changes: 0 additions & 6 deletions xcov19/app/dto.py → xcov19/dto.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
from blacksheep import FromHeader
from pydantic import BaseModel, Field

from typing import Annotated, List


class FromOriginMatchHeader(FromHeader[str]):
name = "X-Origin-Match-Header"
secret = "secret"


class GeoLocation(BaseModel):
lat: float
lng: float
Expand Down
Empty file added xcov19/services/__init__.py
Empty file.
68 changes: 68 additions & 0 deletions xcov19/services/geolocation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from __future__ import annotations

import abc
from typing import TypeVar, Protocol, Callable, List

from xcov19.dto import LocationQueryJSON, Address, FacilitiesResult
from xcov19.utils.mixins import InterfaceProtocolCheckMixin

T = TypeVar("T", bound=LocationQueryJSON)


# Application services


class LocationQueryServiceInterface[T: LocationQueryJSON](Protocol):
"""Location aware service for listing faciltiies.
1. Searches and fetches existing processed results by query_id for a cust_id
2. Resolves coordinates from a given geolocation.
3. Fetches all facilities from a given set of records for a
given radius from geolocation.
Radius is default for now.
# TODO: Filter to be added
"""

@classmethod
@abc.abstractmethod
async def resolve_coordinates(
cls, reverse_geo_lookup_svc: Callable[[T], dict], query: T
) -> Address:
raise NotImplementedError

@classmethod
@abc.abstractmethod
async def fetch_facilities(
cls,
reverse_geo_lookup_svc: Callable[[T], dict],
query: T,
patient_query_lookup_svc: Callable[[Address, T], List[FacilitiesResult]],
) -> List[FacilitiesResult] | None:
raise NotImplementedError


class GeolocationQueryService(
LocationQueryServiceInterface[LocationQueryJSON], InterfaceProtocolCheckMixin
):
@classmethod
async def resolve_coordinates(
cls,
reverse_geo_lookup_svc: Callable[[LocationQueryJSON], dict],
query: LocationQueryJSON,
) -> Address:
"""Resolves to address by geo reverse lookup."""
return Address(**reverse_geo_lookup_svc(query))

@classmethod
async def fetch_facilities(
cls,
reverse_geo_lookup_svc: Callable[[LocationQueryJSON], dict],
query: LocationQueryJSON,
patient_query_lookup_svc: Callable[
[Address, LocationQueryJSON],
List[FacilitiesResult],
],
) -> List[FacilitiesResult] | None:
"""Fetches facilities for a query location for a query id for a customer."""
patient_address = await cls.resolve_coordinates(reverse_geo_lookup_svc, query)
return patient_query_lookup_svc(patient_address, query) or None
8 changes: 4 additions & 4 deletions xcov19/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

import pytest

from xcov19.app.dto import (
from xcov19.dto import (
AnonymousId,
GeoLocation,
LocationQueryJSON,
QueryId,
Address,
FacilitiesResult,
)
from xcov19.app.services import LocationQueryServiceInterface
from xcov19.services.geolocation import LocationQueryServiceInterface
from xcov19.utils.mixins import InterfaceProtocolCheckMixin

# Same as using @pytest.mark.anyio
Expand All @@ -34,7 +34,7 @@ def dummy_coordinates():


@pytest.fixture(scope="class")
def dummy_geolocation(dummy_coordinates):
def dummy_geolocation_query_json(dummy_coordinates):
return LocationQueryJSON(
location=dummy_coordinates,
cust_id=AnonymousId(cust_id="test_cust_id"),
Expand Down Expand Up @@ -62,10 +62,10 @@ async def resolve_coordinates(
async def fetch_facilities(
cls,
reverse_geo_lookup_svc: Callable[[LocationQueryJSON], dict],
query: LocationQueryJSON,
patient_query_lookup_svc: Callable[
[Address, LocationQueryJSON], List[FacilitiesResult]
],
query: LocationQueryJSON,
) -> List[FacilitiesResult] | None:
return [
FacilitiesResult(
Expand Down
Loading

0 comments on commit 178bef5

Please sign in to comment.