The
async_fetch module is the core of the tif1 data fetching infrastructure. It provides async HTTP capabilities with HTTP/2 multiplexing, retry logic, circuit breaker protection, and multi-CDN fallback.Overview
The async fetch system is built onniquests (a modern, actively-maintained fork of requests) and provides:
- HTTP/2 Multiplexing: Single TCP connection for multiple parallel requests, which reduces latency
- Connection Pooling: Connection reuse with configurable pool sizing and keep-alive
- Parallel JSON Parsing: Offloads JSON parsing to thread pools to avoid blocking the async event loop
- Rate Limiting: Semaphore-based concurrency control to prevent CDN overload
- Automatic Retries: Exponential backoff with jitter and circuit breaker protection
- Multi-CDN Fallback: Automatic failover across multiple CDN sources (jsDelivr, Hugging Face buckets, StaticDelivr, custom CDNs)
- Smart Caching: SQLite-backed cache with in-memory LRU layer for hot data
- Payload Validation: Optional pydantic-free fetch-path validation for data integrity
- Resource Management: Automatic cleanup of connections, thread pools, and process pools
Most users do not need to interact with this module directly. The
Session class handles async fetching automatically through methods like laps_async(), get_fastest_laps_tels_async(), and load(). This documentation is for advanced users who need fine-grained control over fetching behavior or want to understand the performance characteristics.Architecture
Data Flow
The async fetch system follows this execution path:- Cache Check: Query in-memory LRU cache, then SQLite cache (if enabled)
- Network Fetch: If cache miss, fetch from CDN with HTTP/2 multiplexing
- JSON Parsing: Offload parsing to thread pool (or process pool for non-telemetry data)
- Validation: Optional fetch-path validation based on payload type
- Cache Write: Persist to SQLite and in-memory cache (if enabled)
- Return: Deliver parsed dictionary to caller
Thread Safety
All async fetch operations are thread-safe:- HTTP session uses connection pooling with thread-safe access
- Thread pool executor is lazily initialized with lock protection
- Circuit breaker uses reentrant locks for nested calls
- CDN manager tracks failures with atomic counters
Resource Lifecycle
Resources are managed automatically:- HTTP Session: Created on first use, reused across all requests, closed on shutdown
- Thread Pool: Created on first use, sized based on
max_workersconfig - Process Pool: Optional, created on first use if
json_parse_workers > 0 - Cleanup: All resources cleaned up via
atexithandlers or explicitcleanup_resources()call
Core Functions
fetch_json_async
Parameters
int
required
Season year (for example, 2025, 2024). Must be a valid F1 season year with available data.
str
required
Grand Prix name (for example, “Monaco Grand Prix”, “Bahrain Grand Prix”). Must match the exact event name in the schedule data.
str
required
Session name (for example, “Race”, “Qualifying”, “Practice 1”, “Sprint”). Must match the exact session name in the schedule data.
str
required
Path to JSON file relative to the session directory. Common paths:
"laps.json"- All lap times for the session"drivers.json"- Driver metadata (names, numbers, teams)"weather.json"- Weather data throughout the session"rcm.json"- Race control messages (flags, safety car, etc.)"laps/VER.json"- Lap times for a specific driver (for example, Verstappen)"telemetry/VER_1_tel.json"- Telemetry for driver VER, lap 1
int | None
default:"None"
Maximum number of retries for failed requests. If
None, uses global config value (default: 3).- Set to
0to disable retries for the fastest cold-start fetch - Set to
3-5for production use (balance between reliability and latency) - Set to
10+for unreliable networks (maximum reliability)
ultra_cold_skip_retries=True in config, this is automatically set to 0 for the first fetch.int | None
default:"None"
Request timeout in seconds. If
None, uses global config value (default: 30).- Set to
10-15for fast networks (minimize latency) - Set to
30-60for typical networks (balance between speed and reliability) - Set to
120+for slow networks (maximum reliability)
bool
default:"True"
If
True, check cache (in-memory + SQLite) before making network requests. Set to False to force fresh data from CDN.Performance impact: Cache hits are 1000x faster than network fetches (~0.1ms vs ~100ms).bool
default:"True"
If
True, persist successful network responses to cache (both in-memory and SQLite). Set to False for ephemeral data or testing.Note: Automatically disabled when ci_mode=True in config.bool
default:"True"
If
True, run fetch-path validation on the fetched data before returning. The fetch
path is pydantic-free; public validate_* APIs remain for compatibility.
Validation rules depend on the payload type:rcm.json: Validate race control messages (strict=False, normalize=False)weather.json: Validate weather data (strict=False)*_tel.json: Validate telemetry data (strict=False, normalize=False)laps.json/laptimes.json: Validate lap times (ifvalidate_lap_times=True)
False for ~10-20% faster parsing when data integrity is guaranteed.Returns
dict[str, Any]
Parsed JSON data as a dictionary. Never returns
None - raises an exception on error instead.The structure depends on the requested path:laps.json:{"laps": [{"lap": 1, "time": "1:23.456", ...}, ...]}drivers.json:{"drivers": [{"code": "VER", "name": "Max Verstappen", ...}, ...]}weather.json:{"weather": [{"time": "2025-05-25T14:00:00", "temp": 25.5, ...}, ...]}*_tel.json:{"tel": {"rpm": [15000, 15100, ...], "speed": [300, 305, ...], ...}}
Raises
exception
Raised when network request fails after all retry attempts. Context includes:
url: The URL that failedstatus_code: HTTP status code (if available)
- Network connectivity issues
- CDN downtime (all CDN sources failed)
- Circuit breaker open (too many recent failures)
- Timeout exceeded
exception
Raised when the requested data does not exist (HTTP 404). Context includes:
year: Season yearevent: Grand Prix namesession: Session name
- Invalid year/event/session combination
- Data not yet available for future events
- Typo in event or session name
exception
Raised when JSON parsing or validation fails. Context includes:
reason: Description of the validation failure
- Corrupted JSON data
- Unexpected data structure
- Validation schema mismatch
Examples
Performance Characteristics
- Cache hit: ~0.1-0.5ms (in-memory) or ~1-5ms (SQLite)
- Cache miss (single request): ~50-150ms (network + parsing)
- Cache miss (parallel requests): ~200-400ms for 20 requests (HTTP/2 multiplexing)
- JSON parsing overhead: ~5-20ms for typical payloads, ~50-200ms for large telemetry payloads
- Validation overhead: ~5-15% additional parsing time (varies by payload type)
fetch_multiple_async
Parameters
list[tuple[int, str, str, str]]
required
List of Performance tip: Group related requests together for optimal HTTP/2 multiplexing.
(year, gp, session, path) tuples to fetch in parallel. Each tuple represents a single fetch operation.Example:bool
default:"True"
If
True, check cache before making network requests for each item. Cache hits are served immediately without consuming concurrency slots.bool
default:"True"
If
True, persist successful network responses to cache. Automatically disabled when ci_mode=True.bool
default:"True"
If
True, run fetch-path validation on each fetched payload.int | None
default:"None"
Maximum number of retries per request. If
None, uses global config value (default: 3).Note: Each request retries independently. A failed request does not block other requests.int | None
default:"None"
Request timeout in seconds per request. If
None, uses global config value (default: 30).int | None
default:"None"
Maximum number of concurrent network requests. If
None, uses global config value (default: 22).Tuning guide:10-20: Conservative, good for shared networks or rate-limited CDNs20-50: Balanced, good for most use cases50-100: Aggressive, good for dedicated networks and high-throughput scenarios100+: Ultra-aggressive, only for specialized scenarios (for example, telemetry prefetching withtelemetry_prefetch_max_concurrent_requests)
Returns
list[dict[str, Any] | None]
List of parsed JSON dictionaries or
None for failed requests, in the same order as the input requests list.Graceful degradation: Failed requests return None and are logged as warnings. The function never raises exceptions - it always returns a list of the same length as the input.Special case: DataNotFoundError (404) is converted to None without logging a warning. Missing data is often expected (for example, driver did not participate in session).Error Handling
Unlikefetch_json_async, this function does not raise exceptions. All errors are handled gracefully:
- NetworkError: Logged as warning, returns
Nonefor that request - DataNotFoundError (404): Silently returns
None(expected for missing data) - InvalidDataError: Logged as warning, returns
Nonefor that request - Other exceptions: Logged as warning, returns
Nonefor that request
None values.
Examples
Performance Characteristics
- All cache hits: ~1-10ms total (parallel cache reads)
- All cache misses (20 requests): ~200-400ms (HTTP/2 multiplexing + parallel parsing)
- All cache misses (100 requests, 50 concurrent): ~1-2s (batched with semaphore)
- Mixed hits/misses: Proportional to miss rate (cache hits do not consume concurrency slots)
- Linear scaling up to
max_concurrent_requestslimit - Beyond limit, requests are queued and processed in batches
- HTTP/2 multiplexing provides ~3-5x speedup vs sequential fetching
- Optimal concurrency depends on network bandwidth and CDN rate limits
- Each request consumes ~1-10KB for typical payloads
- Telemetry payloads can be 100KB-1MB each
- Peak memory =
max_concurrent_requests × average_payload_size - Example: 50 concurrent × 500KB average = ~25MB peak memory
fetch_with_rate_limit
Parameters
Callable
required
Async function (coroutine function) to execute. Must be an
async def function, not a regular function.Any
Positional arguments to pass to
coro_func.asyncio.Semaphore | None
default:"None"
Optional semaphore for rate limiting. If
None, creates a new semaphore based on max_concurrent_requests config (default: 22).Use cases:- Pass a shared semaphore to coordinate concurrency across multiple operations
- Create a custom semaphore with specific limits for fine-grained control
- Leave as
Nonefor automatic concurrency management
Any
Keyword arguments to pass to
coro_func.Returns
Any
The return value from
coro_func execution. Type depends on what coro_func returns.Raises
Any
Any exception raised by
coro_func is propagated to the caller. No exception handling is performed by this function.Examples
This is a utility function for custom concurrency control. Most users should use
fetch_multiple_async(), which handles rate limiting automatically. Use this function only for fine-grained concurrency control or custom async workflows.When to Use
- Custom async workflows: Building specialized data fetching pipelines
- Shared concurrency limits: Coordinating multiple async operations with a single semaphore
- Fine-grained control: Need precise control over concurrency beyond what
fetch_multiple_asyncprovides - Integration: Integrating tif1 fetching with other async operations in the application
When NOT to Use
- Standard bulk fetching: Use
fetch_multiple_async()instead - Single requests: Use
fetch_json_async()directly - Simple use cases: The higher-level functions handle rate limiting automatically
Resource Management
The async fetch system manages several types of resources that need proper cleanup:- HTTP Session: Persistent connection pool for network requests
- Thread Pool Executor: Worker threads for JSON parsing and blocking operations
- Process Pool Executor: Optional worker processes for CPU-intensive JSON parsing
- Circuit Breaker: Failure tracking state for preventing cascading failures
atexit handlers when the Python process exits. For long-running applications, or for explicit control, use the functions below.
cleanup_resources
Behavior
- Closes the shared HTTP session and releases all connections
- Shuts down the thread pool executor (waits for pending tasks to complete)
- Shuts down the process pool executor if enabled (waits for pending tasks)
- Handles cleanup errors gracefully - logs failures without raising exceptions
- Safe to call multiple times (idempotent)
- Thread-safe (uses locks to prevent concurrent cleanup)
When to Call
- Application shutdown: Call before the application exits
- Test teardown: Call in test cleanup to prevent resource leaks
- Long-running processes: Call periodically when many sessions are created and destroyed
- Resource constraints: Call to release connections or memory
Examples
close_session
When to Use
- Explicit connection management: To force connection closure
- Network changes: After network configuration changes (for example, VPN connect/disconnect)
- Testing: To reset connection state between tests
- Resource constraints: To free up connection pool resources
This only closes the HTTP session. Thread pools and process pools remain active. Use
cleanup_resources() for complete cleanup.close_executor
When to Use
- Thread pool management: To control the thread pool lifecycle
- Resource constraints: To free up thread resources
- Testing: To reset executor state between tests
This only closes the thread pool executor. HTTP session and process pool remain active. Use
cleanup_resources() for complete cleanup.Performance Characteristics
The async fetch system is optimized for:- HTTP/2 multiplexing: Single connection for multiple requests
- Connection pooling: Reuses connections across requests
- Parallel JSON parsing: Offloads JSON parsing to thread pool
- Rate limiting: Prevents overwhelming CDN with concurrent requests
- Automatic retries: Exponential backoff with jitter
- Single lap fetch: ~50-100ms
- 20 driver laps in parallel: ~200-300ms (vs 1-2s sequential)
- Full session telemetry (20 drivers × 50 laps): ~10-15s (vs 50-100s sequential)
Configuration
Async fetch behavior is controlled by global configuration:Error Handling
The async fetch system uses a hierarchy of exceptions:Advanced Usage
Custom concurrency limits
Disable caching for fresh data
Disable validation for performance
Implementation Details
HTTP/2 Multiplexing
HTTP/2 Multiplexing
The async fetch system uses niquests with HTTP/2 support, allowing multiple requests to share a single TCP connection. This reduces latency for parallel requests.
JSON Parsing Strategy
JSON Parsing Strategy
JSON parsing is offloaded to a thread pool executor to avoid blocking the async event loop. A process pool is optional for non-telemetry data. The system uses
orjson for fast parsing and can parse multiple responses in parallel. Telemetry payloads use thread-based parsing to avoid cross-process IPC overhead.Rate Limiting
Rate Limiting
A semaphore-based rate limiter ensures no more than
max_concurrent_requests requests are in flight simultaneously. This prevents overwhelming the CDN and triggering rate limits. The default is 22 concurrent requests, configurable via global config.Retry Logic
Retry Logic
Failed requests are retried with exponential backoff and jitter. The backoff formula is:
min(backoff_factor^attempt + random(0, jitter_max), max_delay) seconds, where defaults are backoff_factor=2.0, jitter_max=1.0, and max_delay=60.0. The system also includes circuit breaker logic to prevent cascading failures and special handling for connection pool exhaustion.