Skip to content

Commit 5bade23

Browse files
committed
big refactor of application and modules
1 parent 2f24d47 commit 5bade23

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+1294
-1768
lines changed

src/api/dependencies.py

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,34 @@
1-
from collections.abc import Callable
1+
from typing import Annotated
22

33
from fastapi import Depends, HTTPException, Request
44
from fastapi.security import OAuth2PasswordBearer
55

6-
from config.container import Container, inject
7-
from modules.iam.domain.entities import User
8-
from seedwork.application import Application
9-
10-
from .shared import dependency
6+
from modules.iam.application.services import IamService
7+
from modules.iam.domain.entities import AnonymousUser
8+
from seedwork.application import Application, TransactionContext
119

1210
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
1311

1412

15-
def allow_role(role: str):
16-
def check(user: User):
17-
if user.role != role:
18-
raise HTTPException(status_code=403, detail="Forbidden")
19-
return True
20-
21-
return check
22-
23-
24-
def allow_authenticated():
25-
def check(user: User):
26-
if not user.is_authenticated():
27-
raise HTTPException(status_code=403, detail="Forbidden")
28-
return True
13+
async def get_application(request: Request) -> Application:
14+
application = request.app.container.application()
15+
return application
2916

30-
return check
3117

18+
async def get_transaction_context(
19+
request: Request, app: Annotated[Application, Depends(get_application)]
20+
) -> TransactionContext:
21+
"""Creates a new transaction context for each request"""
3222

33-
def allow_anonymous():
34-
return lambda user: True
35-
36-
37-
def create_app(check: callable) -> Callable[..., Application]:
38-
@inject
39-
async def create(
40-
request: Request, app: Application = dependency(Container.application)
41-
):
23+
with app.transaction_context() as ctx:
4224
try:
4325
access_token = await oauth2_scheme(request=request)
44-
current_user = app.iam_service.find_user_by_access_token(access_token)
26+
current_user = ctx.get_service(IamService).find_user_by_access_token(
27+
access_token
28+
)
4529
except HTTPException as e:
46-
current_user = User.Anonymous()
47-
48-
print("current user", current_user)
49-
check(current_user)
50-
app.current_user = current_user
51-
return app
52-
53-
return create
30+
current_user = AnonymousUser()
5431

32+
ctx.dependency_provider["current_user"] = current_user
5533

56-
def get_current_active_user(
57-
app: Application = Depends(create_app(allow_authenticated())),
58-
):
59-
return app.current_user
34+
yield ctx

src/api/main.py

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
from fastapi import FastAPI, Request
44
from fastapi.responses import JSONResponse
55

6-
import api.routers.catalog
7-
from api.models.catalog import CurrentUser
86
from api.routers import bidding, catalog, diagnostics, iam
97
from config.api_config import ApiConfig
10-
from config.container import Container
8+
from config.container import TopLevelContainer
119
from seedwork.domain.exceptions import DomainException, EntityNotFoundException
1210
from seedwork.infrastructure.logging import LoggerFactory, logger
1311
from seedwork.infrastructure.request_context import request_context
@@ -16,17 +14,8 @@
1614
LoggerFactory.configure(logger_name="api")
1715

1816
# dependency injection container
19-
container = Container()
17+
container = TopLevelContainer()
2018
container.config.from_pydantic(ApiConfig())
21-
container.wire(
22-
modules=[
23-
api.dependencies,
24-
api.routers.catalog,
25-
api.routers.bidding,
26-
api.routers.iam,
27-
api.routers.diagnostics,
28-
]
29-
)
3019

3120
app = FastAPI(debug=container.config.DEBUG)
3221
app.include_router(catalog.router)
@@ -35,14 +24,14 @@
3524
app.include_router(diagnostics.router)
3625
app.container = container
3726

38-
logger.info("using db engine %s" % str(container.engine()))
27+
# logger.info("using db engine %s" % str(container.engine()))
3928

4029

4130
@app.exception_handler(DomainException)
4231
async def unicorn_exception_handler(request: Request, exc: DomainException):
4332
return JSONResponse(
4433
status_code=500,
45-
content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
34+
content={"message": f"Oops! {exc} did something. There goes a rainbow..."},
4635
)
4736

4837

@@ -51,22 +40,23 @@ async def unicorn_exception_handler(request: Request, exc: EntityNotFoundExcepti
5140
return JSONResponse(
5241
status_code=404,
5342
content={
54-
"message": f"Entity {exc.entity_id} not found in {exc.repository.__class__.__name__}"
43+
"message": f"Entity {exc.kwargs} not found in {exc.repository.__class__.__name__}"
5544
},
5645
)
5746

5847

5948
@app.middleware("http")
6049
async def add_request_context(request: Request, call_next):
6150
start_time = time.time()
62-
request_context.begin_request(current_user=CurrentUser.fake_user())
51+
# request_context.begin_request(current_user=CurrentUser.fake_user())
6352
try:
6453
response = await call_next(request)
6554
process_time = time.time() - start_time
6655
response.headers["X-Process-Time"] = str(process_time)
6756
return response
6857
finally:
69-
request_context.end_request()
58+
pass
59+
# request_context.end_request()
7060

7161

7262
@app.get("/")

src/api/models/common.py

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/api/routers/bidding.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
from fastapi import APIRouter
1+
from typing import Annotated
22

3+
from fastapi import APIRouter, Depends
4+
5+
from api.dependencies import get_application
36
from api.models.bidding import BiddingResponse, PlaceBidRequest
4-
from api.shared import dependency
5-
from config.container import Container, inject
7+
from config.container import inject
68
from modules.bidding.application.command import PlaceBidCommand, RetractBidCommand
79
from modules.bidding.application.query.get_bidding_details import GetBiddingDetails
810
from seedwork.application import Application
@@ -17,8 +19,7 @@
1719
@router.get("/bidding/{listing_id}", tags=["bidding"], response_model=BiddingResponse)
1820
@inject
1921
async def get_bidding_details_of_listing(
20-
listing_id,
21-
app: Application = dependency(Container.application),
22+
listing_id, app: Annotated[Application, Depends(get_application)]
2223
):
2324
"""
2425
Shows listing details
@@ -40,7 +41,7 @@ async def get_bidding_details_of_listing(
4041
async def place_bid(
4142
listing_id,
4243
request_body: PlaceBidRequest,
43-
app: Application = dependency(Container.application),
44+
app: Annotated[Application, Depends(get_application)],
4445
):
4546
"""
4647
Places a bid on a listing
@@ -52,7 +53,7 @@ async def place_bid(
5253
bidder_id=request_body.bidder_id,
5354
amount=request_body.amount,
5455
)
55-
app.execute_command(command)
56+
result = app.execute_command(command)
5657

5758
query = GetBiddingDetails(listing_id=listing_id)
5859
query_result = app.execute_query(query)
@@ -71,8 +72,7 @@ async def place_bid(
7172
)
7273
@inject
7374
async def retract_bid(
74-
listing_id,
75-
app: Application = dependency(Container.application),
75+
listing_id, app: Annotated[Application, Depends(get_application)]
7676
):
7777
"""
7878
Retracts a bid from a listing

src/api/routers/catalog.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
from fastapi import APIRouter
1+
from typing import Annotated
2+
from uuid import UUID
23

4+
from fastapi import APIRouter, Depends
5+
6+
from api.dependencies import Application, get_application
37
from api.models.catalog import ListingIndexModel, ListingReadModel, ListingWriteModel
4-
from api.shared import dependency
5-
from config.container import Container, inject
8+
from config.container import inject
69
from modules.catalog.application.command import (
710
CreateListingDraftCommand,
811
DeleteListingDraftCommand,
@@ -23,9 +26,7 @@
2326

2427
@router.get("/catalog", tags=["catalog"], response_model=ListingIndexModel)
2528
@inject
26-
async def get_all_listings(
27-
app: Application = dependency(Container.application),
28-
):
29+
async def get_all_listings(app: Annotated[Application, Depends(get_application)]):
2930
"""
3031
Shows all published listings in the catalog
3132
"""
@@ -37,8 +38,7 @@ async def get_all_listings(
3738
@router.get("/catalog/{listing_id}", tags=["catalog"], response_model=ListingReadModel)
3839
@inject
3940
async def get_listing_details(
40-
listing_id,
41-
app: Application = dependency(Container.application),
41+
listing_id, app: Annotated[Application, Depends(get_application)]
4242
):
4343
"""
4444
Shows listing details
@@ -54,7 +54,7 @@ async def get_listing_details(
5454
@inject
5555
async def create_listing(
5656
request_body: ListingWriteModel,
57-
app: Application = dependency(Container.application),
57+
app: Annotated[Application, Depends(get_application)],
5858
):
5959
"""
6060
Creates a new listing.
@@ -77,8 +77,7 @@ async def create_listing(
7777
)
7878
@inject
7979
async def delete_listing(
80-
listing_id,
81-
app: Application = dependency(Container.application),
80+
listing_id, app: Annotated[Application, Depends(get_application)]
8281
):
8382
"""
8483
Delete listing
@@ -97,17 +96,16 @@ async def delete_listing(
9796
)
9897
@inject
9998
async def publish_listing(
100-
listing_id,
101-
app: Application = dependency(Container.application),
99+
listing_id: UUID, app: Annotated[Application, Depends(get_application)]
102100
):
103101
"""
104102
Creates a new listing.
105103
"""
106104
command = PublishListingDraftCommand(
107105
listing_id=listing_id,
108106
)
109-
command_result = app.execute_command(command)
107+
app.execute_command(command)
110108

111-
query = GetListingDetails(listing_id=command_result.entity_id)
109+
query = GetListingDetails(listing_id=listing_id)
112110
query_result = app.execute_query(query)
113111
return dict(query_result.payload)

src/api/routers/diagnostics.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,21 @@
22

33
from fastapi import APIRouter, Depends
44

5-
from api.dependencies import allow_anonymous, create_app
6-
from seedwork.application import Application
7-
8-
router = APIRouter()
9-
5+
from api.dependencies import get_transaction_context
6+
from seedwork.application import TransactionContext
107

118
from .iam import UserResponse
129

13-
app_for_anonymous = create_app(allow_anonymous())
10+
router = APIRouter()
1411

1512

1613
@router.get("/debug", tags=["diagnostics"])
17-
async def debug(app: Annotated[Application, Depends(app_for_anonymous)]):
14+
async def debug(ctx: Annotated[TransactionContext, Depends(get_transaction_context)]):
1815
return dict(
19-
app_id=id(app),
16+
app_id=id(ctx.app),
2017
user=UserResponse(
21-
id=str(app.current_user.id), username=app.current_user.username
18+
id=str(ctx.current_user.id), username=ctx.current_user.username
2219
),
23-
name=app.name,
24-
version=app.version,
20+
name=ctx.app.name,
21+
version=ctx.app.version,
2522
)

src/api/routers/iam.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1+
from typing import Annotated
2+
13
from fastapi import APIRouter, Depends, HTTPException, status
24
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
35
from pydantic import BaseModel
46

5-
from api.dependencies import get_current_active_user
6-
from api.shared import dependency
7-
from config.container import Container, inject
7+
from api.dependencies import (
8+
Application,
9+
TransactionContext,
10+
get_application,
11+
get_transaction_context,
12+
)
13+
from config.container import inject
814
from modules.iam.application.exceptions import InvalidCredentialsException
9-
from modules.iam.domain.entities import User
10-
from seedwork.application import Application
15+
from modules.iam.application.services import IamService
1116

1217
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
1318
router = APIRouter()
@@ -19,18 +24,19 @@ class UserResponse(BaseModel):
1924

2025

2126
@router.get("/token", tags=["iam"])
22-
async def get_token(token: str = Depends(oauth2_scheme)):
23-
return "sample_token"
27+
async def get_token(app: Annotated[Application, Depends(get_application)]):
28+
return app.current_user.access_token
2429

2530

2631
@router.post("/token", tags=["iam"])
2732
@inject
2833
async def login(
34+
ctx: Annotated[TransactionContext, Depends(get_transaction_context)],
2935
form_data: OAuth2PasswordRequestForm = Depends(),
30-
app: Application = dependency(Container.application),
3136
):
3237
try:
33-
user = app.iam_service.authenticate_with_password(
38+
iam_service = ctx.get_service(IamService)
39+
user = iam_service.authenticate_with_name_and_password(
3440
form_data.username, form_data.password
3541
)
3642
except InvalidCredentialsException:
@@ -44,5 +50,7 @@ async def login(
4450

4551

4652
@router.get("/users/me", tags=["iam"])
47-
async def get_users_me(current_user: User = Depends(get_current_active_user)):
48-
return current_user
53+
async def get_users_me(
54+
app: Annotated[Application, Depends(get_application)],
55+
):
56+
return app.current_user

src/api/shared.py

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)