Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 13 additions & 15 deletions fastapi_ecom/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,26 @@
{"name": "business", "description": "Operations on businesses"},
{"name": "product", "description": "Operations on products"},
{"name": "customer", "description": "Operations on customers"},
{"name": "order", "description": "Operations on orders"}
{"name": "order", "description": "Operations on orders"},
]

# Initialize the FastAPI application
app = FastAPI(
title="FastAPI ECOM",
description="E-Commerce API for businesses and end users using FastAPI.",
version="0.1.0",
openapi_tags=tags_metadata,
swagger_ui_init_oauth={
"clientId": config.GOOGLE_CLIENT_ID,
"clientSecret": config.GOOGLE_CLIENT_SECRET,
}
)
title="FastAPI ECOM",
description="E-Commerce API for businesses and end users using FastAPI.",
version="0.1.0",
openapi_tags=tags_metadata,
swagger_ui_init_oauth={
"clientId": config.GOOGLE_CLIENT_ID,
"clientSecret": config.GOOGLE_CLIENT_SECRET,
},
)

app.add_middleware(SessionMiddleware, secret_key=config.GOOGLE_CLIENT_SECRET)

PREFIX = "/api/v1"


@app.get("/")
def root() -> dict[str, str]:
"""
Expand All @@ -38,11 +39,8 @@ def root() -> dict[str, str]:
:return: Metadata about the API, including title, description, and version.
"""
general("Root endpoint accessed")
return{
"title": "FastAPI ECOM",
"description": "E-Commerce API for businesses and end users using FastAPI.",
"version": "0.1.0"
}
return {"title": "FastAPI ECOM", "description": "E-Commerce API for businesses and end users using FastAPI.", "version": "0.1.0"}


# Include routers for different modules
app.include_router(business.router, prefix=PREFIX)
Expand Down
35 changes: 18 additions & 17 deletions fastapi_ecom/database/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from pathlib import Path
from typing import Union

from sqlalchemy import URL, Engine, create_engine
from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker, create_async_engine
Expand All @@ -11,10 +10,10 @@
baseobjc = declarative_base()

# Path of alembic configuration file
alempath = str(Path(str(Path(str(Path(__file__).parent.resolve().parent.resolve()),"migrations").resolve()),"alembic.ini").resolve())
alempath = str(Path(str(Path(str(Path(__file__).parent.resolve().parent.resolve()), "migrations").resolve()), "alembic.ini").resolve())

# Migration path for alembic configuration
migrpath = str(Path(str(Path(__file__).parent.resolve().parent.resolve()),"migrations").resolve())
migrpath = str(Path(str(Path(__file__).parent.resolve().parent.resolve()), "migrations").resolve())


def get_database_url(engine: str = "async") -> URL:
Expand All @@ -27,26 +26,27 @@ def get_database_url(engine: str = "async") -> URL:
"""
if engine == "sync":
SQLALCHEMY_DATABASE_URL = URL.create(
drivername = "postgresql+psycopg2",
username = config.username,
password = config.password,
host = config.dtbsbhost,
port = config.dtbsbport,
database = config.database,
drivername="postgresql+psycopg2",
username=config.username,
password=config.password,
host=config.dtbsbhost,
port=config.dtbsbport,
database=config.database,
)
return SQLALCHEMY_DATABASE_URL

SQLALCHEMY_DATABASE_URL = URL.create(
drivername = config.dtbsdriver,
username = config.username,
password = config.password,
host = config.dtbsbhost,
port = config.dtbsbport,
database = config.database,
drivername=config.dtbsdriver,
username=config.username,
password=config.password,
host=config.dtbsbhost,
port=config.dtbsbport,
database=config.database,
)
return SQLALCHEMY_DATABASE_URL

def get_engine(engine: str = "async") -> Union[Engine, AsyncEngine]:

def get_engine(engine: str = "async") -> Engine | AsyncEngine:
"""
Create a session engine based on the specified engine type.

Expand All @@ -55,14 +55,15 @@ def get_engine(engine: str = "async") -> Union[Engine, AsyncEngine]:
:return: An SQLAlchemy engine instance, either synchronous or asynchronous.
"""
if engine == "sync":
SQLALCHEMY_DATABASE_URL = get_database_url(engine = "sync")
SQLALCHEMY_DATABASE_URL = get_database_url(engine="sync")
sync_engine = create_engine(url=SQLALCHEMY_DATABASE_URL, echo=config.confecho)
return sync_engine

SQLALCHEMY_DATABASE_URL = get_database_url()
async_engine = create_async_engine(url=SQLALCHEMY_DATABASE_URL, echo=config.confecho)
return async_engine


def get_async_session() -> async_sessionmaker:
"""
Create an asynchronous session factory for handling database sessions.
Expand Down
1 change: 1 addition & 0 deletions fastapi_ecom/database/db_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def make_database() -> None:
command.stamp(alembic_config, "head")
success("Database marked at migration head successfully")


async def get_db() -> AsyncGenerator[AsyncSession, None]:
"""
Dependency function to provide a database session for FastAPI routes.
Expand Down
1 change: 1 addition & 0 deletions fastapi_ecom/database/models/business.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Business(baseobjc, UUIDCreatableMixin, DateCreatableMixin, DateUpdateableM
:cvar products: Relationship to the `Product` model, representing the products offered by the
business.
"""

__tablename__ = "businesses"

id = Column("id", Integer, primary_key=True, index=True, autoincrement=True)
Expand Down
1 change: 1 addition & 0 deletions fastapi_ecom/database/models/customer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Customer(baseobjc, UUIDCreatableMixin, DateCreatableMixin, DateUpdateableM
:cvar created_via_oauth: Flag indicating if the customer account is created using OAuth.
:cvar orders: Relationship to the `Order` model, representing orders placed by the customer.
"""

__tablename__ = "customers"

id = Column("id", Integer, primary_key=True, index=True, autoincrement=True)
Expand Down
3 changes: 2 additions & 1 deletion fastapi_ecom/database/models/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ class Order(baseobjc, UUIDCreatableMixin, DateCreatableMixin, DateUpdateableMixi
:cvar customers: Relationship to the `Customer` model, representing the customer who placed the
order. The record will reflect customer deletion (passive deletes).
"""

__tablename__ = "orders"

id = Column("id", Integer, primary_key=True, index=True, autoincrement=True)
user_id = Column("user_id", Text, ForeignKey('customers.uuid', ondelete="CASCADE"), nullable=False)
user_id = Column("user_id", Text, ForeignKey("customers.uuid", ondelete="CASCADE"), nullable=False)
order_date = Column("order_date", Date, nullable=False)
total_price = Column("total_price", Float, nullable=False)

Expand Down
5 changes: 3 additions & 2 deletions fastapi_ecom/database/models/order_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ class OrderDetail(baseobjc, UUIDCreatableMixin, DateCreatableMixin, DateUpdateab
:cvar products: Relationship to the `Product` model, representing the associated product in
the order detail.
"""

__tablename__ = "order_details"

id = Column("id", Integer, primary_key=True, index=True, autoincrement=True)
product_id = Column("product_id", Text, ForeignKey('products.uuid'), nullable=False)
product_id = Column("product_id", Text, ForeignKey("products.uuid"), nullable=False)
quantity = Column("quantity", Integer, nullable=False)
price = Column("product_price", Float, nullable=False)
order_id = Column("order_id", Text, ForeignKey('orders.uuid', ondelete="CASCADE"), nullable=False)
order_id = Column("order_id", Text, ForeignKey("orders.uuid", ondelete="CASCADE"), nullable=False)

orders = relationship("Order", back_populates="order_details", passive_deletes=True)
products = relationship("Product", back_populates="order_details")
3 changes: 2 additions & 1 deletion fastapi_ecom/database/models/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Product(baseobjc, UUIDCreatableMixin, DateCreatableMixin, DateUpdateableMi
:cvar order_details: Relationship to the `OrderDetail` model, representing the details of
orders containing this product.
"""

__tablename__ = "products"

id = Column("id", Integer, primary_key=True, index=True, autoincrement=True)
Expand All @@ -37,7 +38,7 @@ class Product(baseobjc, UUIDCreatableMixin, DateCreatableMixin, DateUpdateableMi
mfg_date = Column("manufacturing_date", Date, nullable=False)
exp_date = Column("expiry_date", Date, nullable=False)
price = Column("product_price", Float, nullable=False)
business_id = Column("business_id", Text, ForeignKey('businesses.uuid', ondelete="CASCADE"), nullable=False)
business_id = Column("business_id", Text, ForeignKey("businesses.uuid", ondelete="CASCADE"), nullable=False)

businesses = relationship("Business", back_populates="products", passive_deletes=True)
order_details = relationship("OrderDetail", back_populates="products")
3 changes: 3 additions & 0 deletions fastapi_ecom/database/models/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class UUIDCreatableMixin:
:cvar uuid: An 8-character hexadecimal string serving as a unique identifier for the record.
This UUID is generated upon creation and remains constant.
"""

uuid = Column("uuid", Text, unique=True, nullable=False, default=uuid4().hex[0:8])


Expand All @@ -23,6 +24,7 @@ class DateCreatableMixin:
:cvar creation_date: Timestamp marking when the record was created.
Automatically set to the current UTC datetime at creation.
"""

creation_date = Column("creation_date", TIMESTAMP(timezone=True), nullable=False, default=partial(datetime.now, tz=UTC))


Expand All @@ -34,4 +36,5 @@ class DateUpdateableMixin:
Automatically set to the current UTC datetime at creation and should be
updated on each modification.
"""

update_date = Column("update_date", TIMESTAMP(timezone=True), nullable=False, default=partial(datetime.now, tz=UTC))
31 changes: 19 additions & 12 deletions fastapi_ecom/database/pydantic_schemas/business.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from datetime import datetime
from typing import Optional

from pydantic import BaseModel, EmailStr

Expand All @@ -10,6 +9,7 @@ class BusinessBase(BaseModel):
"""
Base model for business-related schemas, providing shared configurations.
"""

class Config:
"""
This class enables attribute mapping from database model instances to Pydantic models
Expand All @@ -19,6 +19,7 @@ class Config:
input data directly (i.e., allow assigning to the model attributes
directly without validation).
"""

from_attributes = True


Expand All @@ -33,12 +34,13 @@ class BusinessView(BusinessBase):
:ivar city: City in which the business is located.
:ivar state: State in which the business is located.
"""

email: EmailStr
name: str
addr_line_1: Optional[str]
addr_line_2: Optional[str]
city: Optional[str]
state: Optional[str]
addr_line_1: str | None
addr_line_2: str | None
city: str | None
state: str | None


class BusinessCreate(BusinessView):
Expand All @@ -48,6 +50,7 @@ class BusinessCreate(BusinessView):

:ivar password: Password for the business account.
"""

password: str


Expand All @@ -63,13 +66,14 @@ class BusinessUpdate(BaseModel):
:ivar state: Updated state of the business, optional.
:ivar password: Updated password for the business account, optional.
"""
email: Optional[EmailStr] = ""
name: Optional[str] = ""
addr_line_1: Optional[str] = ""
addr_line_2: Optional[str] = ""
city: Optional[str] = ""
state: Optional[str] = ""
password: Optional[str] = ""

email: EmailStr | None = ""
name: str | None = ""
addr_line_1: str | None = ""
addr_line_2: str | None = ""
city: str | None = ""
state: str | None = ""
password: str | None = ""


class BusinessInternal(BusinessCreate):
Expand All @@ -82,6 +86,7 @@ class BusinessInternal(BusinessCreate):
:ivar is_verified: Indicates whether the business has been verified, defaults to False.
:ivar creation_date: Timestamp of when the business was created.
"""

id: int
uuid: str
is_verified: bool = False
Expand All @@ -94,6 +99,7 @@ class BusinessResult(APIResult):

:ivar business: Contains details of a single business.
"""

business: BusinessView


Expand All @@ -103,4 +109,5 @@ class BusinessManyResult(APIResult):

:ivar businesses: List of businesses with their details.
"""

businesses: list[BusinessView] = []
Loading