diff --git a/pyproject.toml b/pyproject.toml index 5efe6f1..b58a7d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,9 @@ extend-exclude = ["alembic", "tests"] [tool.ruff.lint] select = ["E", "F", "I", "N", "UP", "B", "A", "SIM"] +[tool.ruff.lint.flake8-bugbear] +extend-immutable-calls = ["Depends", "fastapi.Depends", "Query", "fastapi.Query"] + [tool.mypy] plugins = ["pydantic.mypy"] strict = true \ No newline at end of file diff --git a/src/proxy_pool/common/router.py b/src/proxy_pool/common/router.py new file mode 100644 index 0000000..19ac7dd --- /dev/null +++ b/src/proxy_pool/common/router.py @@ -0,0 +1,36 @@ +from fastapi import APIRouter, Depends +from redis.asyncio import Redis +from sqlalchemy import text +from sqlalchemy.ext.asyncio import AsyncSession + +from proxy_pool.common.dependencies import get_db, get_redis +from proxy_pool.common.schemas import HealthResponse + +router = APIRouter(tags=["system"]) + + +@router.get("/health", response_model=HealthResponse) +async def health_check( + db: AsyncSession = Depends(get_db), + redis: Redis = Depends(get_redis), +) -> HealthResponse: + pg_status = "connected" + try: + await db.execute(text("SELECT 1")) + except Exception: + pg_status = "error" + + redis_status = "connected" + try: + await redis.ping() + except Exception: + redis_status = "error" + + return HealthResponse( + status="healthy" + if pg_status == "connected" and redis_status == "connected" + else "degraded", + postgres=pg_status, + redis=redis_status, + version="0.1.0", + ) diff --git a/src/proxy_pool/common/schemas.py b/src/proxy_pool/common/schemas.py new file mode 100644 index 0000000..41d2fe3 --- /dev/null +++ b/src/proxy_pool/common/schemas.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + + +class HealthResponse(BaseModel): + status: str + postgres: str + redis: str + version: str diff --git a/src/proxy_pool/proxy/schemas.py b/src/proxy_pool/proxy/schemas.py new file mode 100644 index 0000000..4d2743e --- /dev/null +++ b/src/proxy_pool/proxy/schemas.py @@ -0,0 +1,53 @@ +from datetime import datetime +from uuid import UUID + +from pydantic import BaseModel, Field + +from proxy_pool.proxy.models import AnonymityLevel, ProxyProtocol, ProxyStatus + + +class ProxySourceCreate(BaseModel): + url: str = Field(max_length=2048) + parser_name: str = Field(max_length=64) + cron_schedule: str | None = Field(default=None, max_length=64) + default_protocol: ProxyProtocol = ProxyProtocol.HTTP + + +class ProxySourceUpdate(BaseModel): + url: str | None = Field(default=None, max_length=2048) + parser_name: str | None = Field(default=None, max_length=64) + cron_schedule: str | None = None + default_protocol: ProxyProtocol | None = None + is_active: bool | None = None + + +class ProxySourceResponse(BaseModel): + model_config = {"from_attributes": True} + + id: UUID + url: str + parser_name: str + cron_schedule: str | None + default_protocol: ProxyProtocol + is_active: bool + last_scraped_at: datetime | None + created_at: datetime + + +class ProxyResponse(BaseModel): + model_config = {"from_attributes": True} + + id: UUID + ip: str + port: int + protocol: ProxyProtocol + status: ProxyStatus + anonymity: AnonymityLevel | None + exit_ip: str | None + country: str | None + score: float + avg_latency_ms: float | None + uptime_pct: float | None + last_checked_at: datetime | None + first_seen_at: datetime + created_at: datetime