Coverage for app/utils.py: 64%
61 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
1import sys
2from datetime import datetime, timedelta
3from typing import Literal, cast
4from urllib.parse import parse_qs, urlencode, urlparse
6from pydantic import ValidationError
7from sqlalchemy import text
9from app.core.db import read_engine
10from app.core.logger import logger
11from app.models import QueryParams, TimeRange, TimeRangeValidValues
14def start_of_week(date: str | datetime) -> str:
15 if isinstance(date, str):
16 date = datetime.strptime(date, "%Y-%m-%d")
17 start = date - timedelta(days=date.weekday())
18 return start.strftime("%Y-%m-%d")
21def validate_package(package_name: str) -> bool:
22 with read_engine.connect() as conn:
23 result = conn.execute(
24 text("""
25 select
26 package_name
27 from
28 pypi_packages
29 where
30 package_name = :package_name
31 """),
32 {"package_name": package_name},
33 )
34 exists = result.scalar() is not None
35 if not exists:
36 logger.info(f"Package validation failed for: {package_name}")
37 return exists
40def parse_query_params(url: str) -> QueryParams:
41 try:
42 qs = parse_qs(urlparse(url).query)
43 packages = qs.get("packages", [])
44 time_range = TimeRange(
45 value=cast(TimeRangeValidValues, qs.get("time_range", ["3months"])[0])
46 )
47 show_percentage = cast(Literal["on"], qs.get("show_percentage", [None])[0])
48 return QueryParams(
49 time_range=time_range, packages=packages, show_percentage=show_percentage
50 )
51 except ValidationError as e:
52 return QueryParams(error=str(e))
55def generate_hx_push_url(query_params: QueryParams) -> str:
56 if not query_params.packages:
57 return "/"
58 params = {
59 "packages": [package for package in query_params.packages],
60 "time_range": query_params.time_range.value,
61 }
62 if query_params.show_percentage is not None:
63 params["show_percentage"] = [query_params.show_percentage]
64 return "?" + urlencode(params, doseq=True)
67def extract_last_script_tag(html_content: str) -> str | None:
68 pos = len(html_content)
69 while True:
70 pos = html_content.rfind("<script", 0, pos)
71 if pos == -1:
72 return None
74 end_pos = html_content.find("</script>", pos)
75 if end_pos != -1:
76 return html_content[pos : end_pos + len("</script>")]
79def validate_date(date_text: str) -> bool:
80 try:
81 datetime.strptime(date_text, "%Y-%m-%d")
82 return True
83 except ValueError:
84 return False
87def parse_dates(dates: tuple[str, str]) -> tuple[str, str]:
88 start_date, end_date = dates[0], dates[1]
90 if not all(validate_date(date) for date in (start_date, end_date)):
91 logger.error("Start date and end date must be in YYYY-MM-DD format")
92 sys.exit(1)
94 if end_date < start_date:
95 logger.error("End date must be greater than or equal to the start date")
96 sys.exit(1)
98 return start_date, end_date