Coverage for app/models.py: 100%
22 statements
« prev ^ index » next coverage.py v7.6.10, created at 2026-06-04 22:29 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2026-06-04 22:29 +0000
1from datetime import datetime
2from typing import Literal
3from zoneinfo import ZoneInfo
5from dateutil.relativedelta import relativedelta
6from pydantic import BaseModel, computed_field
8from app.normalization import NormalizedPackageName
10TIME_RANGE_MAPPING = {
11 "1month": dict(months=1),
12 "3months": dict(months=3),
13 "6months": dict(months=6),
14 "1year": dict(years=1),
15 "2years": dict(years=2),
16 "5years": dict(years=5),
17}
19PYPI_DOWNLOADS_MIN_DATE = "2016-01-01"
21TimeRangeValidValues = Literal[
22 "1month",
23 "3months",
24 "6months",
25 "1year",
26 "2years",
27 "5years",
28 "allTime",
29 "allTimeCumulative",
30 "allTimeCumulativeAlignTimeline",
31]
34class TimeRange(BaseModel):
35 value: TimeRangeValidValues = "3months"
37 @computed_field # type: ignore
38 @property
39 def date_str(self) -> str:
40 if self.value in [
41 "allTime",
42 "allTimeCumulative",
43 "allTimeCumulativeAlignTimeline",
44 ]:
45 return PYPI_DOWNLOADS_MIN_DATE
47 return (
48 (
49 datetime.now(ZoneInfo("UTC"))
50 - relativedelta(**(TIME_RANGE_MAPPING[self.value])) # type: ignore
51 )
52 .date()
53 .isoformat()
54 )
57class QueryParams(BaseModel):
58 packages: list[NormalizedPackageName] = []
59 time_range: TimeRange = TimeRange(value="3months")
60 show_percentage: Literal["on"] | None = None
61 error: str | None = None