Skip to content

Commit d483e62

Browse files
committed
Make DependencyContainer async
1 parent d6c751d commit d483e62

File tree

8 files changed

+113
-94
lines changed

8 files changed

+113
-94
lines changed
Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
from azure.cosmos import CosmosClient, PartitionKey
1+
from collections.abc import AsyncGenerator
2+
from contextlib import asynccontextmanager
3+
4+
from azure.cosmos import PartitionKey
5+
from azure.cosmos.aio import CosmosClient, DatabaseProxy
26

37
from api.application_settings import ApplicationSettings
48
from api.workflows.products.discontinue_product.discontinue_product_workflow import (
@@ -13,30 +17,54 @@
1317

1418
class DependencyContainer:
1519
@classmethod
16-
def initialize(cls) -> None:
20+
async def initialize(cls) -> None:
1721
cls.initialize_application_settings()
18-
cls.initialize_local_environment()
22+
await cls.initialize_local_environment()
1923

2024
@classmethod
2125
def initialize_application_settings(cls) -> None:
2226
cls.application_settings = ApplicationSettings() # type: ignore[reportCallIssue]
2327

2428
@classmethod
25-
def initialize_local_environment(cls) -> None:
26-
if ApplicationEnvironment.get_current() != ApplicationEnvironment.LOCAL:
27-
return
29+
@asynccontextmanager
30+
async def get_cosmosdb_client(
31+
cls,
32+
) -> AsyncGenerator[CosmosClient]:
33+
application_settings = cls.get_application_settings()
2834

29-
with CosmosClient(
30-
cls.get_application_settings().cosmos_db_no_sql_url,
31-
cls.get_application_settings().cosmos_db_no_sql_key.get_secret_value(),
35+
async with CosmosClient(
36+
application_settings.cosmos_db_no_sql_url,
37+
application_settings.cosmos_db_no_sql_key.get_secret_value(),
3238
) as cosmosdb_client:
33-
cosmosdb_client.create_database_if_not_exists(
34-
id=cls.get_application_settings().cosmos_db_no_sql_database
35-
)
39+
yield cosmosdb_client
40+
41+
@classmethod
42+
@asynccontextmanager
43+
async def get_cosmosdb_database(
44+
cls,
45+
) -> AsyncGenerator[DatabaseProxy]:
46+
application_settings = cls.get_application_settings()
47+
48+
async with cls.get_cosmosdb_client() as cosmosdb_client:
3649
cosmosdb_database = cosmosdb_client.get_database_client(
37-
cls.get_application_settings().cosmos_db_no_sql_database
50+
application_settings.cosmos_db_no_sql_database
3851
)
39-
cosmosdb_database.create_container_if_not_exists(
52+
yield cosmosdb_database
53+
54+
@classmethod
55+
async def initialize_local_environment(cls) -> None:
56+
if ApplicationEnvironment.get_current() != ApplicationEnvironment.LOCAL:
57+
return
58+
59+
application_settings = cls.get_application_settings()
60+
61+
async with cls.get_cosmosdb_client() as cosmosdb_client:
62+
await cosmosdb_client.create_database_if_not_exists(
63+
application_settings.cosmos_db_no_sql_database
64+
)
65+
66+
async with cls.get_cosmosdb_database() as cosmosdb_database:
67+
await cosmosdb_database.create_container_if_not_exists(
4068
id=Product.__name__, partition_key=PartitionKey("/id")
4169
)
4270

@@ -45,13 +73,17 @@ def get_application_settings(cls) -> ApplicationSettings:
4573
return cls.application_settings
4674

4775
@classmethod
48-
def get_publish_product_workflow(cls) -> PublishProductWorkflow:
49-
return PublishProductWorkflow(
50-
application_settings=cls.get_application_settings()
51-
)
76+
@asynccontextmanager
77+
async def get_publish_product_workflow(
78+
cls,
79+
) -> AsyncGenerator[PublishProductWorkflow]:
80+
async with cls.get_cosmosdb_database() as cosmosdb_database:
81+
yield PublishProductWorkflow(cosmosdb_database=cosmosdb_database)
5282

5383
@classmethod
54-
def get_discontinue_product_workflow(cls) -> DiscontinueProductWorkflow:
55-
return DiscontinueProductWorkflow(
56-
application_settings=cls.get_application_settings()
57-
)
84+
@asynccontextmanager
85+
async def get_discontinue_product_workflow(
86+
cls,
87+
) -> AsyncGenerator[DiscontinueProductWorkflow]:
88+
async with cls.get_cosmosdb_database() as cosmosdb_database:
89+
yield DiscontinueProductWorkflow(cosmosdb_database=cosmosdb_database)

api/src/api/main.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from collections.abc import AsyncGenerator
2+
from contextlib import asynccontextmanager
3+
14
from fastapi import FastAPI
25
from fastapi.middleware.cors import CORSMiddleware
36
from opentelemetry.instrumentation.fastapi import ( # type: ignore[reportMissingTypeStubs]
@@ -9,17 +12,27 @@
912
from common.application_environment import ApplicationEnvironment
1013

1114

15+
@asynccontextmanager
16+
async def lifespan(_: FastAPI) -> AsyncGenerator[None]:
17+
await DependencyContainer.initialize()
18+
yield
19+
20+
1221
def add_telemetry(app: FastAPI) -> None:
1322
FastAPIInstrumentor.instrument_app(app) # type: ignore[reportUnknownMemberType]
1423

1524

16-
DependencyContainer.initialize()
1725
openapi_url = (
1826
"/openapi.json"
1927
if ApplicationEnvironment.get_current() != ApplicationEnvironment.PRODUCTION
2028
else None
2129
)
22-
app = FastAPI(title="Python monorepo", version="0.1.0", openapi_url=openapi_url)
30+
app = FastAPI(
31+
title="Python monorepo",
32+
version="0.1.0",
33+
openapi_url=openapi_url,
34+
lifespan=lifespan,
35+
)
2336
app.add_middleware(
2437
CORSMiddleware,
2538
allow_origins=["*"],
Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
1-
from azure.cosmos.aio import CosmosClient
1+
from azure.cosmos.aio import DatabaseProxy
22

3-
from api.application_settings import ApplicationSettings
43
from api.workflows.products.discontinue_product.discontinue_product_request import (
54
DiscontinueProductRequest,
65
)
76
from domain.entities.product import Product
87

98

109
class DiscontinueProductWorkflow:
11-
def __init__(self, application_settings: ApplicationSettings) -> None:
12-
self.application_settings = application_settings
10+
def __init__(self, cosmosdb_database: DatabaseProxy) -> None:
11+
self.cosmosdb_database = cosmosdb_database
1312

1413
async def execute(self, request: DiscontinueProductRequest) -> None:
15-
async with CosmosClient(
16-
self.application_settings.cosmos_db_no_sql_url,
17-
self.application_settings.cosmos_db_no_sql_key.get_secret_value(),
18-
) as cosmosdb_client:
19-
cosmosdb_database = cosmosdb_client.get_database_client(
20-
self.application_settings.cosmos_db_no_sql_database
21-
)
22-
product_container = cosmosdb_database.get_container_client(Product.__name__)
23-
product_dict = await product_container.read_item(
24-
item=request.id, partition_key=request.id
25-
)
26-
product = Product.model_validate(product_dict)
27-
product.discontinue(request.discontinuation_reason)
28-
await product_container.replace_item(product.id, product.model_dump())
14+
product_container = self.cosmosdb_database.get_container_client(
15+
Product.__name__
16+
)
17+
product_dict = await product_container.read_item(
18+
item=request.id, partition_key=request.id
19+
)
20+
product = Product.model_validate(product_dict)
21+
product.discontinue(request.discontinuation_reason)
22+
await product_container.replace_item(product.id, product.model_dump())

api/src/api/workflows/products/product_router.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919

2020
@router.post("")
2121
async def publish_product(request: PublishProductRequest) -> PublishProductResponse:
22-
return await DependencyContainer.get_publish_product_workflow().execute(request)
22+
async with DependencyContainer.get_publish_product_workflow() as workflow:
23+
return await workflow.execute(request)
2324

2425

2526
@router.post("/discontinue")
2627
async def discontinue_product(request: DiscontinueProductRequest) -> None:
27-
return await DependencyContainer.get_discontinue_product_workflow().execute(request)
28+
async with DependencyContainer.get_discontinue_product_workflow() as workflow:
29+
return await workflow.execute(request)
Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
from azure.cosmos.aio import CosmosClient
1+
from azure.cosmos.aio import DatabaseProxy
22

3-
from api.application_settings import ApplicationSettings
43
from api.workflows.products.publish_product.publish_product_request import (
54
PublishProductRequest,
65
)
@@ -11,22 +10,17 @@
1110

1211

1312
class PublishProductWorkflow:
14-
def __init__(self, application_settings: ApplicationSettings) -> None:
15-
self.application_settings = application_settings
13+
def __init__(self, cosmosdb_database: DatabaseProxy) -> None:
14+
self.cosmosdb_database = cosmosdb_database
1615

1716
async def execute(self, request: PublishProductRequest) -> PublishProductResponse:
18-
async with CosmosClient(
19-
self.application_settings.cosmos_db_no_sql_url,
20-
self.application_settings.cosmos_db_no_sql_key.get_secret_value(),
21-
) as cosmosdb_client:
22-
cosmosdb_database = cosmosdb_client.get_database_client(
23-
self.application_settings.cosmos_db_no_sql_database
24-
)
25-
product_container = cosmosdb_database.get_container_client(Product.__name__)
26-
product = Product.publish(
27-
name=request.name,
28-
description=request.description,
29-
price=request.price,
30-
)
31-
await product_container.create_item(product.model_dump())
32-
return PublishProductResponse(id=product.id)
17+
product_container = self.cosmosdb_database.get_container_client(
18+
Product.__name__
19+
)
20+
product = Product.publish(
21+
name=request.name,
22+
description=request.description,
23+
price=request.price,
24+
)
25+
await product_container.create_item(product.model_dump())
26+
return PublishProductResponse(id=product.id)

api/tests/integration/conftest.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,8 @@
1-
from collections.abc import AsyncGenerator
2-
31
import pytest
4-
from azure.cosmos.aio import CosmosClient, DatabaseProxy
52

63
from api.dependency_container import DependencyContainer
74

85

96
@pytest.fixture(autouse=True)
10-
def initialize_dependency_container() -> None:
11-
DependencyContainer.initialize()
12-
13-
14-
@pytest.fixture
15-
async def cosmosdb_database() -> AsyncGenerator[DatabaseProxy, None]:
16-
application_settings = DependencyContainer.get_application_settings()
17-
18-
async with CosmosClient(
19-
application_settings.cosmos_db_no_sql_url,
20-
application_settings.cosmos_db_no_sql_key.get_secret_value(),
21-
) as cosmosdb_client:
22-
cosmosdb_database = cosmosdb_client.get_database_client(
23-
application_settings.cosmos_db_no_sql_database
24-
)
25-
yield cosmosdb_database
7+
async def initialize_dependency_container() -> None:
8+
await DependencyContainer.initialize()
Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import pytest
2-
from azure.cosmos.aio import DatabaseProxy
32

43
from api.dependency_container import DependencyContainer
54
from api.workflows.products.discontinue_product.discontinue_product_request import (
@@ -11,12 +10,15 @@
1110

1211
@pytest.mark.integration
1312
class TestDiscontinueProductWorkflow:
14-
async def test_discontinue_product(self, cosmosdb_database: DatabaseProxy) -> None:
15-
product = ProductBuilder().build()
16-
await cosmosdb_database.get_container_client(Product.__name__).create_item(
17-
product.model_dump()
18-
)
19-
request = DiscontinueProductRequest(id=product.id)
20-
workflow = DependencyContainer.get_discontinue_product_workflow()
13+
async def test_discontinue_product(self) -> None:
14+
async with DependencyContainer.get_cosmosdb_database() as cosmosdb_database:
15+
product = ProductBuilder().build()
16+
await cosmosdb_database.get_container_client(Product.__name__).create_item(
17+
product.model_dump()
18+
)
19+
request = DiscontinueProductRequest(id=product.id)
2120

22-
await workflow.execute(request)
21+
async with (
22+
DependencyContainer.get_discontinue_product_workflow() as workflow
23+
):
24+
await workflow.execute(request)

api/tests/integration/workflows/products/publish_product/test_publish_product_workflow.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,5 @@ class TestPublishProductWorkflow:
1111
async def test_publish_product(self) -> None:
1212
request = PublishProductRequestBuilder().build()
1313

14-
workflow = DependencyContainer.get_publish_product_workflow()
15-
16-
await workflow.execute(request)
14+
async with DependencyContainer.get_publish_product_workflow() as workflow:
15+
await workflow.execute(request)

0 commit comments

Comments
 (0)