test
This commit is contained in:
parent
043386268b
commit
e3860e2490
|
@ -2,27 +2,14 @@ import os
|
||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
# Load .env file from the backend directory (one level up from core)
|
|
||||||
# BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
# load_dotenv(os.path.join(BASE_DIR, '..', '.env')) # Adjust path if needed
|
|
||||||
|
|
||||||
# Or simply let pydantic-settings handle it by default if .env is in the root execution dir
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
DATABASE_URL: str = "sqlite:///./default.db" # Default value if not in .env
|
DATABASE_URL: str = "sqlite:///./default.db"
|
||||||
# Настройки JWT удалены
|
|
||||||
# SECRET_KEY: str = "default_secret"
|
|
||||||
# ALGORITHM: str = "HS256"
|
|
||||||
# ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
env_file = ".env"
|
env_file = ".env"
|
||||||
env_file_encoding = 'utf-8'
|
env_file_encoding = 'utf-8'
|
||||||
# If your .env file is not in the root directory where you run uvicorn,
|
|
||||||
# you might need to specify the path explicitly:
|
|
||||||
# env_file = '../.env' # Example if running from inside 'app' dir
|
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
|
@ -2,17 +2,14 @@ from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
|
|
||||||
# Create SQLAlchemy engine
|
|
||||||
engine = create_engine(
|
engine = create_engine(
|
||||||
settings.DATABASE_URL,
|
settings.DATABASE_URL,
|
||||||
# Required for SQLite to prevent issues with FastAPI's async nature
|
|
||||||
connect_args={"check_same_thread": False} if "sqlite" in settings.DATABASE_URL else {}
|
connect_args={"check_same_thread": False} if "sqlite" in settings.DATABASE_URL else {}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a session factory
|
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
# Dependency to get DB session (we'll use this in routers)
|
|
||||||
def get_db():
|
def get_db():
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -2,7 +2,7 @@ import asyncio
|
||||||
import random
|
import random
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
|
|
||||||
# Делаем функцию асинхронной
|
|
||||||
async def solve_cutting_problem(input_params: Dict[str, Any]) -> Dict[str, Any]:
|
async def solve_cutting_problem(input_params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Асинхронная заглушка для имитации сложного расчета раскройки стекла.
|
Асинхронная заглушка для имитации сложного расчета раскройки стекла.
|
||||||
|
@ -10,26 +10,23 @@ async def solve_cutting_problem(input_params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
print(f"Received input for async fuzzy solver: {input_params}")
|
print(f"Received input for async fuzzy solver: {input_params}")
|
||||||
|
|
||||||
# Имитация времени расчета с использованием asyncio.sleep
|
|
||||||
delay = random.uniform(0.5, 2.0)
|
delay = random.uniform(0.5, 2.0)
|
||||||
print(f"Simulating calculation for {delay:.2f} seconds...")
|
print(f"Simulating calculation for {delay:.2f} seconds...")
|
||||||
await asyncio.sleep(delay) # Используем await asyncio.sleep вместо time.sleep
|
await asyncio.sleep(delay)
|
||||||
|
|
||||||
# Пример выходных данных (остается таким же)
|
|
||||||
output_data = {
|
output_data = {
|
||||||
"layout": [
|
"layout": [
|
||||||
{"piece_id": 1, "x": 10, "y": 10, "width": 500, "height": 300},
|
{"piece_id": 1, "x": 10, "y": 10, "width": 500, "height": 300},
|
||||||
{"piece_id": 2, "x": 520, "y": 10, "width": 400, "height": 300},
|
{"piece_id": 2, "x": 520, "y": 10, "width": 400, "height": 300},
|
||||||
# ... другие детали раскроя ...
|
|
||||||
],
|
],
|
||||||
"waste_percentage": round(random.uniform(5.0, 15.0), 2),
|
"waste_percentage": round(random.uniform(5.0, 15.0), 2),
|
||||||
"number_of_cuts": random.randint(5, 20),
|
"number_of_cuts": random.randint(5, 20),
|
||||||
"processing_time_ms": int(delay * 1000) # Используем задержку как время обработки
|
"processing_time_ms": int(delay * 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Можно добавить логику на основе input_params, если нужно для тестов
|
|
||||||
if input_params.get("optimize_for") == "speed":
|
if input_params.get("optimize_for") == "speed":
|
||||||
output_data["number_of_cuts"] = random.randint(15, 25) # Больше резов - быстрее?
|
output_data["number_of_cuts"] = random.randint(15, 25)
|
||||||
|
|
||||||
print(f"Async fuzzy solver generated output: {output_data}")
|
print(f"Async fuzzy solver generated output: {output_data}")
|
||||||
return output_data
|
return output_data
|
|
@ -1,37 +1,25 @@
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware # Импортируем middleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from app.routers import calculation # Импортируем роутер
|
from app.routers import calculation
|
||||||
|
|
||||||
app = FastAPI(title="Glass Cutting Optimization API")
|
app = FastAPI(title="Glass Cutting Optimization API")
|
||||||
|
|
||||||
# --- Настройка CORS ---
|
|
||||||
# Список разрешенных источников (origins)
|
|
||||||
# Для разработки можно разрешить все или указать адрес вашего React-приложения
|
|
||||||
origins = [
|
origins = [
|
||||||
"http://localhost", # Если React запускается на http://localhost:port
|
"http://localhost",
|
||||||
"http://localhost:3000", # Стандартный порт для create-react-app
|
"http://localhost:3000",
|
||||||
"http://localhost:5173", # Стандартный порт для Vite/React
|
"http://localhost:5173",
|
||||||
# "http://127.0.0.1:5173", # Можно добавить и IP-адрес
|
|
||||||
# "*" # Разрешить все источники (будьте осторожны в production)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=origins, # Указываем разрешенные источники
|
allow_origins=origins,
|
||||||
allow_credentials=True, # Разрешаем куки (если потребуются в будущем)
|
allow_credentials=True,
|
||||||
allow_methods=["*"], # Разрешаем все методы (GET, POST, PUT, etc.)
|
allow_methods=["*"],
|
||||||
allow_headers=["*"], # Разрешаем все заголовки
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
# --- Конец настройки CORS ---
|
|
||||||
|
|
||||||
|
|
||||||
# Подключаем роутер расчетов
|
|
||||||
app.include_router(calculation.router)
|
app.include_router(calculation.router)
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def read_root():
|
async def read_root():
|
||||||
return {"message": "Welcome to the Glass Cutting Optimization API"}
|
return {"message": "Welcome to the Glass Cutting Optimization API"}
|
||||||
|
|
||||||
# Убедимся, что здесь нет подключения auth роутера
|
|
||||||
# from .routers import calculation # Позже подключим calculation
|
|
||||||
# app.include_router(calculation.router)
|
|
|
@ -2,7 +2,5 @@ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||||
from sqlalchemy import Integer
|
from sqlalchemy import Integer
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
# Define a base class using SQLAlchemy 2.0 style
|
|
||||||
class Base(DeclarativeBase):
|
class Base(DeclarativeBase):
|
||||||
# Define a primary key type annotation for convenience
|
|
||||||
pass
|
pass
|
|
@ -8,7 +8,6 @@ from typing import Dict, Any
|
||||||
class Calculation(Base):
|
class Calculation(Base):
|
||||||
__tablename__ = "calculations"
|
__tablename__ = "calculations"
|
||||||
|
|
||||||
#
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, index=True)
|
id: Mapped[int] = mapped_column(primary_key=True, index=True)
|
||||||
input_params: Mapped[Dict[str, Any]] = mapped_column(JSON, nullable=False)
|
input_params: Mapped[Dict[str, Any]] = mapped_column(JSON, nullable=False)
|
||||||
output_results: Mapped[Dict[str, Any] | None] = mapped_column(JSON, nullable=True)
|
output_results: Mapped[Dict[str, Any] | None] = mapped_column(JSON, nullable=True)
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
from sqlalchemy import Column, Integer, String, JSON, Float, DateTime
|
|
||||||
from sqlalchemy.sql import func
|
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
|
|
||||||
Base = declarative_base()
|
|
||||||
|
|
||||||
class Calculation(Base):
|
|
||||||
__tablename__ = "calculations"
|
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
|
||||||
input_params = Column(JSON)
|
|
||||||
output_results = Column(JSON)
|
|
||||||
timestamp = Column(DateTime(timezone=True), server_default=func.now())
|
|
||||||
model_name = Column(String, nullable=True)
|
|
||||||
objective_score = Column(Float, nullable=True)
|
|
||||||
# Удалите эти строки, если они есть:
|
|
||||||
# user_id = Column(Integer, ForeignKey("users.id"))
|
|
||||||
# user = relationship("User", back_populates="calculations")
|
|
||||||
|
|
||||||
# Можно вообще удалить класс User, если он больше не нужен
|
|
|
@ -7,8 +7,8 @@ from app.services import calculation_service as service
|
||||||
from app.db.session import get_db
|
from app.db.session import get_db
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter(
|
||||||
prefix="/calculation", # Общий префикс для эндпоинтов этого роутера
|
prefix="/calculation",
|
||||||
tags=["Calculations"], # Тег для группировки в документации Swagger
|
tags=["Calculations"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.post("/", response_model=CalculationRead, status_code=status.HTTP_201_CREATED)
|
@router.post("/", response_model=CalculationRead, status_code=status.HTTP_201_CREATED)
|
||||||
|
@ -45,8 +45,6 @@ async def read_calculation(
|
||||||
)
|
)
|
||||||
return db_calc
|
return db_calc
|
||||||
|
|
||||||
# Определяем эндпоинт для истории до эндпоинта с ID,
|
|
||||||
# чтобы FastAPI не принял "history" за ID
|
|
||||||
@router.get("/history/", response_model=List[CalculationRead])
|
@router.get("/history/", response_model=List[CalculationRead])
|
||||||
async def read_calculation_history(
|
async def read_calculation_history(
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
|
|
|
@ -2,34 +2,22 @@ from pydantic import BaseModel, ConfigDict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
# --- Базовая схема ---
|
|
||||||
# Общие поля для расчета
|
|
||||||
class CalculationBase(BaseModel):
|
class CalculationBase(BaseModel):
|
||||||
input_params: Dict[str, Any]
|
input_params: Dict[str, Any]
|
||||||
model_name: Optional[str] = None # Модель может быть выбрана сервером
|
model_name: Optional[str] = None
|
||||||
|
|
||||||
# --- Схема для создания (запуска) расчета ---
|
|
||||||
# Данные, которые пользователь отправляет для запуска
|
|
||||||
class CalculationCreate(CalculationBase):
|
class CalculationCreate(CalculationBase):
|
||||||
pass # Пока совпадает с Base, но может быть расширена
|
pass
|
||||||
|
|
||||||
# --- Схема для чтения ---
|
|
||||||
# Полная информация о расчете, возвращаемая API
|
|
||||||
class CalculationRead(CalculationBase):
|
class CalculationRead(CalculationBase):
|
||||||
id: int
|
id: int
|
||||||
output_results: Optional[Dict[str, Any]] = None
|
output_results: Optional[Dict[str, Any]] = None
|
||||||
timestamp: datetime
|
timestamp: datetime
|
||||||
objective_score: Optional[float] = None
|
objective_score: Optional[float] = None
|
||||||
|
|
||||||
# Включаем режим ORM
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
# В Pydantic v1 было:
|
|
||||||
# class Config:
|
|
||||||
# orm_mode = True
|
|
||||||
|
|
||||||
|
|
||||||
# --- Опционально: Схема для обновления (если результаты добавляются позже) ---
|
|
||||||
class CalculationUpdate(BaseModel):
|
class CalculationUpdate(BaseModel):
|
||||||
output_results: Optional[Dict[str, Any]] = None
|
output_results: Optional[Dict[str, Any]] = None
|
||||||
objective_score: Optional[float] = None
|
objective_score: Optional[float] = None
|
||||||
model_name: Optional[str] = None # Если модель определяется по результату
|
model_name: Optional[str] = None
|
|
@ -6,6 +6,4 @@ class Token(BaseModel):
|
||||||
token_type: str
|
token_type: str
|
||||||
|
|
||||||
class TokenData(BaseModel):
|
class TokenData(BaseModel):
|
||||||
username: Optional[str] = None
|
username: Optional[str] = None
|
||||||
# Можно использовать id пользователя, если удобнее
|
|
||||||
# user_id: Optional[int] = None
|
|
|
@ -3,25 +3,19 @@ from typing import List, Optional, Dict, Any
|
||||||
|
|
||||||
from app.models.calculation import Calculation
|
from app.models.calculation import Calculation
|
||||||
from app.schemas.calculation import CalculationCreate, CalculationUpdate
|
from app.schemas.calculation import CalculationCreate, CalculationUpdate
|
||||||
from app.fuzzy_solver import solve_cutting_problem # Импортируем асинхронную заглушку
|
from app.fuzzy_solver import solve_cutting_problem
|
||||||
|
|
||||||
# Делаем функцию создания асинхронной
|
|
||||||
async def create_calculation(db: Session, calc_in: CalculationCreate) -> Calculation:
|
async def create_calculation(db: Session, calc_in: CalculationCreate) -> Calculation:
|
||||||
"""
|
"""
|
||||||
Асинхронно создает запись о расчете, вызывает заглушку и сохраняет результат.
|
Асинхронно создает запись о расчете, вызывает заглушку и сохраняет результат.
|
||||||
"""
|
"""
|
||||||
# 1. Вызываем асинхронную заглушку расчета
|
|
||||||
print("Calling async solver...")
|
print("Calling async solver...")
|
||||||
output_results = await solve_cutting_problem(calc_in.input_params)
|
output_results = await solve_cutting_problem(calc_in.input_params)
|
||||||
print("Async solver finished.")
|
print("Async solver finished.")
|
||||||
|
|
||||||
# 2. Определяем другие параметры (логика остается)
|
|
||||||
objective_score = output_results.get("waste_percentage")
|
objective_score = output_results.get("waste_percentage")
|
||||||
model_name = calc_in.model_name or "default_fuzzy_model_v1"
|
model_name = calc_in.model_name or "default_fuzzy_model_v1"
|
||||||
|
|
||||||
# 3. Создаем объект модели SQLAlchemy
|
|
||||||
# Операции с БД остаются синхронными в этом примере
|
|
||||||
# Для полной асинхронности потребовался бы async-драйвер и AsyncSession
|
|
||||||
print("Creating DB object...")
|
print("Creating DB object...")
|
||||||
db_calc = Calculation(
|
db_calc = Calculation(
|
||||||
input_params=calc_in.input_params,
|
input_params=calc_in.input_params,
|
||||||
|
@ -30,7 +24,6 @@ async def create_calculation(db: Session, calc_in: CalculationCreate) -> Calcula
|
||||||
model_name=model_name
|
model_name=model_name
|
||||||
)
|
)
|
||||||
|
|
||||||
# 4. Сохраняем в базу данных (синхронные операции)
|
|
||||||
print("Adding to DB session...")
|
print("Adding to DB session...")
|
||||||
db.add(db_calc)
|
db.add(db_calc)
|
||||||
print("Committing DB session...")
|
print("Committing DB session...")
|
||||||
|
@ -40,8 +33,6 @@ async def create_calculation(db: Session, calc_in: CalculationCreate) -> Calcula
|
||||||
print("Calculation created and saved.")
|
print("Calculation created and saved.")
|
||||||
return db_calc
|
return db_calc
|
||||||
|
|
||||||
# Функции чтения можно оставить синхронными, т.к. они быстрые
|
|
||||||
# и используют синхронную сессию
|
|
||||||
def get_calculation_by_id(db: Session, calc_id: int) -> Optional[Calculation]:
|
def get_calculation_by_id(db: Session, calc_id: int) -> Optional[Calculation]:
|
||||||
"""
|
"""
|
||||||
Получает расчет по его ID.
|
Получает расчет по его ID.
|
||||||
|
@ -53,16 +44,3 @@ def get_calculations(db: Session, skip: int = 0, limit: int = 100) -> List[Calcu
|
||||||
Получает список всех расчетов с пагинацией.
|
Получает список всех расчетов с пагинацией.
|
||||||
"""
|
"""
|
||||||
return db.query(Calculation).offset(skip).limit(limit).all()
|
return db.query(Calculation).offset(skip).limit(limit).all()
|
||||||
|
|
||||||
# Опционально: Функция для обновления расчета (если нужно)
|
|
||||||
# def update_calculation(db: Session, calc_id: int, calc_update: CalculationUpdate) -> Optional[Calculation]:
|
|
||||||
# db_calc = get_calculation_by_id(db, calc_id)
|
|
||||||
# if not db_calc:
|
|
||||||
# return None
|
|
||||||
# update_data = calc_update.model_dump(exclude_unset=True) # Pydantic v2
|
|
||||||
# # update_data = calc_update.dict(exclude_unset=True) # Pydantic v1
|
|
||||||
# for key, value in update_data.items():
|
|
||||||
# setattr(db_calc, key, value)
|
|
||||||
# db.commit()
|
|
||||||
# db.refresh(db_calc)
|
|
||||||
# return db_calc
|
|
Loading…
Reference in New Issue