From 9360077c5eda6a641475bc20402112cbfb4be9c9 Mon Sep 17 00:00:00 2001 From: agatha Date: Sat, 14 Mar 2026 13:02:42 -0400 Subject: [PATCH] feat: model proxy domain in SQLAlchemy --- src/proxy_pool/proxy/models.py | 117 +++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/proxy_pool/proxy/models.py diff --git a/src/proxy_pool/proxy/models.py b/src/proxy_pool/proxy/models.py new file mode 100644 index 0000000..1a33978 --- /dev/null +++ b/src/proxy_pool/proxy/models.py @@ -0,0 +1,117 @@ +import enum +import uuid +from datetime import datetime + +from sqlalchemy import ( + Boolean, + Float, + ForeignKey, + Index, + Integer, + String, + Text, + func, +) +from sqlalchemy.dialects.postgresql import ENUM, INET +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from proxy_pool.db.base import Base, TimestampMixin, UUIDPrimaryKeyMixin + + +class ProxyProtocol(str, enum.Enum): + HTTP = "http" + HTTPS = "https" + SOCKS4 = "socks4" + SOCKS5 = "socks5" + + +class ProxyStatus(str, enum.Enum): + UNCHECKED = "unchecked" + ACTIVE = "active" + DEAD = "dead" + + +class AnonymityLevel(str, enum.Enum): + TRANSPARENT = "transparent" + ANONYMOUS = "anonymous" + ELITE = "elite" + + +class ProxySource(UUIDPrimaryKeyMixin, TimestampMixin, Base): + __tablename__ = "proxy_sources" + + url: Mapped[str] = mapped_column(String(2048), unique=True) + parser_name: Mapped[str] = mapped_column(String(64)) + cron_schedule: Mapped[str | None] = mapped_column(String(64)) + default_protocol: Mapped[ProxyProtocol] = mapped_column( + ENUM(ProxyProtocol, name="proxy_protocol"), + ) + is_active: Mapped[bool] = mapped_column(default=True) + last_scraped_at: Mapped[datetime | None] = mapped_column() + + proxies: Mapped[list["Proxy"]] = relationship(back_populates="source") + + +class Proxy(UUIDPrimaryKeyMixin, TimestampMixin, Base): + __tablename__ = "proxies" + __table_args__ = ( + Index("ix_proxies_ip_port_proto", "ip", "port", "protocol", unique=True), + Index("ix_proxies_status_score", "status", "score"), + ) + + ip: Mapped[str] = mapped_column(INET) + port: Mapped[int] = mapped_column(Integer) + protocol: Mapped[ProxyProtocol] = mapped_column( + ENUM(ProxyProtocol, name="proxy_protocol", create_type=False), + ) + source_id: Mapped[uuid.UUID] = mapped_column( + ForeignKey("proxy_sources.id"), + ) + status: Mapped[ProxyStatus] = mapped_column( + ENUM(ProxyStatus, name="proxy_status"), + default=ProxyStatus.UNCHECKED, + ) + anonymity: Mapped[AnonymityLevel | None] = mapped_column( + ENUM(AnonymityLevel, name="anonymity_level"), + ) + exit_ip: Mapped[str | None] = mapped_column(INET) + country: Mapped[str | None] = mapped_column(String(2)) + score: Mapped[float] = mapped_column(Float, default=0.0) + avg_latency_ms: Mapped[float | None] = mapped_column(Float) + uptime_pct: Mapped[float | None] = mapped_column(Float) + first_seen_at: Mapped[datetime] = mapped_column(server_default=func.now()) + last_checked_at: Mapped[datetime | None] = mapped_column() + + source: Mapped["ProxySource"] = relationship(back_populates="proxies") + checks: Mapped[list["ProxyCheck"]] = relationship(back_populates="proxy") + tags: Mapped[list["ProxyTag"]] = relationship(back_populates="proxy") + + +class ProxyCheck(UUIDPrimaryKeyMixin, TimestampMixin, Base): + __tablename__ = "proxy_checks" + __table_args__ = (Index("ix_checks_proxy_created", "proxy_id", "created_at"),) + + proxy_id: Mapped[uuid.UUID] = mapped_column( + ForeignKey("proxies.id", ondelete="CASCADE"), + ) + checker_name: Mapped[str] = mapped_column(String(64)) + stage: Mapped[int] = mapped_column(Integer) + passed: Mapped[bool] = mapped_column(Boolean) + latency_ms: Mapped[float | None] = mapped_column(Float) + detail: Mapped[str | None] = mapped_column(Text) + exit_ip: Mapped[str | None] = mapped_column(INET) + + proxy: Mapped["Proxy"] = relationship(back_populates="checks") + + +class ProxyTag(UUIDPrimaryKeyMixin, Base): + __tablename__ = "proxy_tags" + __table_args__ = (Index("ix_tags_proxy_key", "proxy_id", "key", unique=True),) + + proxy_id: Mapped[uuid.UUID] = mapped_column( + ForeignKey("proxies.id", ondelete="CASCADE"), + ) + key: Mapped[str] = mapped_column(String(64)) + value: Mapped[str] = mapped_column(String(256)) + + proxy: Mapped["Proxy"] = relationship(back_populates="tags")