Source code for pydotorg.domains.sponsors.repositories
"""Sponsors domain repositories for database access."""
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING
from advanced_alchemy.repository import SQLAlchemyAsyncRepository
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from pydotorg.domains.sponsors.models import (
Contract,
ContractStatus,
LegalClause,
Sponsor,
Sponsorship,
SponsorshipLevel,
SponsorshipStatus,
)
if TYPE_CHECKING:
from uuid import UUID
[docs]
class SponsorshipLevelRepository(SQLAlchemyAsyncRepository[SponsorshipLevel]):
"""Repository for SponsorshipLevel database operations."""
model_type = SponsorshipLevel
[docs]
async def get_by_slug(self, slug: str) -> SponsorshipLevel | None:
"""Get a sponsorship level by its slug.
Args:
slug: The slug to search for.
Returns:
The sponsorship level if found, None otherwise.
"""
statement = select(SponsorshipLevel).where(SponsorshipLevel.slug == slug)
result = await self.session.execute(statement)
return result.scalar_one_or_none()
[docs]
async def list_ordered(self, limit: int = 100, offset: int = 0) -> list[SponsorshipLevel]:
"""List sponsorship levels ordered by order field.
Args:
limit: Maximum number of levels to return.
offset: Number of levels to skip.
Returns:
List of sponsorship levels ordered by order field.
"""
statement = select(SponsorshipLevel).order_by(SponsorshipLevel.order.asc()).limit(limit).offset(offset)
result = await self.session.execute(statement)
return list(result.scalars().all())
[docs]
async def exists_by_slug(self, slug: str) -> bool:
"""Check if a sponsorship level exists by slug.
Args:
slug: The slug to check.
Returns:
True if a level with this slug exists, False otherwise.
"""
level = await self.get_by_slug(slug)
return level is not None
[docs]
class SponsorRepository(SQLAlchemyAsyncRepository[Sponsor]):
"""Repository for Sponsor database operations."""
model_type = Sponsor
[docs]
async def get_by_slug(self, slug: str) -> Sponsor | None:
"""Get a sponsor by its slug.
Args:
slug: The slug to search for.
Returns:
The sponsor if found, None otherwise.
"""
statement = select(Sponsor).where(Sponsor.slug == slug)
result = await self.session.execute(statement)
return result.scalar_one_or_none()
[docs]
async def list_with_active_sponsorships(
self,
limit: int = 100,
offset: int = 0,
) -> list[Sponsor]:
"""List sponsors with active sponsorships.
Args:
limit: Maximum number of sponsors to return.
offset: Number of sponsors to skip.
Returns:
List of sponsors with active sponsorships, with sponsorships and levels loaded.
"""
statement = (
select(Sponsor)
.join(Sponsorship, Sponsor.id == Sponsorship.sponsor_id)
.where(Sponsorship.status == SponsorshipStatus.FINALIZED)
.options(selectinload(Sponsor.sponsorships).selectinload(Sponsorship.level))
.distinct()
.limit(limit)
.offset(offset)
)
result = await self.session.execute(statement)
return list(result.scalars().unique().all())
[docs]
async def exists_by_slug(self, slug: str) -> bool:
"""Check if a sponsor exists by slug.
Args:
slug: The slug to check.
Returns:
True if a sponsor with this slug exists, False otherwise.
"""
sponsor = await self.get_by_slug(slug)
return sponsor is not None
[docs]
class SponsorshipRepository(SQLAlchemyAsyncRepository[Sponsorship]):
"""Repository for Sponsorship database operations."""
model_type = Sponsorship
[docs]
async def list_by_sponsor_id(
self,
sponsor_id: UUID,
limit: int = 100,
offset: int = 0,
) -> list[Sponsorship]:
"""List sponsorships for a specific sponsor.
Args:
sponsor_id: The sponsor ID to filter by.
limit: Maximum number of sponsorships to return.
offset: Number of sponsorships to skip.
Returns:
List of sponsorships for the sponsor.
"""
statement = select(Sponsorship).where(Sponsorship.sponsor_id == sponsor_id).limit(limit).offset(offset)
result = await self.session.execute(statement)
return list(result.scalars().all())
[docs]
async def list_by_level_id(
self,
level_id: UUID,
limit: int = 100,
offset: int = 0,
) -> list[Sponsorship]:
"""List sponsorships for a specific level.
Args:
level_id: The level ID to filter by.
limit: Maximum number of sponsorships to return.
offset: Number of sponsorships to skip.
Returns:
List of sponsorships for the level.
"""
statement = select(Sponsorship).where(Sponsorship.level_id == level_id).limit(limit).offset(offset)
result = await self.session.execute(statement)
return list(result.scalars().all())
[docs]
async def list_by_status(
self,
status: SponsorshipStatus,
limit: int = 100,
offset: int = 0,
) -> list[Sponsorship]:
"""List sponsorships by status.
Args:
status: The status to filter by.
limit: Maximum number of sponsorships to return.
offset: Number of sponsorships to skip.
Returns:
List of sponsorships with the given status.
"""
statement = select(Sponsorship).where(Sponsorship.status == status).limit(limit).offset(offset)
result = await self.session.execute(statement)
return list(result.scalars().all())
[docs]
async def list_active(self, limit: int = 100, offset: int = 0) -> list[Sponsorship]:
"""List active sponsorships.
Args:
limit: Maximum number of sponsorships to return.
offset: Number of sponsorships to skip.
Returns:
List of active sponsorships.
"""
today = datetime.now(tz=UTC).date()
statement = (
select(Sponsorship)
.where(Sponsorship.status == SponsorshipStatus.FINALIZED)
.where(Sponsorship.start_date <= today)
.where(Sponsorship.end_date >= today)
.limit(limit)
.offset(offset)
)
result = await self.session.execute(statement)
return list(result.scalars().all())
[docs]
async def get_previous_for_sponsor(
self,
sponsor_id: UUID,
current_year: int | None,
) -> Sponsorship | None:
"""Get the previous sponsorship for a sponsor.
Args:
sponsor_id: The sponsor ID to search for.
current_year: The current sponsorship year to exclude.
Returns:
The previous sponsorship if found, None otherwise.
"""
statement = (
select(Sponsorship)
.where(Sponsorship.sponsor_id == sponsor_id)
.where(Sponsorship.status == SponsorshipStatus.FINALIZED)
)
if current_year is not None:
statement = statement.where(Sponsorship.year < current_year)
statement = statement.order_by(Sponsorship.year.desc()).limit(1)
result = await self.session.execute(statement)
return result.scalar_one_or_none()
[docs]
async def list_expiring_soon(
self,
days: int = 90,
limit: int = 100,
offset: int = 0,
) -> list[Sponsorship]:
"""List sponsorships expiring within the given number of days.
Args:
days: Number of days until expiration.
limit: Maximum number of sponsorships to return.
offset: Number of sponsorships to skip.
Returns:
List of sponsorships expiring within the given timeframe.
"""
today = datetime.now(tz=UTC).date()
cutoff_date = today + timedelta(days=days)
statement = (
select(Sponsorship)
.where(Sponsorship.status == SponsorshipStatus.FINALIZED)
.where(Sponsorship.end_date >= today)
.where(Sponsorship.end_date <= cutoff_date)
.order_by(Sponsorship.end_date.asc())
.limit(limit)
.offset(offset)
)
result = await self.session.execute(statement)
return list(result.scalars().all())
[docs]
class LegalClauseRepository(SQLAlchemyAsyncRepository[LegalClause]):
"""Repository for LegalClause database operations."""
model_type = LegalClause
[docs]
async def get_by_slug(self, slug: str) -> LegalClause | None:
"""Get a legal clause by its slug.
Args:
slug: The slug to search for.
Returns:
The legal clause if found, None otherwise.
"""
statement = select(LegalClause).where(LegalClause.slug == slug)
result = await self.session.execute(statement)
return result.scalar_one_or_none()
[docs]
async def list_active(self, limit: int = 100, offset: int = 0) -> list[LegalClause]:
"""List active legal clauses ordered by order field.
Args:
limit: Maximum number of clauses to return.
offset: Number of clauses to skip.
Returns:
List of active legal clauses.
"""
statement = (
select(LegalClause)
.where(LegalClause.is_active.is_(True))
.order_by(LegalClause.order.asc())
.limit(limit)
.offset(offset)
)
result = await self.session.execute(statement)
return list(result.scalars().all())
[docs]
class ContractRepository(SQLAlchemyAsyncRepository[Contract]):
"""Repository for Contract database operations."""
model_type = Contract
[docs]
async def get_by_sponsorship_id(self, sponsorship_id: UUID) -> Contract | None:
"""Get a contract by sponsorship ID.
Args:
sponsorship_id: The sponsorship ID to search for.
Returns:
The contract if found, None otherwise.
"""
statement = select(Contract).where(Contract.sponsorship_id == sponsorship_id)
result = await self.session.execute(statement)
return result.scalar_one_or_none()
[docs]
async def list_by_status(
self,
status: ContractStatus,
limit: int = 100,
offset: int = 0,
) -> list[Contract]:
"""List contracts by status.
Args:
status: The status to filter by.
limit: Maximum number of contracts to return.
offset: Number of contracts to skip.
Returns:
List of contracts with the given status.
"""
statement = (
select(Contract)
.where(Contract.status == status)
.order_by(Contract.created_at.desc())
.limit(limit)
.offset(offset)
)
result = await self.session.execute(statement)
return list(result.scalars().all())