Skip to content
Open
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
Empty file added hw2/hw/shop_api/api/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions hw2/hw/shop_api/api/cart/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .contracts import CartItemResponse, CartResponse
from .routes import router

__all__ = [
'CartItemResponse',
'CartResponse',
'router',
]
38 changes: 38 additions & 0 deletions hw2/hw/shop_api/api/cart/contracts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from pydantic import BaseModel

from shop_api import store
from shop_api.store import CartEntity


class CartItemResponse(BaseModel):
id: int
name: str
quantity: int
available: bool


class CartResponse(BaseModel):
id: int
items: list[CartItemResponse]
price: float

@staticmethod
def from_entity(entity: CartEntity) -> 'CartResponse':
items = []
price = 0.0

for item_id, quantity in entity.items.items():
item = store.get_item(item_id)
if item is None:
continue
items.append(
CartItemResponse(
id=item_id,
name=item.info.name,
quantity=quantity,
available=not item.info.deleted,
)
)
price += item.info.price * quantity

return CartResponse(id=entity.id, items=items, price=price)
63 changes: 63 additions & 0 deletions hw2/hw/shop_api/api/cart/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from http import HTTPStatus
from typing import Annotated

from fastapi import APIRouter, HTTPException, Query, Response
from pydantic import NonNegativeFloat, NonNegativeInt, PositiveInt

from shop_api import store

from .contracts import CartResponse

router = APIRouter(prefix='/cart')


@router.post('', status_code=HTTPStatus.CREATED)
async def create_cart(response: Response) -> dict[str, int]:
entity = store.add_cart()
response.headers['location'] = f'/cart/{entity.id}'
return {'id': entity.id}


@router.get('')
async def get_cart_list(
offset: Annotated[NonNegativeInt, Query()] = 0,
limit: Annotated[PositiveInt, Query()] = 10,
min_price: Annotated[NonNegativeFloat | None, Query()] = None,
max_price: Annotated[NonNegativeFloat | None, Query()] = None,
min_quantity: Annotated[NonNegativeInt | None, Query()] = None,
max_quantity: Annotated[NonNegativeInt | None, Query()] = None,
) -> list[CartResponse]:
result = []

for entity in store.get_carts():
cart = CartResponse.from_entity(entity)
quantity = sum(entity.items.values())

if min_price is not None and cart.price < min_price:
continue
if max_price is not None and cart.price > max_price:
continue
if min_quantity is not None and quantity < min_quantity:
continue
if max_quantity is not None and quantity > max_quantity:
continue

result.append(cart)

return result[offset:offset + limit]


@router.get('/{cart_id}')
async def get_cart(cart_id: int) -> CartResponse:
entity = store.get_cart(cart_id)
if entity is None:
raise HTTPException(HTTPStatus.NOT_FOUND, 'cart not found')
return CartResponse.from_entity(entity)


@router.post('/{cart_id}/add/{item_id}')
async def add_to_cart(cart_id: int, item_id: int) -> CartResponse:
entity = store.add_to_cart(cart_id, item_id)
if entity is None:
raise HTTPException(HTTPStatus.NOT_FOUND, 'cart or item not found')
return CartResponse.from_entity(entity)
3 changes: 3 additions & 0 deletions hw2/hw/shop_api/api/chat/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .routes import router

__all__ = ['router']
28 changes: 28 additions & 0 deletions hw2/hw/shop_api/api/chat/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from uuid import uuid4

from fastapi import APIRouter, WebSocket, WebSocketDisconnect

router = APIRouter()

chats: dict[str, list[WebSocket]] = {}


@router.websocket('/chat/{chat_name}')
async def chat(ws: WebSocket, chat_name: str):
await ws.accept()
username = str(uuid4())

if chat_name not in chats:
chats[chat_name] = []
chats[chat_name].append(ws)

try:
while True:
message = await ws.receive_text()
text = f'{username} :: {message}'
for client in chats[chat_name]:
await client.send_text(text)
except WebSocketDisconnect:
chats[chat_name].remove(ws)
if not chats[chat_name]:
del chats[chat_name]
9 changes: 9 additions & 0 deletions hw2/hw/shop_api/api/item/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .contracts import ItemRequest, ItemResponse, PatchItemRequest
from .routes import router

__all__ = [
'ItemRequest',
'ItemResponse',
'PatchItemRequest',
'router',
]
37 changes: 37 additions & 0 deletions hw2/hw/shop_api/api/item/contracts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from pydantic import BaseModel, ConfigDict

from shop_api.store import ItemEntity, ItemInfo, PatchItemInfo


class ItemResponse(BaseModel):
id: int
name: str
price: float
deleted: bool

@staticmethod
def from_entity(entity: ItemEntity) -> 'ItemResponse':
return ItemResponse(
id=entity.id,
name=entity.info.name,
price=entity.info.price,
deleted=entity.info.deleted,
)


class ItemRequest(BaseModel):
name: str
price: float

def as_item_info(self) -> ItemInfo:
return ItemInfo(name=self.name, price=self.price)


class PatchItemRequest(BaseModel):
model_config = ConfigDict(extra='forbid')

name: str | None = None
price: float | None = None

def as_patch_item_info(self) -> PatchItemInfo:
return PatchItemInfo(name=self.name, price=self.price)
59 changes: 59 additions & 0 deletions hw2/hw/shop_api/api/item/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from http import HTTPStatus
from typing import Annotated

from fastapi import APIRouter, HTTPException, Query, Response
from pydantic import NonNegativeFloat, NonNegativeInt, PositiveInt

from shop_api import store

from .contracts import ItemRequest, ItemResponse, PatchItemRequest

router = APIRouter(prefix='/item')


@router.post('', status_code=HTTPStatus.CREATED)
async def create_item(body: ItemRequest) -> ItemResponse:
entity = store.add_item(body.as_item_info())
return ItemResponse.from_entity(entity)


@router.get('')
async def get_item_list(
offset: Annotated[NonNegativeInt, Query()] = 0,
limit: Annotated[PositiveInt, Query()] = 10,
min_price: Annotated[NonNegativeFloat | None, Query()] = None,
max_price: Annotated[NonNegativeFloat | None, Query()] = None,
show_deleted: Annotated[bool, Query()] = False,
) -> list[ItemResponse]:
entities = store.get_items(offset, limit, min_price, max_price, show_deleted)
return [ItemResponse.from_entity(e) for e in entities]


@router.get('/{item_id}')
async def get_item(item_id: int) -> ItemResponse:
entity = store.get_item(item_id)
if entity is None or entity.info.deleted:
raise HTTPException(HTTPStatus.NOT_FOUND, 'item not found')
return ItemResponse.from_entity(entity)


@router.put('/{item_id}')
async def put_item(item_id: int, body: ItemRequest) -> ItemResponse:
entity = store.update_item(item_id, body.name, body.price)
if entity is None:
raise HTTPException(HTTPStatus.NOT_FOUND, 'item not found')
return ItemResponse.from_entity(entity)


@router.patch('/{item_id}', response_model=None)
async def patch_item(item_id: int, body: PatchItemRequest):
entity = store.patch_item(item_id, body.as_patch_item_info())
if entity is None:
return Response(status_code=HTTPStatus.NOT_MODIFIED)
return ItemResponse.from_entity(entity)


@router.delete('/{item_id}')
async def delete_item(item_id: int) -> Response:
store.delete_item(item_id)
return Response(status_code=HTTPStatus.OK)
8 changes: 8 additions & 0 deletions hw2/hw/shop_api/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
from fastapi import FastAPI

from shop_api.api.cart import router as cart_router
from shop_api.api.chat import router as chat_router
from shop_api.api.item import router as item_router

app = FastAPI(title="Shop API")

app.include_router(cart_router)
app.include_router(item_router)
app.include_router(chat_router)
30 changes: 30 additions & 0 deletions hw2/hw/shop_api/store/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from .models import CartEntity, ItemEntity, ItemInfo, PatchItemInfo
from .queries import (
add_cart,
add_item,
add_to_cart,
delete_item,
get_cart,
get_carts,
get_item,
get_items,
patch_item,
update_item,
)

__all__ = [
'CartEntity',
'ItemEntity',
'ItemInfo',
'PatchItemInfo',
'add_cart',
'add_item',
'add_to_cart',
'delete_item',
'get_cart',
'get_carts',
'get_item',
'get_items',
'patch_item',
'update_item',
]
26 changes: 26 additions & 0 deletions hw2/hw/shop_api/store/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from dataclasses import dataclass, field


@dataclass(slots=True)
class ItemInfo:
name: str
price: float
deleted: bool = False


@dataclass(slots=True)
class ItemEntity:
id: int
info: ItemInfo


@dataclass(slots=True)
class PatchItemInfo:
name: str | None = None
price: float | None = None


@dataclass(slots=True)
class CartEntity:
id: int
items: dict[int, int] = field(default_factory=dict)
Loading
Loading