add: database cars, enpoints
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
__pycache__
|
||||||
|
venv
|
42
README.md
42
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
|
||||||
|
74
crud.py
Normal file
74
crud.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from models import Car, EngineType, HybridType, PowerRatio
|
||||||
|
import schemas
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
def get_car(db: Session, car_id: int) -> 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
|
19
database.py
Normal file
19
database.py
Normal file
@ -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()
|
78
main.py
Normal file
78
main.py
Normal file
@ -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)
|
37
models.py
Normal file
37
models.py
Normal file
@ -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) # соотношение мощности
|
67
schemas.py
Normal file
67
schemas.py
Normal file
@ -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
|
Reference in New Issue
Block a user