From aeb613cda83071e6b6e47a7706813c18e26013e5 Mon Sep 17 00:00:00 2001 From: DIKER0K Date: Mon, 7 Jul 2025 19:38:22 +0500 Subject: [PATCH] add: database cars, enpoints --- .gitignore | 2 ++ README.md | 42 ++++++++++++++++++++++++++++ cars.db | Bin 0 -> 16384 bytes crud.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ database.py | 19 +++++++++++++ main.py | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++ models.py | 37 +++++++++++++++++++++++++ schemas.py | 67 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 319 insertions(+) create mode 100644 .gitignore create mode 100644 cars.db create mode 100644 crud.py create mode 100644 database.py create mode 100644 main.py create mode 100644 models.py create mode 100644 schemas.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82adb58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +venv diff --git a/README.md b/README.md index e69de29..e624d9a 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,42 @@ +# AutoBro Backend + +API сервер для хранения и управления данными об автомобилях. + +## Структура проекта + +- **main.py** - основной файл с API эндпоинтами +- **models.py** - модели SQLAlchemy для базы данных +- **schemas.py** - схемы Pydantic для валидации данных +- **database.py** - настройка подключения к базе данных +- **crud.py** - функции для CRUD операций + +## Запуск проекта + +1. Установите зависимости: + +```bash +pip install fastapi uvicorn sqlalchemy pydantic +``` + +2. Запустите сервер: + +```bash +uvicorn main:app --reload +``` + +Сервер будет доступен по адресу http://localhost:8000 + +## API эндпоинты + +- **GET /cars** - получить список всех автомобилей +- **GET /cars/{car_id}** - получить информацию о конкретном автомобиле +- **POST /cars** - добавить новый автомобиль +- **PUT /cars/{car_id}** - обновить информацию об автомобиле +- **DELETE /cars/{car_id}** - удалить автомобиль + +## Документация API + +После запуска сервера документация доступна по адресам: + +- Swagger UI: http://localhost:8000/docs +- ReDoc: http://localhost:8000/redoc diff --git a/cars.db b/cars.db new file mode 100644 index 0000000000000000000000000000000000000000..56ff9292956eefa55a1281389bbddb177cfa681c GIT binary patch literal 16384 zcmeI$&uY{_7y$5Ov$Wkp|529WZ9E9OVs_gKq8_wq8@0I2*0@!BkTAQM?v6H@Fqv(e zgHS9U#DfPxy!i?u3N2cFh4d{%@BxG#oTR(7Tg8ibDc_LYNoMBzX6BdE=sT#GUyN*NR zoM>yXwl8?(uvLd~+NA#Rd=bICJ9Co2<#e4mA_{tD_2?Q&7SdT_GLZN(6Opt5Zcq^& zEuqs9unp3axzm0;b>VU`O(k)J+=t^a!MO8VWGkw3>h>(Ea4C&UhdLY!%DS5>OdWPD z*$az?y@GC=E9pBOEGB1jb@+!OFDM`Y0w4eaAOHd&00JNY0w4eaAaFVao{p+x%C+5Np-ehk3pMv!(GM`_1 z6f|cS?<;R!Zz<1fGx4kVccTxORcJ9RQWzz>& z`?h4(>}tiTTBc Optional[Car]: + return db.query(Car).filter(Car.id == car_id).first() + +def get_cars(db: Session, skip: int = 0, limit: int = 100) -> List[Car]: + return db.query(Car).offset(skip).limit(limit).all() + +def get_cars_count(db: Session) -> int: + return db.query(Car).count() + +def create_car(db: Session, car: schemas.CarCreate) -> Car: + # Преобразование из Pydantic enum в SQLAlchemy enum + engine_type = EngineType(car.engine_type.value) + hybrid_type = HybridType(car.hybrid_type.value) + power_ratio = PowerRatio(car.power_ratio.value) + + db_car = Car( + image=car.image, + name=car.name, + price=car.price, + base_price=car.base_price, + country_of_origin=car.country_of_origin, + year=car.year, + drive_type=car.drive_type, + mileage=car.mileage, + engine_capacity=car.engine_capacity, + engine_power=car.engine_power, + engine_type=engine_type, + electric_motor_power=car.electric_motor_power, + hybrid_type=hybrid_type, + power_ratio=power_ratio, + ) + db.add(db_car) + db.commit() + db.refresh(db_car) + return db_car + +def update_car(db: Session, car_id: int, car_update: schemas.CarUpdate) -> Optional[Car]: + db_car = get_car(db, car_id) + if not db_car: + return None + + update_data = car_update.model_dump(exclude_unset=True) + + # Обработка enum полей + if "engine_type" in update_data and update_data["engine_type"] is not None: + update_data["engine_type"] = EngineType(update_data["engine_type"]) + + if "hybrid_type" in update_data and update_data["hybrid_type"] is not None: + update_data["hybrid_type"] = HybridType(update_data["hybrid_type"]) + + if "power_ratio" in update_data and update_data["power_ratio"] is not None: + update_data["power_ratio"] = PowerRatio(update_data["power_ratio"]) + + # Обновление полей + for key, value in update_data.items(): + setattr(db_car, key, value) + + db.commit() + db.refresh(db_car) + return db_car + +def delete_car(db: Session, car_id: int) -> bool: + db_car = get_car(db, car_id) + if not db_car: + return False + + db.delete(db_car) + db.commit() + return True diff --git a/database.py b/database.py new file mode 100644 index 0000000..bd265e2 --- /dev/null +++ b/database.py @@ -0,0 +1,19 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + +SQLALCHEMY_DATABASE_URL = "sqlite:///./cars.db" + +engine = create_engine( + SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} +) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/main.py b/main.py new file mode 100644 index 0000000..6e4d5f9 --- /dev/null +++ b/main.py @@ -0,0 +1,78 @@ +from fastapi import FastAPI, Depends, HTTPException, status, Query, Path +from sqlalchemy.orm import Session +import crud +import models +import schemas +from database import engine, get_db +from typing import List, Optional +import uvicorn + +# Создание таблиц в БД +models.Base.metadata.create_all(bind=engine) + +app = FastAPI(title="AutoBro API", description="API для управления базой данных автомобилей") + +@app.get("/") +def read_root(): + return {"message": "AutoBro API"} + +@app.get("/cars", response_model=schemas.CarsResponse) +def get_cars( + skip: int = Query(0, description="Количество пропускаемых записей"), + limit: int = Query(100, description="Максимальное количество записей"), + db: Session = Depends(get_db) +): + cars = crud.get_cars(db, skip=skip, limit=limit) + total = crud.get_cars_count(db) + return {"cars": cars, "total": total} + +@app.get("/cars/{car_id}", response_model=schemas.CarResponse) +def get_car( + car_id: int = Path(..., description="ID автомобиля", gt=0), + db: Session = Depends(get_db) +): + car = crud.get_car(db, car_id=car_id) + if car is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Автомобиль не найден" + ) + return {"car": car} + +@app.post("/cars", response_model=schemas.CarResponse, status_code=status.HTTP_201_CREATED) +def create_car( + car: schemas.CarCreate, + db: Session = Depends(get_db) +): + db_car = crud.create_car(db=db, car=car) + return {"car": db_car} + +@app.put("/cars/{car_id}", response_model=schemas.CarResponse) +def update_car( + car_id: int = Path(..., description="ID автомобиля", gt=0), + car_update: schemas.CarUpdate = ..., + db: Session = Depends(get_db) +): + updated_car = crud.update_car(db=db, car_id=car_id, car_update=car_update) + if updated_car is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Автомобиль не найден" + ) + return {"car": updated_car} + +@app.delete("/cars/{car_id}", status_code=status.HTTP_204_NO_CONTENT) +def delete_car( + car_id: int = Path(..., description="ID автомобиля", gt=0), + db: Session = Depends(get_db) +): + success = crud.delete_car(db=db, car_id=car_id) + if not success: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Автомобиль не найден" + ) + return None + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=3000) diff --git a/models.py b/models.py new file mode 100644 index 0000000..bf2d032 --- /dev/null +++ b/models.py @@ -0,0 +1,37 @@ +from sqlalchemy import Boolean, Column, Float, Integer, String, Enum +import enum +from database import Base + +class EngineType(enum.Enum): + PETROL = "бензиновый" + DIESEL = "дизельный" + ELECTRIC = "электрический" + +class HybridType(enum.Enum): + NONE = "не гибрид" + HYBRID = "электрогибрид" + PHEV = "электрогибрид(PHEV)" + +class PowerRatio(enum.Enum): + ICE_GREATER = "ДВС > ЭД" + ELECTRIC_GREATER = "ЭД > ДВС" + NA = "не применимо" + +class Car(Base): + __tablename__ = "cars" + + id = Column(Integer, primary_key=True, index=True) + image = Column(String, nullable=True) # путь к изображению или URL + name = Column(String, index=True) + price = Column(Float) + base_price = Column(Float) # цена без калькулирования + country_of_origin = Column(String) + year = Column(Integer) + drive_type = Column(String) # привод + mileage = Column(Integer, nullable=True) # пробег + engine_capacity = Column(Float, nullable=True) # объем двигателя + engine_power = Column(Integer, nullable=True) # мощность ДВС + engine_type = Column(Enum(EngineType)) # тип двигателя + electric_motor_power = Column(Integer, nullable=True) # мощность электродвигателя + hybrid_type = Column(Enum(HybridType), default=HybridType.NONE) # тип гибрида + power_ratio = Column(Enum(PowerRatio), default=PowerRatio.NA) # соотношение мощности diff --git a/schemas.py b/schemas.py new file mode 100644 index 0000000..782cfc4 --- /dev/null +++ b/schemas.py @@ -0,0 +1,67 @@ +from pydantic import BaseModel, Field +from typing import Optional, List +from enum import Enum +from models import EngineType, HybridType, PowerRatio + +class EngineTypeEnum(str, Enum): + PETROL = "бензиновый" + DIESEL = "дизельный" + ELECTRIC = "электрический" + +class HybridTypeEnum(str, Enum): + NONE = "не гибрид" + HYBRID = "электрогибрид" + PHEV = "электрогибрид(PHEV)" + +class PowerRatioEnum(str, Enum): + ICE_GREATER = "ДВС > ЭД" + ELECTRIC_GREATER = "ЭД > ДВС" + NA = "не применимо" + +class CarBase(BaseModel): + image: Optional[str] = None + name: str + price: float + base_price: float + country_of_origin: str + year: int + drive_type: str + mileage: Optional[int] = None + engine_capacity: Optional[float] = None + engine_power: Optional[int] = None + engine_type: EngineTypeEnum + electric_motor_power: Optional[int] = None + hybrid_type: HybridTypeEnum = HybridTypeEnum.NONE + power_ratio: PowerRatioEnum = PowerRatioEnum.NA + +class CarCreate(CarBase): + pass + +class CarUpdate(BaseModel): + image: Optional[str] = None + name: Optional[str] = None + price: Optional[float] = None + base_price: Optional[float] = None + country_of_origin: Optional[str] = None + year: Optional[int] = None + drive_type: Optional[str] = None + mileage: Optional[int] = None + engine_capacity: Optional[float] = None + engine_power: Optional[int] = None + engine_type: Optional[EngineTypeEnum] = None + electric_motor_power: Optional[int] = None + hybrid_type: Optional[HybridTypeEnum] = None + power_ratio: Optional[PowerRatioEnum] = None + +class Car(CarBase): + id: int + + class Config: + from_attributes = True + +class CarResponse(BaseModel): + car: Car + +class CarsResponse(BaseModel): + cars: List[Car] + total: int