Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ jobs:
strategy:
matrix:
python-version: ["3.14"]
uv-version: ["0.11.8"]
uv-version: ["0.11.26"]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- name: Set up uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v8.2.0
with:
version: "${{ matrix.uv-version }}"

Expand All @@ -44,7 +44,7 @@ jobs:

- name: Update test coverage badge
if: github.event_name == 'push'
uses: schneegans/dynamic-badges-action@v1.7.0
uses: schneegans/dynamic-badges-action@v1.8.0
with:
auth: ${{ secrets.GIST_SECRET }}
gistID: 1362ebafcd51d3f65dae7935b1d322eb
Expand All @@ -57,7 +57,7 @@ jobs:

- name: Update python version badge
if: github.event_name == 'push'
uses: schneegans/dynamic-badges-action@v1.7.0
uses: schneegans/dynamic-badges-action@v1.8.0
with:
auth: ${{ secrets.GIST_SECRET }}
gistID: 15a234815aa74059953a766a10e92688
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-startup-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
name: Startup Test (${{ matrix.tool }} ${{ matrix.mode }})
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Install ${{ matrix.tool }}
run: |
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ jobs:

steps:
- name: Setup | Checkout Repository
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Action | Semantic Version Release
id: release
# Adjust tag with desired version if applicable.
uses: python-semantic-release/python-semantic-release@v10.4.1
uses: python-semantic-release/python-semantic-release@v10.5.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
git_committer_name: "github-actions"
Expand All @@ -37,7 +37,7 @@ jobs:

steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ secrets.VPS_HOST }}
port: ${{ secrets.VPS_PORT }}
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ Router → get_* dependency (api/dependencies.py)
- Test structure mirrors `app/` DDD layout (`tests/domain/`, `tests/adapters/`, etc.).
- HTML/JSON fixtures for parser tests: `tests/fixtures/`.
- Refresh test fixtures from live pages: `just exec "python -m tests.update_test_fixtures"`.
- `patch("httpx.AsyncClient.get", ...)` is the standard mock pattern for HTTP calls.
- `patch("httpx2.AsyncClient.get", ...)` is the standard mock pattern for HTTP calls.
- `pytest.mark.parametrize` used extensively for enum coverage.
- Tests run in parallel via `pytest-xdist` (`-n auto`). Ensure test isolation.

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Build arguments
ARG PYTHON_VERSION=3.14
ARG UV_VERSION=0.11.8
ARG UV_VERSION=0.11.26

# Create a temporary stage to pull the uv binary
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv-stage
Expand Down
12 changes: 6 additions & 6 deletions app/adapters/blizzard/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import time
from typing import TYPE_CHECKING

import httpx
import httpx2
from fastapi import HTTPException, status

from app.adapters.blizzard.throttle import BlizzardThrottle
Expand Down Expand Up @@ -33,7 +33,7 @@ def __init__(self):
self.throttle: ThrottlePort | None = (
BlizzardThrottle() if settings.throttle_enabled else None
)
self.client = httpx.AsyncClient(
self.client = httpx2.AsyncClient(
headers={
"User-Agent": (
f"OverFastAPI v{settings.app_version} - "
Expand All @@ -52,7 +52,7 @@ async def get(
*,
headers: dict[str, str] | None = None,
params: dict[str, str] | None = None,
) -> httpx.Response:
) -> httpx2.Response:
"""Make an HTTP GET request, respecting the adaptive throttle."""
if self.throttle:
await self._throttle_wait()
Expand Down Expand Up @@ -98,19 +98,19 @@ async def _execute_request(
url: str,
normalized_endpoint: str,
kwargs: dict,
) -> httpx.Response:
) -> httpx2.Response:
"""Execute the HTTP GET and record metrics."""
start_time = time.perf_counter()
try:
response = await self.client.get(url, **kwargs)
except httpx.TimeoutException as error:
except httpx2.TimeoutException as error:
duration = time.perf_counter() - start_time
self._record_metrics(normalized_endpoint, "timeout", duration)
raise self._blizzard_response_error(
status_code=0,
error="Blizzard took more than 10 seconds to respond, resulting in a timeout",
) from error
except httpx.RemoteProtocolError as error:
except httpx2.RemoteProtocolError as error:
duration = time.perf_counter() - start_time
self._record_metrics(normalized_endpoint, "error", duration)
raise self._blizzard_response_error(
Expand Down
7 changes: 5 additions & 2 deletions app/domain/parsers/player_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,10 @@ def compute_heroes_data(
# Calculate special values (winrate, kda, averages)
for hero_key, hero_stats in computed_heroes_stats.items():
# Ignore computation for heroes without stats
if hero_stats["time_played"] <= 0:
if (
not isinstance(hero_stats["time_played"], int)
or hero_stats["time_played"] <= 0
):
continue

computed_heroes_stats[hero_key]["winrate"] = _calculate_winrate(hero_stats)
Expand All @@ -233,7 +236,7 @@ def compute_heroes_data(
return {
hero_key: hero_stats
for hero_key, hero_stats in computed_heroes_stats.items()
if hero_stats["time_played"] > 0
if isinstance(hero_stats["time_played"], int) and hero_stats["time_played"] > 0
}


Expand Down
4 changes: 2 additions & 2 deletions app/domain/parsers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
from app.infrastructure.logger import logger

if TYPE_CHECKING:
import httpx
import httpx2

_HTTP_504 = 504


def validate_response_status(
response: httpx.Response,
response: httpx2.Response,
valid_codes: list[int] | None = None,
) -> None:
"""Validate HTTP response status code.
Expand Down
4 changes: 2 additions & 2 deletions app/domain/ports/blizzard_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import TYPE_CHECKING, Protocol

if TYPE_CHECKING:
import httpx
import httpx2


class BlizzardClientPort(Protocol):
Expand All @@ -15,7 +15,7 @@ async def get(
*,
headers: dict[str, str] | None = None,
params: dict[str, str] | None = None,
) -> httpx.Response:
) -> httpx2.Response:
"""GET request to the given URL, respecting configured throttling."""
...

Expand Down
6 changes: 3 additions & 3 deletions app/infrastructure/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from datetime import UTC, datetime
from typing import Any

import httpx
import httpx2
from fastapi import HTTPException, status

from app.config import settings
Expand Down Expand Up @@ -136,7 +136,7 @@ def send_discord_webhook_message(
url: str | None = None,
fields: list[dict[str, Any]] | None = None,
color: int | None = None,
) -> httpx.Response | None:
) -> httpx2.Response | None:
"""Send a Discord webhook message using modern embed syntax.

Rate-limited to one call per 30 minutes with the same parameters.
Expand All @@ -156,6 +156,6 @@ def send_discord_webhook_message(
embed = _build_embed(title, description, url, fields, color)
payload = {"username": "OverFast API", "embeds": [embed]}

return httpx.post( # pragma: no cover
return httpx2.post( # pragma: no cover
settings.discord_webhook_url, json=payload, timeout=10
)

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

20 changes: 0 additions & 20 deletions openspec/config.yaml

This file was deleted.

22 changes: 0 additions & 22 deletions openspec/specs/human-readable-duration/spec.md

This file was deleted.

12 changes: 6 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"asyncpg==0.31.*",
"fastapi[standard-no-fastapi-cloud-cli]==0.136.*",
"httpx[http2]==0.28.*",
"fastapi[standard-no-fastapi-cloud-cli]==0.139.*",
"httpx2[http2]==2.5.*",
"loguru==0.7.*",
"prometheus-client==0.25.*",
"pydantic==2.13.*",
"pydantic-settings==2.14.*",
"selectolax==0.4.*",
"sentry-sdk[fastapi]==2.58.*",
"sentry-sdk[fastapi]==2.64.*",
"taskiq==0.12.*",
"taskiq-fastapi==0.5.*",
"valkey[libvalkey]==6.1.*",
Expand All @@ -39,10 +39,10 @@ Issues = "https://github.com/TeKrop/overfast-api/issues"

[dependency-groups]
dev = [
"fakeredis[valkey]==2.35.*",
"fakeredis[valkey]==2.36.*",
"ipdb==0.13.*",
"pytest==9.0.*",
"pytest-asyncio==1.3.*",
"pytest==9.1.*",
"pytest-asyncio==1.4.*",
"pytest-cov==7.1.*",
"pytest-randomly==4.1.*",
"pytest-xdist==3.8.*",
Expand Down
Loading