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
11 changes: 11 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#настройки базы данных
DB_HOST=db
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=shefler_students_db

# настройки приложения
APP_NAME=SheflerStudentsAPI
APP_VERSION=1.0.0
DEBUG=True
37 changes: 37 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
env.bak/
venv.bak/

# среда разарботки
.vscode/
.idea/
*.swp
*.swo

#среда
.env
.env.local

# docker
*.log

# база данных
*.db
*.sqlite

# тестирование
.pytest_cache/
.coverage
htmlcov/

#OS
.DS_Store
Thumbs.db
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM python:3.11-slim
WORKDIR /app

#установка системных зависимостей
RUN apt-get update && apt-get install -y gcc && rm -rf /var/lib/apt/lists/*

# копирование и установка Python зависимостей
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

#копирование кода приложения
COPY ./app /app

# запуск приложения
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
Empty file added app/__init__.py
Empty file.
Empty file added app/api/__init__.py
Empty file.
Empty file added app/api/dependencies.py
Empty file.
2 changes: 2 additions & 0 deletions app/api/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from api.routes.student import router as students
from api.routes.group import router as groups
76 changes: 76 additions & 0 deletions app/api/routes/group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from fastapi import APIRouter, Depends, Query, status
from sqlmodel import Session
from typing import List

from database import get_session
from services.group import GroupService
from schemas.group import GroupCreate, GroupUpdate, GroupResponse, GroupWithStudents
from schemas.common import MessageResponse

router = APIRouter(prefix="/groups", tags=["Groups"])

@router.post("/", response_model=GroupResponse, status_code=status.HTTP_201_CREATED)
def sozdat_gruppu(
group_data: GroupCreate,
session: Session = Depends(get_session)
):
service = GroupService(session)
return service.sozdat_gruppu(group_data)


@router.get("/{group_id}", response_model=GroupWithStudents)
def poluchit_gruppu_po_id(
group_id: int,
session: Session = Depends(get_session)
):
service = GroupService(session)
group = service.poluchit_gruppu_so_studentami(group_id)

response = GroupWithStudents.model_validate(group)
response.students_count = len(group.students)

return response


@router.get("/", response_model=List[GroupResponse])
def poluchit_vse_gruppy(
skip: int = Query(0, ge=0, description="Количество пропускаемых с начала записей"),
limit: int = Query(100, ge=1, le=100, description="Максимальное количество записей"),
session: Session = Depends(get_session)
):
service = GroupService(session)
gruppy = service.poluchit_vse_gruppy(skip, limit)

result = []
for group in gruppy:
group_dict = group.model_dump()
group_dict['students_count'] = len(group.students)
result.append(GroupResponse(**group_dict))

return result


@router.put("/{group_id}", response_model=GroupResponse)
def obnovit_gruppu(
group_id: int,
group_data: GroupUpdate,
session: Session = Depends(get_session)
):
service = GroupService(session)
group = service.obnovit_gruppu(group_id, group_data)
response = GroupResponse.model_validate(group)
response.students_count = len(group.students)

return response


@router.delete("/{group_id}", response_model=MessageResponse)
def udalit_gruppu(
group_id: int,
session: Session = Depends(get_session)
):
service = GroupService(session)
group = service.poluchit_gruppu(group_id)
group_name = group.name
service.udalit_gruppu(group_id)
return MessageResponse(message=f"Группа {group_name} успешно удалена")
115 changes: 115 additions & 0 deletions app/api/routes/student.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from fastapi import APIRouter, Depends, Query, status
from sqlmodel import Session
from typing import List

from database import get_session
from services.student import StudentService
from schemas.student import StudentCreate, StudentUpdate, StudentResponse, StudentWithGroup
from schemas.common import MessageResponse

router = APIRouter(prefix="/students", tags=["Students"])

@router.post("/", response_model=StudentResponse, status_code=status.HTTP_201_CREATED)
def sozdat_studenta(
student_data: StudentCreate,
session: Session = Depends(get_session)
):
#эндпоинт создания нового студента
service = StudentService(session)
return service.sozdat_studenta(student_data)

@router.get("/{student_id}", response_model=StudentWithGroup)
def poluchit_studenta(
student_id: int,
session: Session = Depends(get_session)
):
#эндпоинт получения студента по id
service = StudentService(session)
student = service.poluchit_studenta(student_id)

#добавим название группы в ответ
response = StudentWithGroup.model_validate(student)
if student.group:
response.group_name = student.group.name

return response

@router.get("/", response_model=List[StudentResponse])
def poluchit_vseh_studentov(
skip: int = Query(0, ge=0, description="Количество пропускаемых от начала спика записей"),
limit: int = Query(100, ge=1, le=100, description="Максимальное количество записей"),
session: Session = Depends(get_session)
):
#эндпоинт получения всех студентов
service = StudentService(session)
return service.poluchit_vseh_studentov(skip, limit)

@router.put("/{student_id}", response_model=StudentResponse)
def obnovit_studenta(
student_id: int,
student_data: StudentUpdate,
session: Session = Depends(get_session)
):
# эндпоинт обновления данных студента
service = StudentService(session)
return service.obnovit_studenta(student_id, student_data)

@router.delete("/{student_id}", response_model=MessageResponse)
def udalit_studenta(
student_id: int,
session: Session = Depends(get_session)
):
service = StudentService(session)
student = service.poluchit_studenta(student_id)
name = f"{student.last_name} {student.first_name}"
if student.middle_name:
name += f" {student.middle_name}"

service.udalit_studenta(student_id)
return MessageResponse(message=f"Студент {name} успешно удалён", success=True)

@router.post("/{student_id}/groups/{group_id}", response_model=MessageResponse)
def dobavit_studenta_v_gruppu(
student_id: int,
group_id: int,
session: Session = Depends(get_session)
):
service = StudentService(session)
service.dobavit_studenta_v_gruppu(student_id, group_id)
student = service.poluchit_studenta(student_id)
name = f"{student.last_name} {student.first_name}"
if student.middle_name:
name += f" {student.middle_name}"

return MessageResponse(message=f"Студент {name} успешно добавлен в группу", success=True)

@router.delete("/{student_id}/groups", response_model=MessageResponse)
def udalit_studenta_iz_gruppy(
student_id: int,
session: Session = Depends(get_session)
):
service = StudentService(session)
student = service.poluchit_studenta(student_id)
name = f"{student.last_name} {student.first_name}"
if student.middle_name:
name += f" {student.middle_name}"
service.udalit_studenta_iz_gruppy(student_id)

return MessageResponse(message=f"Студент {name} успешно удален из группы", success=True)

@router.post("/{student_id}/transfer", response_model=StudentResponse)
def perevesti_studenta(
student_id: int,
to_group_id: int = Query(..., description="Идентификатор целевой группы"),
session: Session = Depends(get_session)
):
service = StudentService(session)
return service.perevesti_studenta(student_id, to_group_id)

@router.get("/groups/{group_id}/students", response_model=List[StudentResponse])
def poluchit_studentov_v_gruppe(
group_id: int,
session: Session = Depends(get_session)
):
service = StudentService(session)
return service.poluchit_studentov_gruppy(group_id)
27 changes: 27 additions & 0 deletions app/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pydantic_settings import BaseSettings
from typing import Optional


class Settings(BaseSettings):
#настройки подключения к базе данных
DB_HOST: str = "db"
DB_PORT: int = 5432
DB_USER: str = "postgres"
DB_PASSWORD: str = "postgres"
DB_NAME: str = "shefler_students_db" #моя база данных

# настройки приложения
APP_NAME: str = "SheflerStudentsAPI"
APP_VERSION: str = "1.0.0"
DEBUG: bool = False

@property
def DATABASE_URL(self) -> str:
return f"postgresql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"

class Config:
env_file = ".env"
case_sensitive = True


settings = Settings()
Empty file added app/core/__init__.py
Empty file.
53 changes: 53 additions & 0 deletions app/core/exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from fastapi import HTTPException, status

class StudentNotFoundError(HTTPException):
#Ошибка: студент не найден
def __init__(self, student_id: int):
super().__init__(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Студент с id {student_id} не найден"
)

class GroupNotFoundError(HTTPException):
#Ошибка: группа не найдена
def __init__(self, group_id: int):
super().__init__(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Группа с id {group_id} не найдена"
)

class StudentAlreadyInGroupError(HTTPException):
#Ошибка: студент уже состоит в группе
def __init__(self, student_id: int, group_id: int):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Студент уже состоит в группе, вы можете перевести его в другую"
)

class StudentNotInGroupError(HTTPException):
#Ошибка: студент не состоит в группе
def __init__(self, student_id: int, group_id: int = 0, is_transfer: bool = False):
if is_transfer:
detail = "Студент пока не состоит ни в одной группе, сначала добавьте его в группу"
else:
detail = "Данный студент не состоит в группе"
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail=detail
)

class EmailAlreadyExistsError(HTTPException):
#Ошибка: email уже используется другим студентом
def __init__(self, email: str):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Студент с email {email} уже существует"
)

class GroupNameAlreadyExistsError(HTTPException):
#Ошибка: название группы уже существует
def __init__(self, name: str):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Группа с названием {name} уже существует"
)
20 changes: 20 additions & 0 deletions app/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from sqlmodel import SQLModel, create_engine, Session
from config import settings
from typing import Generator

#движокдля подключения к базе данных
engine = create_engine(
settings.DATABASE_URL,
echo=settings.DEBUG, #если режим отладки, то выводим запросы в консоль
pool_size=15, #открываем 15 соединений с бд
max_overflow=15 # при нехватке можем добавить еще 15 соединений
)

def create_db_and_tables():
#создаем таблицы в базе данных
SQLModel.metadata.create_all(engine)

def get_session() -> Generator[Session, None, None]:
#получаем сессию для работы с базой данных
with Session(engine) as session:
yield session
Loading