Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
APP_NAME='Critical Value API v1'
SECRET_KEY=supersecretkey
ACCESS_TOKEN_EXPIRE_MINUTES=30
LOG_LEVEL=INFO

DB_HOST=localhost
DB_PORT=5432
DB_NAME=db_name
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
name: PySATL CI

on:
push:
branches:
Expand Down Expand Up @@ -35,10 +33,10 @@ jobs:
run: poetry install --with dev

- name: Run Ruff lint
run: poetry run ruff check --output-format=github
run: poetry run ruff check --output-format=github --extend-exclude "migrations"

- name: Check formatting with Ruff
run: poetry run ruff format --check
run: poetry run ruff format pysatl_knowledge --check

- name: Mypy
run: |
Expand Down
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ repos:
hooks:
- id: flake8
additional_dependencies: [Flake8-pyproject]
exclude: migrations
# stages: [push]

- repo: https://github.com/pre-commit/mirrors-mypy
Expand Down
21 changes: 21 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: "3.9"

services:
db:
image: postgres:15
container_name: knowledge-db
ports:
- "${DB_PORT}:5432"
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}
POSTGRES_DB: ${DB_NAME}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 5s
retries: 5

volumes:
postgres_data:
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""Create users and experiments tables

Revision ID: 9a61883b84c3
Revises:
Revises:
Create Date: 2025-07-28 20:58:42.261092

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from alembic import op


# revision identifiers, used by Alembic.
Expand Down
57 changes: 42 additions & 15 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ dependencies = [
"pydantic>=2.7.0",
"python-dotenv>=1.0.1",
"loguru>=0.7.2",
"httpx>=0.27.0"
"httpx>=0.27.0",
"python-multipart (>=0.0.20,<0.0.21)",
"pydantic-settings (>=2.10.1,<3.0.0)"
]

[build-system]
Expand Down Expand Up @@ -61,6 +63,7 @@ lines_after_imports=2
skip_glob = ["**/.env*", "**/env/*", "**/.venv/*", "**/docs/*", "**/user_data/*"]

[tool.ruff]
ignore = ["B008"]
line-length = 100
extend-exclude = [".env", ".venv"]
exclude = [
Expand All @@ -72,7 +75,7 @@ extend-select = [
"C90", "B", "F", "E", "W", "UP", "I", "A", "TID", "YTT", "S", "PTH", "ASYNC", "NPY"
]
extend-ignore = [
"E241", "E272", "E221", "B007", "B904", "S603", "S607", "S608", "NPY002"
"E241", "E272", "E221", "B007", "B904", "S603", "S607", "S608", "NPY002", "UP007"
]

[tool.mypy]
Expand Down
24 changes: 19 additions & 5 deletions pysatl_knowledge/api/auth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
from fastapi import APIRouter
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm

from pysatl_knowledge.schemas.auth_schema import LoginResponse
from pysatl_knowledge.services.auth_service import AuthService
from pysatl_knowledge.services.dependencies import get_auth_service

router = APIRouter(prefix="/hello", tags=["Hello"])

router = APIRouter(prefix="/auth", tags=["Authentication"])

@router.get("")
async def say_hello():
return {"message": "Hello World!"}

@router.post("/login", response_model=LoginResponse)
async def login(
form_data: OAuth2PasswordRequestForm = Depends(),
service: AuthService = Depends(get_auth_service),
):
"""
Получить JWT токен.
"""
token = await service.login(form_data.username, form_data.password)
if not token:
raise HTTPException(status_code=401, detail="Invalid credentials")
return token
78 changes: 78 additions & 0 deletions pysatl_knowledge/api/critical_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Path

from pysatl_knowledge.core.security.jwt_utils import get_current_admin, get_current_user
from pysatl_knowledge.schemas.critical_value_schema import (
CriticalValueCreate,
CriticalValueResponse,
CriticalValueVerify,
)
from pysatl_knowledge.services.critical_value_service import CriticalValuesService
from pysatl_knowledge.services.dependencies import get_cv_service


router = APIRouter(prefix="/critical_values", tags=["Critical Values"])


@router.get("", response_model=CriticalValueResponse)
async def get_critical_values(
criterion_code: str,
size: int,
iterations: int,
service: CriticalValuesService = Depends(get_cv_service),
):
return (
await service.get_cv_by_params(
criterion_code=criterion_code,
sample_size=size,
iterations=iterations,
status="verified",
)
)[0]


@router.get("/all", response_model=list[CriticalValueResponse])
async def get_all_critical_values(
criterion_code: Optional[str] = None,
size: Optional[int] = None,
iterations: Optional[int] = None,
status: Optional[str] = None,
current_user=Depends(get_current_user),
service: CriticalValueCreate = Depends(get_cv_service),
):
return await service.get_cv_by_params(
criterion_code=criterion_code,
sample_size=size,
iterations=iterations,
status=status,
)


@router.post("", response_model=CriticalValueResponse)
async def create_critical_value(
data: CriticalValueCreate, service: CriticalValuesService = Depends(get_cv_service)
):
"""
Создать новое критическое значение.
"""
try:
return await service.create_cv(data)
except ValueError as e:
raise HTTPException(status_code=422, detail=str(e))


@router.patch("/{critical_value_id}/verify", response_model=CriticalValueResponse)
async def verify_critical_value(
data: CriticalValueVerify,
critical_value_id: int = Path(..., ge=1),
service: CriticalValuesService = Depends(get_cv_service),
current_admin=Depends(get_current_admin),
):
"""
Верифицировать или отклонить критическое значение (только для admin).
"""
result = await service.verify_cv(critical_value_id, data.status)
if not result:
raise HTTPException(status_code=404, detail="Critical value not found")
return result
22 changes: 14 additions & 8 deletions pysatl_knowledge/core/config.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
DB_HOST: str
DB_PORT: int
DB_NAME: str
DB_USER: str
DB_PASS: str
app_name: str = "test"
secret_key: str = "supersecretkey"
access_token_expire_minutes: int = 30
log_level: str = "INFO"
DB_HOST: str = "localhost"
DB_PORT: int = 5432
DB_NAME: str = "test"
DB_USER: str = "test"
DB_PASS: str = "<PASSWORD>"

class Config:
env_file = ".env"
extra = "ignore"

@property
def DATABASE_URL(self) -> str:
Expand All @@ -19,7 +27,5 @@ def DATABASE_URL(self) -> str:
f"{self.DB_NAME}"
)

model_config = SettingsConfigDict(env_file=".env")


settings = Settings()
Loading
Loading