Skip to content

Commit a0da114

Browse files
authored
Merge pull request #69 from zezOtik/zemliakov/lab7
Lab 7
2 parents 8b21277 + 6f58e47 commit a0da114

File tree

15 files changed

+374
-0
lines changed

15 files changed

+374
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM python:3.11-slim
2+
3+
WORKDIR /app
4+
5+
COPY requirements.txt .
6+
RUN pip install --no-cache-dir -r requirements.txt
7+
8+
COPY . .
9+
10+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

students_folder/zemliakov_alexey/lab7/app/__init__.py

Whitespace-only changes.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from fastapi import FastAPI
2+
3+
from app.router_room import router as room_router
4+
5+
from app.router_booking import router as booking_router
6+
7+
from app.operations.database_migrations import create_table, delete_table
8+
9+
from contextlib import asynccontextmanager
10+
11+
12+
@asynccontextmanager
13+
async def lifespan(app: FastAPI):
14+
await create_table()
15+
print("Таблица готова")
16+
yield
17+
await delete_table()
18+
print("Таблица очищена")
19+
20+
21+
app = FastAPI(lifespan=lifespan)
22+
23+
app.include_router(room_router)
24+
app.include_router(booking_router)

students_folder/zemliakov_alexey/lab7/app/models/__init__.py

Whitespace-only changes.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
2+
from sqlalchemy import DateTime, BIGINT, ForeignKey
3+
from datetime import datetime
4+
from zoneinfo import ZoneInfo
5+
6+
7+
def utc_now() -> datetime:
8+
return datetime.now(ZoneInfo("UTC"))
9+
10+
11+
class TableModel(DeclarativeBase):
12+
pass
13+
14+
15+
class RoomORM(TableModel):
16+
__tablename__ = "room"
17+
__table_args__ = {"schema": "public"}
18+
19+
id: Mapped[int] = mapped_column(BIGINT, primary_key=True, autoincrement=True)
20+
name: Mapped[str]
21+
capacity: Mapped[int]
22+
location: Mapped[str]
23+
24+
25+
class BookingORM(TableModel):
26+
__tablename__ = "booking"
27+
__table_args__ = {"schema": "public"}
28+
id: Mapped[int] = mapped_column(BIGINT, primary_key=True, autoincrement=True)
29+
room_id: Mapped[int] = mapped_column(BIGINT, ForeignKey("public.room.id"))
30+
user_name: Mapped[str]
31+
start_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
32+
end_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from pydantic import BaseModel, AfterValidator, Field, ConfigDict, RootModel, model_validator
2+
from typing_extensions import Self
3+
from typing import Annotated, Literal, List
4+
from datetime import datetime
5+
from zoneinfo import ZoneInfo
6+
7+
8+
# 2025-04-05T12:00:00Z
9+
def validate_aware(dt: datetime) -> datetime:
10+
if dt.tzinfo is None:
11+
raise ValueError("datetime must include timezone information")
12+
return dt
13+
14+
15+
AwareDatetime = Annotated[datetime, AfterValidator(validate_aware)]
16+
17+
18+
def default_execution_time():
19+
return datetime.now(ZoneInfo("UTC"))
20+
21+
22+
class Room(BaseModel):
23+
name: str
24+
capacity: int
25+
location: str
26+
27+
28+
class RoomAdd(Room):
29+
pass
30+
31+
32+
class RoomGet(Room):
33+
id: int
34+
35+
model_config = ConfigDict(from_attributes=True)
36+
37+
38+
class RoomListGet(RootModel[List[RoomGet]]):
39+
model_config = ConfigDict(from_attributes=True)
40+
41+
42+
class RoomFreeGet(BaseModel):
43+
id: int
44+
start_time: AwareDatetime = Field(default_factory=default_execution_time)
45+
end_time: AwareDatetime = Field(default_factory=default_execution_time)
46+
47+
model_config = ConfigDict(from_attributes=True)
48+
49+
50+
class Booking(BaseModel):
51+
room_id: int
52+
user_name: str
53+
start_time: AwareDatetime = Field(default_factory=default_execution_time)
54+
end_time: AwareDatetime = Field(default_factory=default_execution_time)
55+
56+
@model_validator(mode="after")
57+
def check_date(self) -> Self:
58+
if self.start_time >= self.end_time:
59+
raise ValueError("start date must be before end")
60+
return self
61+
62+
63+
class BookingReserve(Booking):
64+
pass
65+
66+
67+
class DeleteBookingReserve(BaseModel):
68+
booking_id: int
69+
user_name: str
70+
71+
model_config = ConfigDict(from_attributes=True)
72+
73+
74+
class UserBookingGet(BaseModel):
75+
user_name: str
76+
77+
model_config = ConfigDict(from_attributes=True)
78+
79+
80+
class UserBookingGetRes(Booking):
81+
id: int
82+
83+
model_config = ConfigDict(from_attributes=True)

students_folder/zemliakov_alexey/lab7/app/operations/__init__.py

Whitespace-only changes.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
2+
from app.models.database_models import TableModel
3+
4+
engine = create_async_engine("postgresql+asyncpg://postgres:postgres@postgres_container:5432/postgres")
5+
6+
7+
async def create_table():
8+
async with engine.begin() as conn:
9+
await conn.run_sync(TableModel.metadata.create_all)
10+
11+
12+
async def delete_table():
13+
async with engine.begin() as conn:
14+
await conn.run_sync(TableModel.metadata.drop_all)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from app.models.database_models import RoomORM, BookingORM
2+
from app.models.store_models import RoomAdd, RoomFreeGet, RoomListGet, BookingReserve, DeleteBookingReserve, \
3+
UserBookingGet, UserBookingGetRes
4+
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
5+
from sqlalchemy import select, delete
6+
from typing import List
7+
8+
engine = create_async_engine("postgresql+asyncpg://postgres:postgres@postgres_container:5432/postgres")
9+
new_session = async_sessionmaker(engine, expire_on_commit=False)
10+
11+
12+
class RoomWorkflow:
13+
@classmethod
14+
async def add_room(cls, room: RoomAdd) -> int:
15+
async with new_session() as session:
16+
data = room.model_dump()
17+
new_room = RoomORM(**data)
18+
session.add(new_room)
19+
await session.flush()
20+
await session.commit()
21+
return new_room.id
22+
23+
@classmethod
24+
async def get_rooms(cls) -> RoomListGet:
25+
async with new_session() as session:
26+
query = select(RoomORM)
27+
result = await session.execute(query)
28+
room_models = result.scalars().all()
29+
30+
rooms = RoomListGet.model_validate(room_models)
31+
return rooms.root
32+
33+
34+
class BookingWorkflow:
35+
@classmethod
36+
async def reserve_room(cls, booking: BookingReserve) -> int:
37+
async with new_session() as session:
38+
data = booking.model_dump()
39+
if not await cls.check_available(RoomFreeGet(id=booking.room_id, start_time=booking.start_time,
40+
end_time=booking.end_time)):
41+
await session.commit()
42+
raise KeyError
43+
new_booking = BookingORM(**data)
44+
session.add(new_booking)
45+
await session.flush()
46+
await session.commit()
47+
return new_booking.id
48+
49+
@classmethod
50+
async def delete_reserve_room(cls, booking: DeleteBookingReserve) -> str:
51+
async with new_session() as session:
52+
data = booking.model_dump()
53+
query = delete(BookingORM).where(
54+
BookingORM.id == data["booking_id"] and BookingORM.user_name == data["user_name"])
55+
results = await session.execute(query)
56+
await session.commit()
57+
if results.rowcount() > 0:
58+
return f"success deleted {results.rowcount()} rows"
59+
60+
return "nothing for delete"
61+
62+
@classmethod
63+
async def get_users_booking(cls, user: UserBookingGet) -> List[UserBookingGetRes]:
64+
async with new_session() as session:
65+
data = user.model_dump()
66+
query = select(BookingORM).where(BookingORM.user_name == data["user_name"])
67+
result = await session.execute(query)
68+
await session.commit()
69+
res = []
70+
for row in result.scalars().all():
71+
res.append(UserBookingGetRes(id=row.id, room_id=row.room_id, user_name=row.user_name,
72+
start_time=row.start_time, end_time=row.end_time))
73+
74+
return res
75+
76+
@classmethod
77+
async def check_available(cls, free_room: RoomFreeGet) -> bool:
78+
async with new_session() as session:
79+
data = free_room.model_dump()
80+
query = select(BookingORM).where(BookingORM.room_id == data["id"])
81+
result = await session.execute(query)
82+
room_models = result.scalars().all()
83+
for row in room_models:
84+
if check_intersection_time(data["start_time"], data["end_time"], row.start_time, row.end_time):
85+
return False
86+
await session.commit()
87+
return True
88+
89+
90+
def check_intersection_time(t1_start, t1_end, t2_start, t2_end) -> bool:
91+
return (t1_start <= t2_start < t1_end) or (t2_start <= t1_start < t2_end)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from fastapi import APIRouter, Depends
2+
3+
from app.operations.database_operations import BookingWorkflow
4+
from app.models.store_models import BookingReserve, DeleteBookingReserve, UserBookingGet, UserBookingGetRes, RoomFreeGet
5+
6+
from typing import List
7+
8+
router = APIRouter(
9+
prefix="/booking",
10+
tags=["бронирования"],
11+
)
12+
13+
14+
@router.post(
15+
"/add_booking",
16+
summary="Добавление бронирования")
17+
async def add_booking(booking: BookingReserve = Depends()) -> dict:
18+
try:
19+
id = await BookingWorkflow.reserve_room(booking)
20+
except ValueError:
21+
return {"error": "unprocessable entity"}
22+
except KeyError:
23+
return {"error": "this room reserved for this time"}
24+
return {"id": id}
25+
26+
27+
@router.get("/get_users_booking",
28+
summary="Получение бронирований пользователя")
29+
async def get_rooms(user: UserBookingGet = Depends()) -> List[UserBookingGetRes]:
30+
rooms = await BookingWorkflow.get_users_booking(user)
31+
return rooms
32+
33+
34+
@router.delete("/delete_booking",
35+
summary="Удаление бронирования пользователя")
36+
async def get_rooms(user: DeleteBookingReserve = Depends()) -> str:
37+
res = await BookingWorkflow.delete_reserve_room(user)
38+
return res
39+
40+
41+
@router.get("/check_room",
42+
summary="Проверка доступности бронирования комнаты на время")
43+
async def get_rooms(room: RoomFreeGet = Depends()) -> bool:
44+
res = await BookingWorkflow.check_available(room)
45+
return res

0 commit comments

Comments
 (0)