Skip to content

Commit c1d24e2

Browse files
committed
refactor catalog api endpoints
1 parent 0b8514d commit c1d24e2

File tree

13 files changed

+134
-38
lines changed

13 files changed

+134
-38
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ psycopg2-binary = "^2.9.2"
2424
freezegun = "^1.1.0"
2525
SQLAlchemy-Utils = "^0.38.3"
2626
pre-commit = "^2.20.0"
27+
click = "8.0.4"
2728

2829
[tool.poetry.dev-dependencies]
2930
poethepoet = "^0.10.0"

src/api/main.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
app.include_router(iam.router)
2424
app.container = container
2525

26-
2726
logger.info("using db engine %s" % str(container.engine()))
2827

2928

src/api/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@ def fake_user(cls):
1414

1515
class ListingWriteModel(BaseModel):
1616
title: str
17+
description: str
18+
ask_price_amount: float
19+
ask_price_currency: str = "USD"
1720

1821

1922
class ListingReadModel(BaseModel):
2023
id: UUID
2124
title: str = ""
25+
description: str
26+
ask_price_amount: float
27+
ask_price_currency: str
2228

2329

2430
class ListingIndexModel(BaseModel):

src/api/routers/catalog.py

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
from api.models import ListingIndexModel, ListingReadModel, ListingWriteModel
44
from api.shared import dependency
55
from config.container import Container, inject
6+
from modules.catalog import CatalogModule
67
from modules.catalog.application.command.create_listing_draft import (
78
CreateListingDraftCommand,
89
)
910
from modules.catalog.application.query.get_all_listings import GetAllListings
1011
from modules.catalog.application.query.get_listing_details import GetListingDetails
11-
from modules.catalog.module import CatalogModule
12+
from seedwork.domain.value_objects import Money
1213
from seedwork.infrastructure.request_context import request_context
1314

1415
router = APIRouter()
@@ -23,21 +24,24 @@ async def get_all_listings(
2324
Shows all published listings in the catalog
2425
"""
2526
query = GetAllListings()
26-
query_result = module.execute_query(query)
27-
return dict(data=query_result.result)
27+
with module.unit_of_work():
28+
query_result = module.execute_query(query)
29+
return dict(data=query_result.result)
2830

2931

3032
@router.get("/catalog/{listing_id}", tags=["catalog"], response_model=ListingReadModel)
3133
@inject
3234
async def get_listing_details(
33-
listing_id, module: CatalogModule = dependency(Container.catalog_module)
35+
listing_id,
36+
module: CatalogModule = dependency(Container.catalog_module),
3437
):
3538
"""
3639
Shows listing details
3740
"""
3841
query = GetListingDetails(listing_id=listing_id)
39-
query_result = module.execute_query(query)
40-
return query_result.result
42+
with module.unit_of_work():
43+
query_result = module.execute_query(query)
44+
return query_result.result
4145

4246

4347
@router.post(
@@ -51,19 +55,21 @@ async def create_listing(
5155
"""
5256
Creates a new listing.
5357
"""
54-
command_result = module.execute_command(
55-
CreateListingDraftCommand(
56-
title=request_body.title,
57-
description="",
58-
price=1,
59-
seller_id=request_context.current_user.id,
60-
)
58+
command = CreateListingDraftCommand(
59+
title=request_body.title,
60+
description=request_body.description,
61+
ask_price=Money(request_body.ask_price_amount, request_body.ask_price_currency),
62+
seller_id=request_context.current_user.id,
6163
)
64+
with module.unit_of_work():
65+
command_result = module.execute_command(command)
66+
67+
query = GetListingDetails(listing_id=command_result.result)
68+
query_result = module.execute_query(query)
69+
return query_result.result
6270

63-
query = GetListingDetails(listing_id=command_result.result)
64-
query_result = module.execute_query(query)
65-
return query_result.result
6671

67-
# TODO: for now we return just the id, but in the future we should return
68-
# a representation of a newly created listing resource
69-
return {"id": result.id}
72+
#
73+
# # TODO: for now we return just the id, but in the future we should return
74+
# # a representation of a newly created listing resource
75+
# return {"id": result.id}

src/config/container.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from sqlalchemy import create_engine
44

55
from modules.catalog import CatalogModule
6+
from modules.iam import IamModule
67

78

89
def _default(val):
@@ -60,3 +61,8 @@ class Container(containers.DeclarativeContainer):
6061
CatalogModule,
6162
engine=engine,
6263
)
64+
65+
iam_module = providers.Factory(
66+
IamModule,
67+
engine=engine,
68+
)

src/modules/catalog/__init__.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from contextlib import contextmanager
33
from contextvars import ContextVar
44
from dataclasses import dataclass
5+
from typing import Any
56

67
from sqlalchemy.orm import Session
78

@@ -44,6 +45,7 @@ def get_arg(name, kwargs1, kwargs2):
4445

4546
@dataclass
4647
class UnitOfWork:
48+
module: Any # FIXME: type
4749
db_session: Session
4850
correlation_id: uuid.UUID
4951
listing_repository: ListingRepository
@@ -64,6 +66,7 @@ def unit_of_work(self, **kwargs):
6466
db_session = None
6567
with Session(engine) as db_session:
6668
uow = UnitOfWork(
69+
module=self,
6770
correlation_id=correlation_id,
6871
db_session=db_session,
6972
listing_repository=PostgresJsonListingRepository(db_session=db_session),
@@ -96,7 +99,19 @@ def execute_command(self, command):
9699
if isinstance(attr, param_type):
97100
kwargs[param_name] = attr
98101

99-
return handler(command=command, **kwargs)
102+
return handler(command, **kwargs)
103+
104+
def execute_query(self, query):
105+
query_class = type(query)
106+
handler = registry.get_query_handler_for(query_class)
107+
kwargs = registry.get_query_handler_parameters_for(query_class)
108+
109+
for param_name, param_type in kwargs.items():
110+
for attr in self.uow.__dict__.values():
111+
if isinstance(attr, param_type):
112+
kwargs[param_name] = attr
113+
114+
return handler(query, **kwargs)
100115

101116
@property
102117
def uow(self) -> UnitOfWork:
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1-
from modules.catalog.domain.repositories import ListingRepository
1+
from sqlalchemy.orm import Session
2+
3+
from modules.catalog.application.query.model_mappers import map_listing_model_to_dao
4+
from modules.catalog.infrastructure.listing_repository import ListingModel
25
from seedwork.application.decorators import query_handler
36
from seedwork.application.queries import Query
47
from seedwork.application.query_handlers import QueryResult
58

69

710
class GetAllListings(Query):
11+
"""This query does not need any parameters"""
12+
813
...
914

1015

1116
@query_handler
1217
def get_all_listings(
13-
query: GetAllListings, listing_repository: ListingRepository
18+
query: GetAllListings,
19+
session: Session,
1420
) -> QueryResult:
15-
queryset = listing_repository.session.query(listing_repository.model)
16-
result = [dict(id=row.id, **row.data) for row in queryset.all()]
21+
queryset = session.query(ListingModel)
22+
result = [map_listing_model_to_dao(row) for row in queryset.all()]
1723
# TODO: add error handling
1824
return QueryResult.ok(result)
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
from modules.catalog.domain.repositories import ListingRepository
1+
from sqlalchemy.orm import Session
2+
3+
from modules.catalog.application.query.model_mappers import map_listing_model_to_dao
4+
from modules.catalog.infrastructure.listing_repository import ListingModel
25
from seedwork.application.decorators import query_handler
36
from seedwork.application.queries import Query
47
from seedwork.application.query_handlers import QueryResult
@@ -10,11 +13,7 @@ class GetListingDetails(Query):
1013

1114

1215
@query_handler
13-
def get_listing_details(
14-
query: GetListingDetails, listing_repository: ListingRepository
15-
) -> QueryResult:
16-
queryset = listing_repository.session.query(listing_repository.model).filter_by(
17-
id=query.listing_id
18-
)
19-
result = [dict(id=row.id, **row.data) for row in queryset.all()][0]
16+
def get_listing_details(query: GetListingDetails, session: Session) -> QueryResult:
17+
queryset = session.query(ListingModel).filter_by(id=query.listing_id)
18+
result = [map_listing_model_to_dao(row) for row in queryset.all()][0]
2019
return QueryResult.ok(result)

src/modules/catalog/application/query/get_listings_of_seller.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from modules.catalog.domain.repositories import ListingRepository
1+
from sqlalchemy.orm import Session
2+
3+
from modules.catalog.infrastructure.listing_repository import ListingModel
24
from seedwork.application.decorators import query_handler
35
from seedwork.application.queries import Query
46
from seedwork.application.query_handlers import QueryResult
@@ -10,10 +12,9 @@ class GetListingsOfSeller(Query):
1012

1113

1214
@query_handler
13-
def get_listings_of_seller(
14-
query: GetListingsOfSeller, listing_repository: ListingRepository
15-
) -> QueryResult:
16-
queryset = listing_repository.session.query(listing_repository.model) # .filter(
15+
def get_listings_of_seller(query: GetListingsOfSeller, session: Session) -> QueryResult:
16+
# FIXME: use seller_id to filter out listings
17+
queryset = session.query(ListingModel) # .filter(
1718
# listing_repository.model.data['seller'].astext.cast(UUID) == query.seller_id
1819
# )
1920
result = [dict(id=row.id, **row.data) for row in queryset.all()]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from modules.catalog.infrastructure.listing_repository import ListingModel
2+
3+
4+
def map_listing_model_to_dao(instance: ListingModel):
5+
"""maps ListingModel to a data access object (a dictionary)"""
6+
data = instance.data
7+
return dict(
8+
id=instance.id,
9+
title=data["title"],
10+
description=data["description"],
11+
ask_price_amount=data["ask_price"]["amount"],
12+
ask_price_currency=data["ask_price"]["currency"],
13+
seller_id=data["seller_id"],
14+
)

0 commit comments

Comments
 (0)