diff --git a/.env b/.env new file mode 100644 index 0000000..a80ff41 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +DB_USER=postgres +DB_PASSWORD=12345 +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=mydatabase \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a80ff41 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +DB_USER=postgres +DB_PASSWORD=12345 +DB_HOST=localhost +DB_PORT=5432 +DB_NAME=mydatabase \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1bbf2f4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.9-slim-buster +WORKDIR /app +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY . . +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/README.md b/README.md index 6f852b8..1a5deb9 100644 --- a/README.md +++ b/README.md @@ -7,5 +7,12 @@ $ .\myenv\Scripts\activate ``` Так же установить пакеты: ```shell -$ pip install fastapi uvicorn sqlalchemy databases[sqlite] python-dotenv -``` \ No newline at end of file +$ pip install fastapi uvicorn sqlalchemy asyncpg psycopg2 pydantic python-dotenv +``` +Что бы собрать контейнер и запустить его воспользоваться командами: +```shell +$ docker-compose up --build +$ docker-compose up -d +``` + +P.s. Для запуска необходим установленный на пк Docker. \ No newline at end of file diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..204bfe6 --- /dev/null +++ b/app/config.py @@ -0,0 +1,14 @@ +from pydantic import BaseSettings + +class Settings(BaseSettings): + db_user: str + db_password: str + db_host: str + db_port: str + db_name: str + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + +settings = Settings() \ No newline at end of file diff --git a/app/database.py b/app/database.py new file mode 100644 index 0000000..a62bb97 --- /dev/null +++ b/app/database.py @@ -0,0 +1,12 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.ext.declarative import declarative_base +from app.config import settings + +# Подключение к базе данных +SQLALCHEMY_DATABASE_URL = f"postgresql://{settings.db_user}:{settings.db_password}@{settings.db_host}:{settings.db_port}/{settings.db_name}" + +engine = create_engine(SQLALCHEMY_DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() diff --git a/app/main.py b/app/main.py deleted file mode 100644 index 5596b44..0000000 --- a/app/main.py +++ /dev/null @@ -1,16 +0,0 @@ -# This is a sample Python script. - -# Press Shift+F10 to execute it or replace it with your code. -# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings. - - -def print_hi(name): - # Use a breakpoint in the code line below to debug your script. - print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint. - - -# Press the green button in the gutter to run the script. -if __name__ == '__main__': - print_hi('PyCharm') - -# See PyCharm help at https://www.jetbrains.com/help/pycharm/ diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..d6e5d5c --- /dev/null +++ b/app/models.py @@ -0,0 +1,42 @@ +from typing import List +from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy.orm import relationship +from pydantic import BaseModel +from app.database import Base + +# Модель студента +class Student(Base): + __tablename__ = 'students' + id = Column(Integer, primary_key=True, index=True) + name = Column(String, nullable=False) + group_id = Column(Integer, ForeignKey('groups.id')) + group = relationship('Group', back_populates='students') + +# Модель группы +class Group(Base): + __tablename__ = 'groups' + id = Column(Integer, primary_key=True, index=True) + name = Column(String, nullable=False) + students = relationship('Student', back_populates='group') + +# Модель для создания студента +class StudentCreate(BaseModel): + name: str + group_id: int + +# Модель для создания группы +class GroupCreate(BaseModel): + name: str + +# Модель для ответа с информацией о студенте +class StudentResponse(BaseModel): + id: int + name: str + group_id: int + +# Модель для ответа с информацией о группе +class GroupResponse(BaseModel): + id: int + name: str + students: List[StudentResponse] + diff --git a/app/routes.py b/app/routes.py new file mode 100644 index 0000000..c131143 --- /dev/null +++ b/app/routes.py @@ -0,0 +1,125 @@ +from fastapi import APIRouter, HTTPException, Depends +from sqlalchemy.orm import Session +from app.database import SessionLocal, engine +from app.models import Student, Group, StudentCreate, GroupCreate, StudentResponse, GroupResponse + +router = APIRouter() + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + + +# API эндпоинт для создания студента +@router.post("/students/") +def create_student(student: StudentCreate, db: Session = Depends(get_db)): + db_student = Student(name=student.name, group_id=student.group_id) + db.add(db_student) + db.commit() + db.refresh(db_student) + return {"id": db_student.id, "name": db_student.name, "group_id": db_student.group_id} + +# API эндпоинт для создания группы +@router.post("/groups/") +def create_group(group: GroupCreate, db: Session = Depends(get_db)): + db_group = Group(name=group.name) + db.add(db_group) + db.commit() + db.refresh(db_group) + return {"id": db_group.id, "name": db_group.name} + +# API эндпоинт для получения информации о студенте по его id +@router.get("/students/{student_id}") +def get_student(student_id: int, db: Session = Depends(get_db)): + student = db.query(Student).filter(Student.id == student_id).first() + if not student: + raise HTTPException(status_code=404, detail="Student not found") + return {"id": student.id, "name": student.name, "group_id": student.group_id} + +# API эндпоинт для получения информации о группе по ее id +@router.get("/groups/{group_id}") +def get_group(group_id: int, db: Session = Depends(get_db)): + group = db.query(Group).filter(Group.id == group_id).first() + if not group: + raise HTTPException(status_code=404, detail="Group not found") + return {"id": group.id, "name": group.name} + +# API эндпоинт для удаления студента +@router.delete("/students/{student_id}") +def delete_student(student_id: int, db: Session = Depends(get_db)): + student = db.query(Student).filter(Student.id == student_id).first() + if not student: + raise HTTPException(status_code=404, detail="Student not found") + db.delete(student) + db.commit() + return {"message": "Student deleted"} + +# API эндпоинт для удаления группы +@router.delete("/groups/{group_id}") +def delete_group(group_id: int, db: Session = Depends(get_db)): + group = db.query(Group).filter(Group.id == group_id).first() + if not group: + raise HTTPException(status_code=404, detail="Group not found") + db.delete(group) + db.commit() + return {"message": "Group deleted"} + +# API эндпоинт для получения списка студентов +@router.get("/students/") +def get_students(db: Session = Depends(get_db)): + students = db.query(Student).all() + return students + +# API эндпоинт для получения списка групп +@router.get("/groups/") +def get_groups(db: Session = Depends(get_db)): + groups = db.query(Group).all() + return groups + +# API эндпоинт для добавления студента в группу +@router.post("/groups/{group_id}/students/{student_id}") +def add_student_to_group(group_id: int, student_id: int, db: Session = Depends(get_db)): + group = db.query(Group).filter(Group.id == group_id).first() + student = db.query(Student).filter(Student.id == student_id).first() + if not group: + raise HTTPException(status_code=404, detail="Group not found") + if not student: + raise HTTPException(status_code=404, detail="Student not found") + student.group_id = group_id + db.commit() + return {"message": "Student added to group"} + +# API эндпоинт для удаления студента из группы +@router.delete("/groups/{group_id}/students/{student_id}") +def remove_student_from_group(group_id: int, student_id: int, db: Session = Depends(get_db)): + student = db.query(Student).filter(Student.id == student_id).first() + if not student: + raise HTTPException(status_code=404, detail="Student not found") + student.group_id = None + db.commit() + return {"message": "Student removed from group"} + +# API эндпоинт для получения всех студентов в группе +@router.get("/groups/{group_id}/students/") +def get_students_in_group(group_id: int, db: Session = Depends(get_db)): + group = db.query(Group).filter(Group.id == group_id).first() + if not group: + raise HTTPException(status_code=404, detail="Group not found") + students = group.students + return students + +# API эндпоинт для перевода студента из группы A в группу B +@router.put("/students/{student_id}/move/{group_id}") +def move_student(student_id: int, group_id: int, db: Session = Depends(get_db)): + student = db.query(Student).filter(Student.id == student_id).first() + group = db.query(Group).filter(Group.id == group_id).first() + if not student: + raise HTTPException(status_code=404, detail="Student not found") + if not group: + raise HTTPException(status_code=404, detail="Group not found") + student.group_id = group_id + db.commit() + return {"message": "Student moved to another group"} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c4d3652 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3.8' + +services: + db: + image: postgres + restart: always + environment: + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_NAME} + ports: + - 5432:5432 + + app: + build: + context: . + dockerfile: Dockerfile + restart: always + environment: + - DB_USER=${DB_USER} + - DB_PASSWORD=${DB_PASSWORD} + - DB_HOST=localhost + - DB_PORT=5432 + - DB_NAME=${DB_NAME} + ports: + - 8000:8000 + depends_on: + - db \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..a9f4ab4 --- /dev/null +++ b/main.py @@ -0,0 +1,7 @@ +from fastapi import FastAPI +from app.routes import router + +app = FastAPI() + +app.include_router(router, prefix="/api") + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..93e7c6e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +fastapi +uvicorn +sqlalchemy +asyncpg +psycopg2-binary +pydantic +python-dotenv \ No newline at end of file