feat: add proxy service layer with upsert and query

This commit is contained in:
agatha 2026-03-14 16:15:49 -04:00
parent 8532b9ca8d
commit a38d6a5e36

View File

@ -0,0 +1,99 @@
from sqlalchemy import func, select
from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.ext.asyncio import AsyncSession
from proxy_pool.proxy.models import (
AnonymityLevel,
Proxy,
ProxyProtocol,
ProxyStatus,
)
async def upsert_proxies(
db: AsyncSession,
proxies: list[dict],
) -> tuple[int, int]:
"""Upsert discovered proxies. Returns (new_count, updated_count)."""
if not proxies:
return 0, 0
stmt = insert(Proxy).values(proxies)
stmt = stmt.on_conflict_do_update(
index_elements=["ip", "port", "protocol"],
set_={"source_id": stmt.excluded.source_id},
)
result = await db.execute(stmt)
total = result.rowcount
await db.commit()
# rowcount from on_conflict_do_update counts both inserts and updates
# We can't easily distinguish them without a subquery, so return total
return total, 0
async def query_proxies(
db: AsyncSession,
*,
status: ProxyStatus | None = None,
protocol: ProxyProtocol | None = None,
anonymity: AnonymityLevel | None = None,
country: str | None = None,
min_score: float | None = None,
max_latency_ms: float | None = None,
min_uptime_pct: float | None = None,
verified_within_minutes: int | None = None,
sort_by: str = "score",
sort_order: str = "desc",
limit: int = 50,
offset: int = 0,
) -> tuple[list[Proxy], int]:
"""Query proxies with filtering and sorting. Returns (proxies, total_count)."""
query = select(Proxy)
if status is not None:
query = query.where(Proxy.status == status)
if protocol is not None:
query = query.where(Proxy.protocol == protocol)
if anonymity is not None:
query = query.where(Proxy.anonymity == anonymity)
if country is not None:
query = query.where(Proxy.country == country.upper())
if min_score is not None:
query = query.where(Proxy.score >= min_score)
if max_latency_ms is not None:
query = query.where(Proxy.avg_latency_ms <= max_latency_ms)
if min_uptime_pct is not None:
query = query.where(Proxy.uptime_pct >= min_uptime_pct)
if verified_within_minutes is not None:
query = query.where(
Proxy.last_checked_at
>= func.now()
- func.make_interval(0, 0, 0, 0, 0, verified_within_minutes, 0)
)
# Count before pagination
count_query = select(func.count()).select_from(query.subquery())
total = (await db.execute(count_query)).scalar_one()
# Sorting
sort_columns = {
"score": Proxy.score,
"latency": Proxy.avg_latency_ms,
"uptime": Proxy.uptime_pct,
"last_checked": Proxy.last_checked_at,
"created": Proxy.created_at,
}
sort_col = sort_columns.get(sort_by, Proxy.score)
if sort_order == "asc":
query = query.order_by(sort_col.asc().nulls_last())
else:
query = query.order_by(sort_col.desc().nulls_last())
query = query.offset(offset).limit(limit)
result = await db.execute(query)
proxies = list(result.scalars().all())
return proxies, total