Format everything

This commit is contained in:
Jacob Windsor 2025-02-19 16:25:48 +01:00
parent ed6c49a1b7
commit ecfef9a09d
22 changed files with 115 additions and 62 deletions

View File

@ -1,9 +1,23 @@
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
class HouseCreateRequest(BaseModel):
address: str = Field(..., min_length=1, max_length=255, description="House address", examples=["123 Main St"])
city: str = Field(..., description="City where the house is located", examples=["Springfield"])
country: str = Field(..., description="Country where the house is located", examples=["USA"])
price: float = Field(..., description="Price of the house", examples=[250000.00])
description: str = Field(..., description="Description of the house", examples=["A beautiful 3-bedroom house"])
class HouseCreateRequest(BaseModel):
address: str = Field(
...,
min_length=1,
max_length=255,
description="House address",
examples=["123 Main St"],
)
city: str = Field(
..., description="City where the house is located", examples=["Springfield"]
)
country: str = Field(
..., description="Country where the house is located", examples=["USA"]
)
price: float = Field(..., description="Price of the house", examples=[250000.00])
description: str = Field(
...,
description="Description of the house",
examples=["A beautiful 3-bedroom house"],
)

View File

@ -1,5 +1,5 @@
from pydantic import BaseModel from pydantic import BaseModel
class HouseCreateResponse(BaseModel): class HouseCreateResponse(BaseModel):
id: str id: str

View File

@ -1,5 +1,6 @@
from pydantic import BaseModel from pydantic import BaseModel
class HouseResponse(BaseModel): class HouseResponse(BaseModel):
id: str id: str
description: str description: str
@ -8,5 +9,6 @@ class HouseResponse(BaseModel):
country: str country: str
price: float price: float
class HousesListResponse(BaseModel): class HousesListResponse(BaseModel):
houses: list[HouseResponse] houses: list[HouseResponse]

View File

@ -1,7 +1,7 @@
from pydantic import BaseModel from pydantic import BaseModel
class OwnerDetailResponse(BaseModel): class OwnerDetailResponse(BaseModel):
id: str id: str
user_id: str user_id: str
email: str email: str

View File

@ -1,5 +1,6 @@
from pydantic import BaseModel from pydantic import BaseModel
class OwnerResponse(BaseModel): class OwnerResponse(BaseModel):
id: str id: str
user_id: str user_id: str

View File

@ -8,11 +8,13 @@ from .middleware.authenticate import authenticate
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
@asynccontextmanager @asynccontextmanager
async def lifespan(_app: FastAPI): async def lifespan(_app: FastAPI):
create_db_and_tables() create_db_and_tables()
yield yield
app = FastAPI( app = FastAPI(
title="Fair Housing API", title="Fair Housing API",
description="Provides access to core functionality for the fair housing platform.", description="Provides access to core functionality for the fair housing platform.",

View File

@ -3,7 +3,10 @@ from typing import Annotated
from ..settings import get_settings from ..settings import get_settings
from ..repositories.user_repository import UserRepository from ..repositories.user_repository import UserRepository
async def authenticate(request: Request, user_repository: Annotated[UserRepository, Depends()]) -> bool:
async def authenticate(
request: Request, user_repository: Annotated[UserRepository, Depends()]
) -> bool:
""" """
Authenticate the current request. Authenticate the current request.
""" """

View File

@ -1,6 +1,7 @@
from sqlmodel import SQLModel, Field from sqlmodel import SQLModel, Field
from uuid import uuid4, UUID from uuid import uuid4, UUID
class House(SQLModel, table=True): class House(SQLModel, table=True):
id: UUID = Field(primary_key=True, default_factory=uuid4) id: UUID = Field(primary_key=True, default_factory=uuid4)
address: str = Field() address: str = Field()

View File

@ -1,6 +1,7 @@
from sqlmodel import SQLModel, Field from sqlmodel import SQLModel, Field
from uuid import uuid4, UUID from uuid import uuid4, UUID
class Owner(SQLModel, table=True): class Owner(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True) id: UUID = Field(default_factory=uuid4, primary_key=True)
user_id: UUID = Field(foreign_key="user.id", unique=True) user_id: UUID = Field(foreign_key="user.id", unique=True)

View File

@ -1,6 +1,7 @@
from sqlmodel import SQLModel, Field from sqlmodel import SQLModel, Field
from uuid import uuid4, UUID from uuid import uuid4, UUID
class User(SQLModel, table=True): class User(SQLModel, table=True):
id: UUID = Field(default_factory=lambda: uuid4(), primary_key=True) id: UUID = Field(default_factory=lambda: uuid4(), primary_key=True)
email: str = Field(unique=True, nullable=False) email: str = Field(unique=True, nullable=False)

View File

@ -6,6 +6,7 @@ from ..repositories.user_repository import UserRepository
from typing import Annotated from typing import Annotated
from fastapi import Depends, Request from fastapi import Depends, Request
class AuthContext: class AuthContext:
""" """
Provides authentication context for the current request. Provides authentication context for the current request.
@ -16,7 +17,9 @@ class AuthContext:
request: Request, request: Request,
) -> None: ) -> None:
if not get_settings().environment == "development": if not get_settings().environment == "development":
raise NotImplementedError("AuthProvider is only implemented for development environment.") raise NotImplementedError(
"AuthProvider is only implemented for development environment."
)
self._authenticated_user = request.state.user self._authenticated_user = request.state.user

View File

@ -1,4 +1,3 @@
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from sqlalchemy import Engine from sqlalchemy import Engine
@ -21,9 +20,9 @@ settings = get_settings()
async def _get_async_engine() -> AsyncEngine: async def _get_async_engine() -> AsyncEngine:
return create_async_engine( return create_async_engine(
f"postgresql+asyncpg://{settings.db.username}:{settings.db.password}@{settings.db.host}:{settings.db.port}/{settings.db.db_name}", f"postgresql+asyncpg://{settings.db.username}:{settings.db.password}@{settings.db.host}:{settings.db.port}/{settings.db.db_name}",
future=True, future=True,
) )
async def get_session() -> AsyncGenerator[AsyncSession, None]: async def get_session() -> AsyncGenerator[AsyncSession, None]:
@ -64,7 +63,9 @@ def _seed_db():
session = Session(_get_engine()) session = Session(_get_engine())
existing_user = session.query(User).filter(User.email == settings.app.mock_user_email).first() existing_user = (
session.query(User).filter(User.email == settings.app.mock_user_email).first()
)
if not existing_user: if not existing_user:
mock_user = User( mock_user = User(
email=settings.app.mock_user_email, email=settings.app.mock_user_email,
@ -80,4 +81,3 @@ def create_db_and_tables():
engine = _get_engine() engine = _get_engine()
SQLModel.metadata.create_all(engine) SQLModel.metadata.create_all(engine)
_seed_db() _seed_db()

View File

@ -7,6 +7,7 @@ from sqlmodel import select
from uuid import UUID from uuid import UUID
class HouseRepository: class HouseRepository:
def __init__(self, session: Annotated[AsyncSession, Depends(get_session)]) -> None: def __init__(self, session: Annotated[AsyncSession, Depends(get_session)]) -> None:
self.session = session self.session = session

View File

@ -7,6 +7,7 @@ from sqlmodel import select
from uuid import UUID from uuid import UUID
class OwnerRepository: class OwnerRepository:
def __init__(self, session: Annotated[AsyncSession, Depends(get_session)]) -> None: def __init__(self, session: Annotated[AsyncSession, Depends(get_session)]) -> None:
self.session = session self.session = session

View File

@ -7,6 +7,7 @@ from sqlmodel import select
from uuid import UUID from uuid import UUID
class UserRepository: class UserRepository:
def __init__(self, session: Annotated[AsyncSession, Depends(get_session)]) -> None: def __init__(self, session: Annotated[AsyncSession, Depends(get_session)]) -> None:
self.session = session self.session = session

View File

@ -11,14 +11,18 @@ from ..dtos.houses_list_response import HousesListResponse, HouseResponse
router = APIRouter() router = APIRouter()
@router.post("") @router.post("")
async def create_house(body: HouseCreateRequest, auth: Annotated[AuthContext, Depends()], house_repository: Annotated[HouseRepository, Depends()], owner_repository: Annotated[OwnerRepository, Depends()]) -> HouseCreateResponse: async def create_house(
body: HouseCreateRequest,
auth: Annotated[AuthContext, Depends()],
house_repository: Annotated[HouseRepository, Depends()],
owner_repository: Annotated[OwnerRepository, Depends()],
) -> HouseCreateResponse:
owner = await owner_repository.get_by_id(auth.user.id) owner = await owner_repository.get_by_id(auth.user.id)
if not owner: if not owner:
new_owner = Owner( new_owner = Owner(user_id=auth.user.id)
user_id=auth.user.id
)
await owner_repository.save(new_owner) await owner_repository.save(new_owner)
@ -28,24 +32,33 @@ async def create_house(body: HouseCreateRequest, auth: Annotated[AuthContext, De
city=body.city, city=body.city,
country=body.country, country=body.country,
price=body.price, price=body.price,
description=body.description description=body.description,
) )
await house_repository.save(house) await house_repository.save(house)
return HouseCreateResponse( return HouseCreateResponse(id=str(house.id))
id=str(house.id)
)
@router.get("") @router.get("")
async def get_all_houses(house_repository: Annotated[HouseRepository, Depends()], limit: int = 100, offset: int = 0) -> HousesListResponse: async def get_all_houses(
house_repository: Annotated[HouseRepository, Depends()],
limit: int = 100,
offset: int = 0,
) -> HousesListResponse:
all_houses = await house_repository.get_all(offset=offset, limit=limit) all_houses = await house_repository.get_all(offset=offset, limit=limit)
house_responses = ([HouseResponse(id=str(house.id), description=house.description, address=house.address, city=house.city, country=house.country, price=house.price) for house in all_houses]) house_responses = [
HouseResponse(
id=str(house.id),
description=house.description,
address=house.address,
city=house.city,
country=house.country,
price=house.price,
)
for house in all_houses
]
print(house_responses) print(house_responses)
return HousesListResponse( return HousesListResponse(houses=house_responses)
houses=house_responses
)

View File

@ -10,22 +10,27 @@ router = APIRouter()
@router.get("") @router.get("")
async def get_owners(owner_repository: Annotated[OwnerRepository, Depends()]) -> OwnerListResponse: async def get_owners(
owner_repository: Annotated[OwnerRepository, Depends()],
) -> OwnerListResponse:
owners = await owner_repository.get_all() owners = await owner_repository.get_all()
owners_response = [OwnerResponse(id=str(owner.id), user_id=str(owner.user_id)) for owner in owners] owners_response = [
OwnerResponse(id=str(owner.id), user_id=str(owner.user_id)) for owner in owners
]
return OwnerListResponse(owners=owners_response)
return OwnerListResponse(
owners=owners_response
)
@router.get("/{id}") @router.get("/{id}")
async def get_owner(id: str, owner_repository: Annotated[OwnerRepository, Depends()], user_repository: Annotated[UserRepository, Depends()]) -> OwnerDetailResponse: async def get_owner(
id: str,
owner_repository: Annotated[OwnerRepository, Depends()],
user_repository: Annotated[UserRepository, Depends()],
) -> OwnerDetailResponse:
owner = await owner_repository.get_by_id(id) owner = await owner_repository.get_by_id(id)
user = await user_repository.get_by_id(owner.user_id) user = await user_repository.get_by_id(owner.user_id)
return OwnerDetailResponse( return OwnerDetailResponse(
id=str(owner.id), id=str(owner.id), user_id=str(owner.user_id), email=user.email
user_id=str(owner.user_id),
email=user.email
) )

View File

@ -20,7 +20,9 @@ class HousePricePredictor:
# Mock initialization - in reality would load model weights # Mock initialization - in reality would load model weights
pass pass
def predict(self, square_feet: float, bedrooms: int, bathrooms: float) -> Prediction: def predict(
self, square_feet: float, bedrooms: int, bathrooms: float
) -> Prediction:
""" """
Mock prediction method that returns: Mock prediction method that returns:
- predicted price - predicted price
@ -37,8 +39,7 @@ class HousePricePredictor:
# Add some randomness to make it interesting # Add some randomness to make it interesting
confidence_score = random.uniform(0.8, 0.99) confidence_score = random.uniform(0.8, 0.99)
similar_listings = [ similar_listings = [
predicted_price * random.uniform(0.9, 1.1) predicted_price * random.uniform(0.9, 1.1) for _ in range(3)
for _ in range(3)
] ]
return Prediction(predicted_price, confidence_score, similar_listings) return Prediction(predicted_price, confidence_score, similar_listings)

View File

@ -1,5 +1,6 @@
import random import random
class InvestorPredictor():
class InvestorPredictor:
def is_investor(user: User) -> bool: def is_investor(user: User) -> bool:
return random.random() < 0.5 return random.random() < 0.5

View File

@ -1,4 +1,3 @@
from functools import lru_cache from functools import lru_cache
from pydantic import Field from pydantic import Field
@ -9,12 +8,15 @@ import os
load_dotenv() load_dotenv()
class _BaseConfig(BaseSettings): class _BaseConfig(BaseSettings):
environment: str = Field(default=os.getenv("ENVIRONMENT", "development")) environment: str = Field(default=os.getenv("ENVIRONMENT", "development"))
class _AppSettings(_BaseConfig): class _AppSettings(_BaseConfig):
mock_user_email: str = "test@test.com" mock_user_email: str = "test@test.com"
class _DbSettings(_BaseConfig): class _DbSettings(_BaseConfig):
username: str = Field(default=os.getenv("PG_USER")) username: str = Field(default=os.getenv("PG_USER"))
password: str = Field(default=os.getenv("PG_PASSWORD")) password: str = Field(default=os.getenv("PG_PASSWORD"))