The
core_utils package provides the foundational utilities that power tif1’s high-performance data processing pipeline. While these are primarily internal APIs, they expose powerful capabilities for advanced users who need fine-grained control over data transformations, backend conversions, and performance optimization.This module is the performance engine of tif1, implementing zero-copy operations, optimized JSON parsing, and intelligent DataFrame transformations that enable the library to process millions of rows of telemetry data with minimal overhead.Overview
The core utilities package (tif1.core_utils) is organized into five specialized modules, each addressing a critical aspect of the library’s performance and functionality:
- Backend Conversion (
backend_conversion.py) - Zero-copy DataFrame conversion between pandas and polars using Apache Arrow - JSON Utilities (
json_utils.py) - High-performance JSON parsing with orjson, providing 2-3x faster deserialization - Helper Functions (
helpers.py) - DataFrame manipulation, validation, and data transformation utilities - Constants (
constants.py) - Column name mappings, rename dictionaries, and configuration constants - Resource Manager (
resource_manager.py) - Context manager for guaranteed resource cleanup with LIFO ordering
tif1 to achieve its performance goals while maintaining compatibility with both pandas and polars backends. The design philosophy emphasizes:
- Zero-copy operations wherever possible to minimize memory overhead
- Lazy evaluation and deferred imports to reduce startup time
- Type safety with comprehensive validation and error handling
- Performance-first design with optimized hot paths
- Backend agnostic APIs that work seamlessly with pandas or polars
Module Import Structure
Performance Philosophy
The core_utils module is designed with performance as the primary concern. Every function is optimized for:- Minimal memory allocations: Reuse buffers, avoid unnecessary copies
- Cache-friendly access patterns: Sequential reads, predictable branches
- SIMD-friendly operations: Leverage NumPy/Arrow vectorization
- Lazy computation: Defer work until absolutely necessary
- Zero-copy semantics: Share memory between backends via Arrow
tif1 to load and process a full race weekend (3 practice sessions, qualifying, race) with telemetry for all 20 drivers in under 5 seconds on modern hardware.
Backend Conversion
The backend conversion module provides zero-copy DataFrame transformations between pandas and polars using Apache Arrow as the interchange format. This enables seamless switching between backends without the performance penalty of traditional serialization/deserialization.Architecture
The conversion system leverages:- Apache Arrow as the zero-copy interchange format
- PyArrow extension arrays for pandas to maintain Arrow memory layout
- Lazy polars imports to avoid dependency requirements when not needed
- Automatic fallback to standard conversion if zero-copy fails
pandas_to_polars
pl.from_pandas() with Arrow as the interchange format, avoiding memory copies when possible.
Parameters:
df(pd.DataFrame): pandas DataFrame to convert. Can contain any pandas-supported dtype including nullable types, categoricals, and datetime types.rechunk(bool, optional): Whether to rechunk the resulting polars DataFrame for optimal memory layout. Default isFalseto preserve zero-copy semantics. Set toTrueif you plan to perform many operations on the polars DataFrame and want contiguous memory.
pl.DataFrame: polars DataFrame with equivalent data and schema
ImportError: If polars is not installed in the environmentValueError: If the conversion fails due to incompatible data types or corrupted data
- Time complexity: O(1) for zero-copy conversion (when rechunk=False), O(n) when rechunking
- Memory overhead: Minimal (shares memory with source DataFrame via Arrow)
- Typical performance: ~50ms for 1M rows with zero-copy
polars_to_pandas
df.to_pandas() with PyArrow extension arrays to maintain Arrow memory layout in pandas, enabling zero-copy semantics.
Parameters:
df(pl.DataFrame): polars DataFrame to convert. Supports all polars data types including nested types (List, Struct), categoricals, and temporal types.use_pyarrow(bool, optional): Whether to use PyArrow extension arrays in the resulting pandas DataFrame. Default isTruefor zero-copy conversion. Set toFalseto convert to native pandas dtypes (slower but more compatible with legacy pandas code).
pd.DataFrame: pandas DataFrame with equivalent data and schema
ImportError: If polars is not installed in the environmentValueError: If the conversion fails due to incompatible data types
- Time complexity: O(1) for zero-copy (when use_pyarrow=True), O(n) for native pandas dtypes
- Memory overhead: Minimal with PyArrow arrays, 2x memory usage with native dtypes
- Typical performance: ~100ms for 1M rows with PyArrow
convert_backend
df(DataFrame): DataFrame to convert. Can be eitherpd.DataFrameorpl.DataFrame.target_backend(str): Target backend name. Must be either"pandas"or"polars"(case-sensitive).
DataFrame: DataFrame in the target backend format. If already in the target backend, returns the input DataFrame unchanged (no-op).
ValueError: Iftarget_backendis not “pandas” or “polars”, or if the conversion failsImportError: If polars is not installed and target is “polars”
- No-op detection: O(1) type check to avoid unnecessary conversions
- Conversion time: Same as
pandas_to_polarsorpolars_to_pandas - Memory efficient: Uses zero-copy conversion internally
JSON Utilities
The JSON utilities module provides high-performance JSON parsing and serialization using orjson, a fast, correct JSON library for Python written in Rust. This module is critical fortif1’s performance as it handles parsing of large JSON payloads from the CDN containing lap data, telemetry, and race control messages.
Why orjson?
orjson provides significant advantages over Python’s standard libraryjson module:
- 2-3x faster parsing: Rust-based implementation with SIMD optimizations
- Lower memory usage: Efficient memory allocation and reuse
- Native bytes support: Parse directly from HTTP response bodies without decoding
- NumPy integration: Automatic handling of NumPy types during serialization
- Strict correctness: Validates JSON spec compliance
Architecture
The JSON utilities module implements a fallback strategy:- Primary: Use orjson for maximum performance
- Fallback: Use stdlib json if orjson fails (rare edge cases)
- Automatic: No user configuration required
json_loads
payload(str | bytes | bytearray | memoryview): JSON data to parse. Accepts multiple input types for flexibility:str: Standard JSON string (UTF-8 encoded)bytes: Raw bytes from HTTP responses (most efficient)bytearray: Mutable byte arraymemoryview: Zero-copy view of bytes (converted to bytes internally)
Any: Parsed Python object. Common return types:dict: JSON objects{}list: JSON arrays[]str,int,float,bool,None: JSON primitives
json.JSONDecodeError: If the payload is not valid JSON (from fallback parser)ValueError: If the payload is malformed (from orjson)
- Time complexity: O(n) where n is the payload size
- Memory overhead: Minimal (orjson uses efficient allocation)
- Typical performance:
- Small payloads (<1KB): ~10μs
- Medium payloads (100KB): ~2ms
- Large payloads (10MB): ~180ms
json_dumps
data(Any): Python object to serialize. Supported types:dict,list: Collectionsstr,int,float,bool,None: Primitivesdatetime,date,time: Temporal types (ISO 8601 format)UUID: Converted to stringnumpytypes: Automatically converted to Python equivalentsdataclasses,pydantic models: Serialized to dict
str: JSON string representation of the data
TypeError: If the data contains non-serializable types (e.g., custom classes without__dict__)
- Time complexity: O(n) where n is the data size
- Memory overhead: Minimal (efficient string building)
- Typical performance:
- Small objects (<1KB): ~5μs
- Medium objects (100KB): ~1ms
- Large objects (10MB): ~100ms
json_dumps uses orjson for 2-3x faster serialization than stdlib json. It automatically handles NumPy types, datetime objects, and other common Python types without requiring custom encoders.parse_response_json
response(Any): HTTP response object. Should have either:.contentattribute (bytes): Preferred for performance.json()method: Fallback for compatibility
Any: Parsed Python object from the response JSON body
json.JSONDecodeError: If the response body is not valid JSONAttributeError: If the response object has neither.contentnor.json()
- Optimized path: Parse from
.contentbytes using orjson (~180ms for 10MB) - Fallback path: Use
.json()method (~450ms for 10MB) - Speedup: 2-3x faster than calling
.json()directly
JSON Utilities Best Practices
- Use bytes when possible: Pass
response.contenttojson_loadsinstead ofresponse.text - Prefer parse_response_json: Use
parse_response_json()for HTTP responses - Trust the fallback: The automatic fallback to stdlib json ensures reliability
- Benchmark your use case: Profile your specific JSON payloads to measure impact
- Handle errors gracefully: Catch
json.JSONDecodeErrorfor malformed JSON
- ❌
json.loads(response.text)- Slow (decode + parse) - ❌
response.json()- Slower (uses stdlib json) - ✅
json_loads(response.content)- Fast (direct bytes parse) - ✅
parse_response_json(response)- Fastest (optimized path)
Helper Functions
The helpers module (helpers.py) provides a comprehensive suite of utility functions for DataFrame manipulation, validation, and data transformation. These functions are the workhorses of tif1’s data processing pipeline, handling everything from input validation to complex DataFrame operations across both pandas and polars backends.
Design Philosophy
The helper functions follow these core principles:- Backend Agnostic: All functions work seamlessly with both pandas and polars DataFrames
- Zero-Copy Optimization: Minimize memory allocations and avoid unnecessary data copies
- Type Safety: Comprehensive validation with clear error messages
- Performance First: Optimized hot paths for common operations
- Defensive Programming: Handle edge cases gracefully with fallback strategies
Validation Functions
Input validation is critical for data integrity and user experience. The validation helpers provide comprehensive checks with informative error messages._validate_year
year(int): Year to validate. Must be an integer representing a calendar year.min_year(int): Minimum supported year (inclusive). Typically 2018 fortif1.max_year(int): Maximum supported year (inclusive). Typically the current year + 1 for future scheduled races.
ValueError: If year is outside the range[min_year, max_year]. Error message includes the valid range and the invalid value provided.
- Time complexity: O(1) - simple integer comparison
- Typical execution: <1μs
This validation is performed early in the data loading pipeline to fail fast and provide clear feedback before any network requests are made.
_validate_drivers_list
drivers(list[str] | None): List of driver codes to validate. Each code should be a non-empty string (typically 3-letter abbreviations like “VER”, “HAM”, “LEC”). Can beNoneto indicate no filtering.
TypeError: Ifdriversis not a list or None. Error message includes the actual type received.ValueError: If the list is empty, or if any element is not a non-empty string. Error messages are specific to the validation failure.
- Must be a list type (not tuple, set, or other iterable)
- Cannot be an empty list (use
Noneinstead to indicate “all drivers”) - All elements must be strings
- All strings must be non-empty (no empty strings or whitespace-only strings)
- Time complexity: O(n) where n is the number of drivers
- Typical execution: <10μs for 20 drivers
_validate_lap_number
lap_number(int): Lap number to validate. Must be a positive integer (>= 1).
TypeError: Iflap_numberis not an integer. Error message includes the actual type received.ValueError: Iflap_numberis zero or negative. Error message includes the invalid value.
- Must be an integer type (not float, string, or other numeric type)
- Must be positive (>= 1)
- Time complexity: O(1) - simple type check and comparison
- Typical execution: <1μs
_validate_string_param
param(str): String parameter to validate. Must be a non-empty string with at least one non-whitespace character.param_name(str): Human-readable parameter name for error messages. Used to provide context in error messages (e.g., “gp”, “session_type”, “driver”).
TypeError: Ifparamis not a string. Error message includes the parameter name and actual type received.ValueError: Ifparamis empty or contains only whitespace. Error message includes the parameter name.
- Must be a string type (not int, None, or other type)
- Cannot be empty string
"" - Cannot be whitespace-only (e.g.,
" ","\t","\n")
- Time complexity: O(n) where n is the string length (for whitespace check)
- Typical execution: <5μs for typical parameter lengths
This function uses
.strip() to check for whitespace-only strings, ensuring that parameters like " " are rejected even though they have non-zero length.URL Encoding
_encode_url_component
component(str): String to encode. Can contain any Unicode characters, spaces, or special characters.
str: URL-encoded string with all special characters percent-encoded (e.g., space becomes%20,&becomes%26).
- Uses
@lru_cache(maxsize=1024)for memoization - Repeated calls with the same input return cached results instantly
- Cache size of 1024 is sufficient for typical usage (GP names, session types, etc.)
- Time complexity: O(n) for first call, O(1) for cached calls
- Typical execution:
- First call: ~10μs
- Cached call: <1μs (cache lookup)
DataFrame Utility Functions
These functions provide backend-agnostic operations for DataFrame manipulation, enabling seamless work with both pandas and polars._is_empty_df
df: DataFrame-like object to check. Can bepd.DataFrame,pl.DataFrame, or any object with.emptyor.is_empty()attributes.lib(str): Backend library name ("pandas"or"polars"). Used as a hint for optimization, but the function also performs runtime type checking.
bool:Trueif the DataFrame is empty (zero rows),Falseotherwise.
- Type-based detection: Check
isinstance(df, pd.DataFrame)orisinstance(df, pl.DataFrame) - Attribute-based detection: Check for
.empty(pandas) or.is_empty()(polars) - Fallback: Use
len(df) == 0as last resort
- Time complexity: O(1) - all checks are constant time
- Typical execution: <1μs
This function prefers concrete type checking over the
lib parameter because some code paths can surface pandas DataFrames even when the configured backend is polars (e.g., during backend conversion)._create_empty_df
lib(str): Backend library name. Must be either"pandas"or"polars".
pd.DataFrameiflib == "pandas"pl.DataFrameiflib == "polars"and polars is availablepd.DataFrameas fallback if polars is requested but not installed
- Time complexity: O(1) - creates empty structure
- Typical execution: <10μs
_filter_valid_laptimes
LapTimeSeconds column for analysis. This function is critical for data quality, removing invalid laps (pit laps, out laps, deleted laps) and providing a consistent numeric representation of lap times.
Parameters:
laps: Laps DataFrame (pandas or polars). Must contain aLapTimecolumn.lib(str): Backend library name ("pandas"or"polars").
- Filtered DataFrame with:
- Only rows where
LapTimeis valid (not null/NaN) - New
LapTimeSecondscolumn containing lap time as float (seconds) - For pandas:
LapTimeconverted totimedelta64[ns]dtype - For polars:
LapTimekept as original type,LapTimeSecondsadded as Float64
- Only rows where
- Pandas: Converts
LapTimetotimedelta64[ns]and createsLapTimeSecondsas float - Polars: Casts
LapTimeto Float64 (non-strict) and aliases asLapTimeSeconds - Optimization: Minimizes copies by filtering before copying (pandas) or using lazy operations (polars)
- Time complexity: O(n) where n is the number of laps
- Memory overhead: Minimal (single column addition)
- Typical execution: ~5ms for 1000 laps
This function is called automatically during session loading. The
LapTimeSeconds column is essential for numerical analysis, sorting, and filtering operations that require numeric comparison._rename_columns
df: DataFrame to rename (pandas or polars)rename_map(dict): Mapping of old column names to new names. UseNoneas the value to drop a column.lib(str): Backend library name ("pandas"or"polars")
- DataFrame with renamed columns. Columns mapped to
Noneare dropped.
- Duplicate prevention: Skips renames that would create duplicate column names
- Drop columns: Columns mapped to
Noneare removed from the DataFrame - No-op renames: Skips renames where source == target (e.g.,
{"Driver": "Driver"}) - Existing columns: Avoids conflicts when target name already exists independently
- Time complexity: O(n) where n is the number of columns
- Memory overhead: Minimal (column metadata only, no data copy)
- Typical execution: <1ms for typical DataFrames
_apply_categorical
df: DataFrame to modify (pandas or polars)cols(list): List of column names to convert to categoricallib(str): Backend library name ("pandas"or"polars")
- DataFrame with specified columns converted to categorical dtype
- Memory savings: 50-90% reduction for columns with low cardinality
- Faster operations: Comparisons and groupby operations are faster
- Preserved semantics: String operations still work as expected
- Time complexity: O(n × m) where n is rows and m is number of categorical columns
- Memory savings: Typically 70-80% for Driver, Team, Compound columns
- Typical execution: ~10ms for 1000 laps with 4 categorical columns
The library automatically applies categorical dtype to
Driver, Team, Compound, and TrackStatus columns during session loading. This optimization is transparent to users but provides significant memory and performance benefits.Constants
Column name mappings and constants used throughout the library.Column rename maps
The constants module defines mappings for renaming columns from CDN format to user-facing format:Standard column order
Constants
The constants module (constants.py) defines all column name mappings, configuration values, and standard column orders used throughout the library. These constants ensure consistency across the codebase and provide FastF1 compatibility.
Year Range Constants
Cache Configuration
Column Rename Maps
The library defines comprehensive column rename mappings to transform CDN data format to user-facing FastF1-compatible format. These mappings handle both verbose and abbreviated column names from different CDN data versions.Lap Data Rename Map
Telemetry Rename Map
Race Control Messages Rename Map
Weather Data Rename Map
Categorical Columns
- Driver: ~70% reduction (20 unique values in ~1000 laps)
- Team: ~75% reduction (10 unique values)
- Compound: ~80% reduction (3-5 unique values)
- TrackStatus: ~85% reduction (2-4 unique values)
Standard Column Names
FastF1 Column Order
- Identification columns (index, driver, time)
- Core lap data (lap time, number, stint)
- Detailed timing (sectors, speed traps)
- Metadata (tyres, position, flags)
- Weather data
- tif1-specific additions
Resource Manager
The resource manager module (resource_manager.py) provides a robust pattern for managing resources with guaranteed cleanup, even when initialization fails partway through. This is critical for preventing resource leaks in error scenarios.
Architecture
TheResourceManager class implements the context manager protocol and tracks resources in a LIFO (Last-In-First-Out) stack. When cleanup occurs, resources are cleaned up in reverse order of creation, ensuring that dependencies are respected.
Key Features:
- LIFO cleanup order: Resources cleaned up in reverse order of registration
- Error resilience: Cleanup continues even if individual resources fail to close
- Comprehensive logging: All cleanup operations are logged for debugging
- Multiple cleanup methods: Supports both
.close()and.shutdown()methods
ResourceManager
Methods
_register_resource
name(str): Human-readable name for the resource (used in logging). Should be descriptive for debugging purposes.resource(Any): The resource object to track. Should have a.close()or.shutdown()method for cleanup.
_cleanup_resources
- Iterate through resources in reverse order
- For each resource, try
.close()method first - If no
.close(), try.shutdown(wait=True)method - If neither exists, skip the resource
- Log any errors but continue cleanup
- Clear the resource list after all cleanup attempts
Usage Patterns
Basic Context Manager Usage
Error Handling During Initialization
Manual Cleanup
Logging
The ResourceManager provides comprehensive logging for debugging:Error Resilience
If cleanup fails for one resource, other resources are still cleaned up:ResourceManager is used internally by the library for managing HTTP sessions, cache connections, and other resources. Most users don’t need to interact with it directly, but it’s available for advanced use cases requiring custom resource management.
Performance Considerations
JSON Parsing
The library uses orjson for JSON parsing, which provides:- 2-3x faster parsing than stdlib json
- Lower memory usage
- Native support for bytes input
- Automatic handling of numpy types
Lib Conversion
When converting between backends:- pandas → polars: Uses PyArrow for zero-copy when possible
- polars → pandas: Uses PyArrow by default for efficiency
- Rechunking: Optional for polars to optimize memory layout
Advanced Usage
Custom lib conversion
Custom JSON Processing
Best Practices
- Use orjson for JSON: Always use
json_loads/json_dumpsfor performance - Prefer PyArrow conversion: Keep
use_pyarrow=Truefor lib conversion - Validate early: Use validation helpers to catch errors early
- Let the library handle resources: ResourceManager is automatic
- Use constants for column names: Reference standard column names from constants
Summary
The core_utils package provides:- High-performance JSON parsing with orjson
- Efficient lib conversion (pandas ↔ polars)
- Data validation utilities
- Column name standardization
- Resource management
- Internal helpers for DataFrame operations