Skip to content

Commit 31d1db3

Browse files
committed
migrate to async
1 parent de8e330 commit 31d1db3

File tree

20 files changed

+153
-112
lines changed

20 files changed

+153
-112
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
default_language_version:
2-
python: python3.10
2+
python: python3.11
33

44
repos:
5-
# native hints instead of `from typing` | List -> list
6-
- repo: https://github.com/sondrelg/pep585-upgrade
7-
rev: 'v1.0' # Version to check
8-
hooks:
9-
- id: upgrade-type-hints
10-
115
# Only for removing unused imports > Other staff done by Black
126
- repo: https://github.com/myint/autoflake
137
rev: "v1.4" # Version to check

poetry.lock

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ requests = "^2.28.1"
2727
bcrypt = "^4.0.1"
2828
mypy = "^1.4.1"
2929
fastapi = "^0.110.0"
30-
lato = "^0.10.0"
3130
pydantic-settings = "^2.2.1"
31+
lato = "^0.12.0"
3232

3333
[tool.poetry.dev-dependencies]
3434
poethepoet = "^0.10.0"

src/api/dependencies.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,23 @@
22

33
from fastapi import Depends, Request
44
from fastapi.security import OAuth2PasswordBearer
5+
from lato import Application, TransactionContext
56

67
from modules.iam.application.services import IamService
78
from modules.iam.domain.entities import User
8-
from lato import Application, TransactionContext
99

1010
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
1111

1212

1313
async def get_application(request: Request) -> Application:
14-
application = request.app.container.application()
15-
return application
14+
return request.state.lato_application
1615

1716

1817
async def get_transaction_context(
1918
app: Annotated[Application, Depends(get_application)],
2019
) -> TransactionContext:
2120
"""Creates a new transaction context for each request"""
22-
23-
with app.transaction_context() as ctx:
21+
async with app.transaction_context() as ctx:
2422
yield ctx
2523

2624

src/api/main.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
from fastapi import FastAPI, Request
44
from fastapi.responses import JSONResponse
5+
from pydantic import ValidationError
56

67
from api.dependencies import oauth2_scheme # noqa
78
from api.routers import bidding, catalog, diagnostics, iam
89
from config.api_config import ApiConfig
9-
from config.container import create_application, ApplicationContainer
10+
from config.container import ApplicationContainer
1011
from seedwork.domain.exceptions import DomainException, EntityNotFoundException
1112
from seedwork.infrastructure.database import Base
1213
from seedwork.infrastructure.logging import LoggerFactory, logger
@@ -28,16 +29,27 @@
2829
app.include_router(bidding.router)
2930
app.include_router(iam.router)
3031
app.include_router(diagnostics.router)
31-
app.container = container
32+
app.container = container # type: ignore
3233

3334

35+
@app.exception_handler(ValidationError)
36+
async def pydantic_validation_exception_handler(request: Request, exc: ValidationError):
37+
return JSONResponse(
38+
status_code=422,
39+
content={
40+
"detail": exc.errors(),
41+
},
42+
)
43+
44+
45+
# startup
3446

3547
try:
3648
import uuid
3749

3850
from modules.iam.application.services import IamService
3951

40-
with app.container.application().transaction_context() as ctx:
52+
with container.application().transaction_context() as ctx:
4153
iam_service = ctx[IamService]
4254
iam_service.create_user(
4355
user_id=uuid.UUID(int=1),
@@ -50,7 +62,7 @@
5062

5163

5264
@app.exception_handler(DomainException)
53-
async def unicorn_exception_handler(request: Request, exc: DomainException):
65+
async def domain_exception_handler(request: Request, exc: DomainException):
5466
if container.config.DEBUG:
5567
raise exc
5668

@@ -61,7 +73,9 @@ async def unicorn_exception_handler(request: Request, exc: DomainException):
6173

6274

6375
@app.exception_handler(EntityNotFoundException)
64-
async def unicorn_exception_handler(request: Request, exc: EntityNotFoundException):
76+
async def entity_not_found_exception_handler(
77+
request: Request, exc: EntityNotFoundException
78+
):
6579
return JSONResponse(
6680
status_code=404,
6781
content={
@@ -70,6 +84,12 @@ async def unicorn_exception_handler(request: Request, exc: EntityNotFoundExcepti
7084
)
7185

7286

87+
@app.middleware("http")
88+
async def add_lato_application(request: Request, call_next):
89+
request.state.lato_application = container.application()
90+
return await call_next(request)
91+
92+
7393
@app.middleware("http")
7494
async def add_process_time(request: Request, call_next):
7595
start_time = time.time()

src/api/routers/bidding.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from typing import Annotated
22

33
from fastapi import APIRouter, Depends
4+
from lato import Application
45

56
from api.dependencies import get_application
67
from api.models.bidding import BiddingResponse, PlaceBidRequest
78
from config.container import inject
89
from modules.bidding.application.command import PlaceBidCommand, RetractBidCommand
910
from modules.bidding.application.query.get_bidding_details import GetBiddingDetails
10-
from lato import Application
1111

1212
router = APIRouter()
1313

@@ -25,7 +25,7 @@ async def get_bidding_details_of_listing(
2525
Shows listing details
2626
"""
2727
query = GetBiddingDetails(listing_id=listing_id)
28-
result = app.execute(query)
28+
result = await app.execute_async(query)
2929
return BiddingResponse(
3030
listing_id=result.id,
3131
auction_end_date=result.ends_at,
@@ -52,10 +52,11 @@ async def place_bid(
5252
bidder_id=request_body.bidder_id,
5353
amount=request_body.amount,
5454
)
55-
app.execute(command)
55+
await app.execute_async(command)
56+
# execute_async, or execute?
5657

5758
query = GetBiddingDetails(listing_id=listing_id)
58-
result = app.execute(query)
59+
result = await app.execute_async(query)
5960
return BiddingResponse(
6061
listing_id=result.id,
6162
auction_end_date=result.ends_at,

src/api/routers/catalog.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@
2222

2323

2424
@router.get("/catalog", tags=["catalog"], response_model=ListingIndexModel)
25-
@inject
26-
def get_all_listings(app: Annotated[Application, Depends(get_application)]):
25+
async def get_all_listings(app: Annotated[Application, Depends(get_application)]):
2726
"""
2827
Shows all published listings in the catalog
2928
"""
3029
query = GetAllListings()
31-
result = app.execute(query)
30+
result = await app.execute_async(query)
3231
return dict(data=result)
3332

3433

@@ -41,7 +40,7 @@ async def get_listing_details(
4140
Shows listing details
4241
"""
4342
query = GetListingDetails(listing_id=listing_id)
44-
query_result = app.execute_query(query)
43+
query_result = await app.execute_async(query)
4544
return dict(data=query_result.payload)
4645

4746

@@ -87,7 +86,7 @@ async def delete_listing(
8786
listing_id=listing_id,
8887
seller_id=current_user.id,
8988
)
90-
app.execute(command)
89+
await app.execute_async(command)
9190

9291

9392
@router.post(
@@ -109,8 +108,8 @@ async def publish_listing(
109108
listing_id=listing_id,
110109
seller_id=current_user.id,
111110
)
112-
app.execute(command)
111+
await app.execute_async(command)
113112

114113
query = GetListingDetails(listing_id=listing_id)
115-
response = app.execute(query)
114+
response = await app.execute_async(query)
116115
return response

src/api/tests/test_bidding.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from seedwork.infrastructure.logging import logger
99

1010

11-
def setup_app_for_bidding_tests(app, listing_id, seller_id, bidder_id):
11+
async def setup_app_for_bidding_tests(app, listing_id, seller_id, bidder_id):
1212
logger.info("Adding users")
1313
with app.transaction_context() as ctx:
1414
iam_service = ctx["iam_service"]
@@ -19,15 +19,17 @@ def setup_app_for_bidding_tests(app, listing_id, seller_id, bidder_id):
1919
password="password",
2020
access_token="token1",
2121
)
22+
ctx["logger"].debug(f"Added seller: {seller_id}")
23+
2224
iam_service.create_user(
2325
user_id=bidder_id,
2426
2527
password="password",
2628
access_token="token2",
2729
)
30+
ctx["logger"].debug(f"Added bidder: {bidder_id}")
2831

29-
logger.info("Adding listing")
30-
app.execute(
32+
await app.execute_async(
3133
CreateListingDraftCommand(
3234
listing_id=listing_id,
3335
title="Foo",
@@ -36,18 +38,23 @@ def setup_app_for_bidding_tests(app, listing_id, seller_id, bidder_id):
3638
seller_id=seller_id,
3739
)
3840
)
39-
app.execute(
41+
logger.info(f"Created listing draft: {listing_id}")
42+
43+
await app.execute_async(
4044
PublishListingDraftCommand(listing_id=listing_id, seller_id=seller_id)
4145
)
46+
logger.info(f"Published listing draft {listing_id} by seller {seller_id}")
47+
4248
logger.info("test setup complete")
4349

4450

4551
@pytest.mark.integration
46-
def test_place_bid(app, api_client):
52+
@pytest.mark.asyncio
53+
async def test_place_bid(app, api_client):
4754
listing_id = GenericUUID(int=1)
4855
seller_id = GenericUUID(int=2)
4956
bidder_id = GenericUUID(int=3)
50-
setup_app_for_bidding_tests(app, listing_id, seller_id, bidder_id)
57+
await setup_app_for_bidding_tests(app, listing_id, seller_id, bidder_id)
5158

5259
url = f"/bidding/{listing_id}/place_bid"
5360

src/api/tests/test_catalog.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ def test_empty_catalog_list(api_client):
1515

1616

1717
@pytest.mark.integration
18-
def test_catalog_list_with_one_item(app, api_client):
18+
@pytest.mark.asyncio
19+
async def test_catalog_list_with_one_item(app, api_client):
1920
# arrange
20-
app.execute(
21+
await app.execute_async(
2122
CreateListingDraftCommand(
2223
listing_id=GenericUUID(int=1),
2324
title="Foo",
@@ -48,9 +49,10 @@ def test_catalog_list_with_one_item(app, api_client):
4849

4950

5051
@pytest.mark.integration
51-
def test_catalog_list_with_two_items(app, api_client):
52+
@pytest.mark.asyncio
53+
async def test_catalog_list_with_two_items(app, api_client):
5254
# arrange
53-
app.execute(
55+
await app.execute_async(
5456
CreateListingDraftCommand(
5557
listing_id=GenericUUID(int=1),
5658
title="Foo #1",
@@ -59,7 +61,7 @@ def test_catalog_list_with_two_items(app, api_client):
5961
seller_id=GenericUUID(int=2),
6062
)
6163
)
62-
app.execute(
64+
await app.execute_async(
6365
CreateListingDraftCommand(
6466
listing_id=GenericUUID(int=2),
6567
title="Foo #2",
@@ -86,9 +88,10 @@ def test_catalog_create_draft_fails_due_to_incomplete_data(
8688

8789

8890
@pytest.mark.integration
89-
def test_catalog_delete_draft(app, authenticated_api_client):
91+
@pytest.mark.asyncio
92+
async def test_catalog_delete_draft(app, authenticated_api_client):
9093
current_user = authenticated_api_client.current_user
91-
app.execute(
94+
await app.execute_async(
9295
CreateListingDraftCommand(
9396
listing_id=GenericUUID(int=1),
9497
title="Listing to be deleted",
@@ -111,11 +114,12 @@ def test_catalog_delete_non_existing_draft_returns_404(authenticated_api_client)
111114

112115

113116
@pytest.mark.integration
114-
def test_catalog_publish_listing_draft(app, authenticated_api_client):
117+
@pytest.mark.asyncio
118+
async def test_catalog_publish_listing_draft(app, authenticated_api_client):
115119
# arrange
116120
current_user = authenticated_api_client.current_user
117121
listing_id = GenericUUID(int=1)
118-
app.execute(
122+
await app.execute_async(
119123
CreateListingDraftCommand(
120124
listing_id=listing_id,
121125
title="Listing to be published",
@@ -132,11 +136,12 @@ def test_catalog_publish_listing_draft(app, authenticated_api_client):
132136
assert response.status_code == 200
133137

134138

135-
def test_published_listing_appears_in_biddings(app, authenticated_api_client):
139+
@pytest.mark.asyncio
140+
async def test_published_listing_appears_in_biddings(app, authenticated_api_client):
136141
# arrange
137142
listing_id = GenericUUID(int=1)
138143
current_user = authenticated_api_client.current_user
139-
app.execute(
144+
await app.execute_async(
140145
CreateListingDraftCommand(
141146
listing_id=listing_id,
142147
title="Listing to be published",
@@ -145,7 +150,7 @@ def test_published_listing_appears_in_biddings(app, authenticated_api_client):
145150
seller_id=current_user.id,
146151
)
147152
)
148-
app.execute(
153+
await app.execute_async(
149154
PublishListingDraftCommand(
150155
listing_id=listing_id,
151156
seller_id=current_user.id,

0 commit comments

Comments
 (0)