Initial commit with Poetry

This commit is contained in:
Samuel Roach 2022-06-01 14:31:52 +01:00
commit bba67271e9
31 changed files with 2040 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Common Ignores
.vscode
__pycache__
# Personal Ignores
# Mostly notes and testing
dist

149
README.rst Normal file
View File

@ -0,0 +1,149 @@
# Ergast-Py
![Stars](https://img.shields.io/github/stars/Samuel-Roach/ergast-py?color=purple&style=for-the-badge) ![Size](https://img.shields.io/github/languages/code-size/Samuel-Roach/ergast-py?style=for-the-badge) ![Commits](https://img.shields.io/github/commit-activity/m/Samuel-Roach/ergast-py?color=orange&style=for-the-badge)
A comprehensive Python wrapper for the Ergast API. Built for easy use and functionality, Ergast-py aims to bring the Ergast API into the Python network as seemlessly as possible.
## What is Ergast?
[Ergast](http://ergast.com/mrd/) is a free, experimental API for accessing motor-racing data, dating back to the beginning of World Championships in 1950. The website provides plenty of detail into how effective the API can be, and the many options that are available for data gathering using it.
## Why should I use Ergast-Py?
Ergast-Py provides a clean, Python orientated wrapper for this API. It has been designed to remove the heavy lifting of handling the API formatting behind the scenes, allowing developers to easily access the data that is relevant to them. All the data is conformed into clean class code, allowing for users to keep a Python-centric mindset whilst developing.
## How to install
Ergast-py is part of PyPi, so can be installed with the pip command:
```
pip install ergast-py
```
## Usage
Once ergast-py is installed on your system you can then begin using the library in querying the ergast API. To begin, initialise an instance of the ``Ergast()`` class.
```python
from ergast-py import Ergast
e = Ergast()
```
Queries can then be built up with function calls in a sequential manner. Once you've built up a query, finally define what data you wish to get using a ``get_xyz()`` function.
```python
# http://ergast.com/api/f1/2008/5/results
race_results = e.season(2008).round(5).get_results()
# http://ergast.com/api/f1/drivers/massa
felipe_massa = e.driver_str("massa").get_drivers()
# http://ergast.com/api/f1/current/constructorStandings/3
constructor_standings = e.season().standing(3).get_constructor_standings()
```
## Structure and Types
Ergast-py has many models which allow the user to more effectively use and manipulate the data available to them through Ergast. All models of data are available through ``.models.xyz``.
<details>
<summary>Available models in ergast-py</summary>
</br>
| Name | Description |
| --------------------- | --------------------------------------------------------- |
| AverageSpeed | The average speed achieved during a fastest lap |
| Circuit | Details about a Formula One circuit |
| ConstructorStanding | A single constructor's representation in the standings |
| Constructor | A Formula One constructor |
| DriverStanding | A single driver's representation in the standings |
| Driver | A Formula One driver |
| FastestLap | A fastest lap achieved by a driver |
| Lap | Details about a drivers lap |
| Location | The position of a circuit |
| PitStop | Details about a driver's pit stop |
| Race | Full representation of a Formula One race |
| Result | Details about a driver's result |
| Season | Details about a Formula One season |
| StandingsList | A list of standings; constructors or drivers |
| Status | Details about the final status of a driver in a race |
| Timing | Details about the timings of a driver during a lap |
</details>
## Query building
There are 3 types of query available in the ``Ergast()`` class. <b>FILTER</b> functions build up the query, by filtering down the data that you will receive. <b>PAGING</b> functions control the flow of data if there is excessive amounts, limiting it to not overflow the API. <b>RETURN</b> functions detail what type of data you're expecting back from the query.
The order of the builder functions is inconsequential, however the final function called should be a <i>return</i> function.
```
Ergast().{paging/filter}.{return}
```
More detail on the available functions within the ``Ergast()`` class is available below.
<details>
<summary><b>FILTER</b> functions</summary>
</br>
| Name | Arguments | Notes |
| --------------- | ------------------------ | ------------------------------------------------------------------------------ |
| season | year: int | If you call season with no arguments it will default to the current season |
| round | round: int | If you call round with no arguments it will default to the last round |
| driver | driver: Driver | The Driver equivalent of ``driver_str`` |
| driver_str | driver: str | The String equivalent of ``driver``. Must use driver's driverId |
| constructor | constructor: Constructor | The Constructor equivalent of ``constructor_str`` |
| constructor_str | constructor: str | The String equivalent of ``constructor``. Must use constructor's constructorId |
| qualifying | position: int | Position at the <i>end</i> of qualifying |
| sprint | position: int | |
| grid | position: int | Position lined up on the grid |
| result | position: int | |
| fastest | position: int | Ranking in list of each drivers fastest lap |
| circuit | circuit: Circuit | The Circuit equivalent of ``circuit_str`` |
| circuit_str | circuit: str | The String equivalent of ``circuit``. Must use circuit's circuitId |
| status | status: int | The Integer equivalent of ``status_string``. Must use statusId |
| status_str | status: str | The String equivalent of ``status`` |
| standing | position: int | Position of Driver or Constructor in standing |
| lap | lap_number: int | |
| pit_stop | stop_number: int | |
</details>
<details>
<summary><b>PAGING</b> functions</summary>
</br>
| Name | Arguments |
| ---------------- | ----------- |
| limit | amount: int |
| offset | amount: int |
</details>
<details>
<summary><b>RETURN</b> functions</summary>
</br>
| Name | Return Type |
| ------------------------- | --------------------- |
| get_circuits | list[Circuit] |
| get_constructors | list[Constructor] |
| get_drivers | list[Driver] |
| get_qualifying | list[Race] |
| get_sprints | list[Race] |
| get_results | list[Race] |
| get_races | list[Race] |
| get_seasons | list[Season] |
| get_statuses | list[Status] |
| get_driver_standings | list[StandingsList] |
| get_constructor_standings | list[StandingsList] |
| get_laps | list[Race] |
| get_pit_stops | list[Race] |
</details>
## Credits
This library would not be possible without the freely available [Ergast](http://ergast.com/mrd/) API. For full information about the API and it's responsible use, please refer to their website.

1
ergast_py/__init__.py Normal file
View File

@ -0,0 +1 @@
__version__ = '0.1.0'

View File

View File

@ -0,0 +1,139 @@
class Expected():
"""
Storage of the expected keys returned and their types
"""
location = {
"lat": "float",
"long": "float",
"locality": "string",
"country": "string",
}
circuit = {
"circuitId": "string",
"url": "string",
"circuitName": "string",
"Location": "string",
}
constructor = {
"constructorId": "string",
"url": "string",
"name": "string",
"nationality": "string",
}
driver = {
"driverId": "string",
"permanentNumber": "int",
"code": "string",
"url": "string",
"givenName": "string",
"familyName": "string",
"dateOfBirth": "string",
"nationality": "string",
}
race = {
"season": "int",
"round": "int",
"url": "string",
"raceName": "string",
"Circuit": "dict",
"date": "string",
"time": "string",
"Results": "dict",
"FirstPractice": "dict",
"SecondPractice": "dict",
"ThirdPractice": "dict",
"Sprint": "dict",
"SprintResults": "dict",
"Qualifying": "dict",
"QualifyingResults": "dict",
"PitStops": "dict",
"Laps": "dict",
}
result = {
"number": "int",
"position": "int",
"positionText": "string",
"points": "float",
"Driver": "dict",
"Constructor": "dict",
"grid": "int",
"laps": "int",
"status": "string",
"Time": "dict",
"FastestLap": "dict",
"Q1": "string",
"Q2": "string",
"Q3": "string",
}
fastest_lap = {
"rank": "int",
"lap": "int",
"Time": "dict",
"AverageSpeed": "dict",
}
average_speed = {
"units": "string",
"speed": "float",
}
pit_stop = {
"driverId": "string",
"lap": "int",
"stop": "int",
"time": "string",
"duration": "string",
}
lap = {
"number": "int",
"Timings": "dict",
}
timing = {
"driverId": "string",
"position": "int",
"time": "string",
}
season = {
"season": "int",
"url": "string",
}
status = {
"statusId": "int",
"count": "int",
"status": "string"
}
driver_standing = {
"position": "int",
"positionText": "string",
"points": "float",
"wins": "int",
"Driver": "dict",
"Constructors": "dict"
}
constructor_standing = {
"position": "int",
"positionText": "string",
"points": "float",
"wins": "int",
"Constructor": "dict"
}
standings_list = {
"season": "int",
"round": "int",
"DriverStandings": "dict",
"ConstructorStandings": "dict"
}

View File

@ -0,0 +1,142 @@
class StatusType():
"""
A mapping of String onto the Status ID for that type
"""
status_map = {
"": 0,
"Finished": 1,
"Disqualified": 2,
"Accident": 3,
"Collision": 4,
"Engine": 5,
"Gearbox": 6,
"Transmission": 7,
"Clutch": 8,
"Hydraulics": 9,
"Electrical": 10,
"+1 Lap": 11,
"+2 Laps": 12,
"+3 Laps": 13,
"+4 Laps": 14,
"+5 Laps": 15,
"+6 Laps": 16,
"+7 Laps": 17,
"+8 Laps": 18,
"+9 Laps": 19,
"Spun off": 20,
"Radiator": 21,
"Suspension": 22,
"Brakes": 23,
"Differential": 24,
"Overheating": 25,
"Mechanical": 26,
"Tyre": 27,
"Driver Seat": 28,
"Puncture": 29,
"Driveshaft": 30,
"Retired": 31,
"Fuel pressure": 32,
"Front wing": 33,
"Water pressure": 34,
"Refuelling": 35,
"Wheel": 36,
"Throttle": 37,
"Steering": 38,
"Technical": 39,
"Electronics": 40,
"Broken wing": 41,
"Heat shield fire": 42,
"Exhaust": 43,
"Oil leak": 44,
"+11 Laps": 45,
"Wheel rim": 46,
"Water leak": 47,
"Fuel pump": 48,
"Track rod": 49,
"+17 Laps": 50,
"Oil pressure": 51,
"+13 Laps": 53,
"Withdrew": 54,
"+12 Laps": 55,
"Engine fire": 56,
"+26 Laps": 58,
"Tyre puncture": 59,
"Out of fuel": 60,
"Wheel nut": 61,
"Not classified": 62,
"Pneumatics": 63,
"Handling": 64,
"Rear wing": 65,
"Fire": 66,
"Wheel bearing": 67,
"Physical": 68,
"Fuel system": 69,
"Oil line": 70,
"Fuel rig": 71,
"Launch control": 72,
"Injured": 73,
"Fuel": 74,
"Power loss": 75,
"Vibrations": 76,
"107% Rule": 77,
"Safety": 78,
"Drivetrain": 79,
"Ignition": 80,
"Did not qualify": 81,
"Injury": 82,
"Chassis": 83,
"Battery": 84,
"Stalled": 85,
"Halfshaft": 86,
"Crankshaft": 87,
"+10 Laps": 88,
"Safety concerns": 89,
"Not restarted": 90,
"Alternator": 91,
"Underweight": 92,
"Safety belt": 93,
"Oil pump": 94,
"Fuel leak": 95,
"Excluded": 96,
"Did not prequalify": 97,
"Injection": 98,
"Distributor": 99,
"Driver unwell": 100,
"Turbo": 101,
"CV joint": 102,
"Water pump": 103,
"Fatal accident": 104,
"Spark plugs": 105,
"Fuel pipe": 106,
"Eye injury": 107,
"Oil pipe": 108,
"Axle": 109,
"Water pipe": 110,
"+14 Laps": 111,
"+15 Laps": 112,
"+25 Laps": 113,
"+18 Laps": 114,
"+22 Laps": 115,
"+16 Laps": 116,
"+24 Laps": 117,
"+29 Laps": 118,
"+23 Laps": 119,
"+21 Laps": 120,
"Magneto": 121,
"+44 Laps": 122,
"+30 Laps": 123,
"+19 Laps": 124,
"+46 Laps": 125,
"Supercharger": 126,
"+20 Laps": 127,
"+42 Laps": 128,
"Engine misfire": 129,
"Collision damage": 130,
"Power Unit": 131,
"ERS": 132,
"Brake duct": 135,
"Seat": 136,
"Damage": 137,
"Debris": 138,
"Illness": 139,
}

224
ergast_py/ergast.py Normal file
View File

@ -0,0 +1,224 @@
from __future__ import annotations
from ergast_py.models.driver import Driver
from ergast_py.models.circuit import Circuit
from ergast_py.models.constructor import Constructor
from ergast_py.models.standings_list import StandingsList
from ergast_py.models.status import Status
from ergast_py.constants.status_type import StatusType
from ergast_py.models.season import Season
from ergast_py.models.race import Race
from ergast_py.models.lap import Lap
from ergast_py.models.pit_stop import PitStop
from ergast_py.requester import Requester
from ergast_py.type_constructor import TypeConstructor
class Ergast():
"""
Class for querying the Ergast API
"""
def __init__(self) -> None:
self.reset()
self.requester = Requester()
self.type_constructor = TypeConstructor()
def reset(self) -> None:
self.params = {
"season": None,
"seasons": None,
"round": None,
"driver": None,
"constructor": None,
"grid": None,
"qualifying": None,
"sprint": None,
"result": None,
"fastest": None,
"circuit": None,
"status": None,
"standing": None,
"races": None,
"limit": None,
"offset": None,
"lap": None,
"pit_stop": None,
}
#
# FILTER FUNCTIONS
#
def season(self, year: int="current") -> Ergast:
self.params["season"] = year
return self
def round(self, round: int="last") -> Ergast:
self.params["round"] = round
return self
def driver(self, driver: Driver) -> Ergast:
self.params["driver"] = driver.driverId
return self
def driver_str(self, driver: str) -> Ergast:
self.params["driver"] = driver
return self
def constructor(self, constructor: Constructor) -> Ergast:
self.params["constructor"] = constructor.constructorId
return self
def constructor_str(self, constructor: str) -> Ergast:
self.params["constructor"] = constructor
return self
def qualifying(self, position: int) -> Ergast:
self.params["qualifying"] = position
return self
def sprint(self, position: int) -> Ergast:
self.params["sprint"] = position
return self
def grid(self, position: int) -> Ergast:
self.params["grid"] = position
return self
def result(self, position: int) -> Ergast:
self.params["result"] = position
return self
def fastest(self, position: int) -> Ergast:
self.params["fastest"] = position
return self
def circuit(self, circuit: Circuit) -> Ergast:
self.params["circuit"] = circuit.circuitId
return self
def circuit_str(self, circuit: str) -> Ergast:
self.params["circuit"] = circuit
return self
def status(self, status: int) -> Ergast:
self.params["status"] = status
return self
def status_str(self, status: str) -> Ergast:
self.params["status"] = StatusType().status_map[status]
return self
def standing(self, position: int) -> Ergast:
self.params["standing"] = position
return self
def lap(self, lap_number: int) -> Ergast:
self.params["lap"] = lap_number
return self
def pit_stop(self, stop_number: int) -> Ergast:
self.params["pit_stop"] = stop_number
return self
#
# PAGING FUNCTIONS
#
def limit(self, amount: int) -> Ergast:
self.params["limit"] = amount
return self
def offset(self, amount: int) -> Ergast:
self.params["offset"] = amount
return self
#
# RETURN FUNCTIONS
#
# Race and Results Queries
def get_circuits(self) -> list[Circuit]:
circuits_json = self.requester.get_circuits(self.params)
circuits = self.type_constructor.construct_circuits(circuits_json)
self.reset()
return circuits
def get_constructors(self) -> list[Constructor]:
constructors_json = self.requester.get_constructors(self.params)
constructors = self.type_constructor.construct_constructors(constructors_json)
self.reset()
return constructors
def get_drivers(self) -> list[Driver]:
drivers_json = self.requester.get_drivers(self.params)
drivers = self.type_constructor.construct_drivers(drivers_json)
self.reset()
return drivers
def get_qualifying(self) -> list[Race]:
qualify_json = self.requester.get_qualifying(self.params)
qualifying = self.type_constructor.construct_races(qualify_json)
self.reset()
return qualifying
def get_sprints(self) -> list[Race]:
sprint_json = self.requester.get_sprints(self.params)
sprint = self.type_constructor.construct_races(sprint_json)
self.reset()
return sprint
def get_results(self) -> list[Race]:
results_json = self.requester.get_results(self.params)
results = self.type_constructor.construct_races(results_json)
self.reset()
return results
def get_races(self) -> list[Race]:
races_json = self.requester.get_races(self.params)
races = self.type_constructor.construct_races(races_json)
self.reset()
return races
def get_seasons(self) -> list[Season]:
seasons_json = self.requester.get_seasons(self.params)
seasons = self.type_constructor.construct_seasons(seasons_json)
self.reset()
return seasons
def get_statuses(self) -> list[Status]:
statuses_json = self.requester.get_statuses(self.params)
statuses = self.type_constructor.construct_statuses(statuses_json)
self.reset()
return statuses
# Standings Queries
def get_driver_standings(self) -> list[StandingsList]:
standings_lists_json = self.requester.get_driver_standings(self.params)
standings_lists = self.type_constructor.construct_standings_lists(standings_lists_json)
self.reset()
return standings_lists
def get_constructor_standings(self) -> list[StandingsList]:
standings_lists_json = self.requester.get_constructor_standings(self.params)
standings_lists = self.type_constructor.construct_standings_lists(standings_lists_json)
self.reset()
return standings_lists
# Laps and Pit Stops Queries
def get_laps(self) -> list[Race]:
laps_json = self.requester.get_laps(self.params)
laps = self.type_constructor.construct_races(laps_json)
self.reset()
return laps
def get_pit_stops(self) -> list[Race]:
pit_stops_json = self.requester.get_pit_stops(self.params)
pit_stops = self.type_constructor.construct_races(pit_stops_json)
self.reset()
return pit_stops

45
ergast_py/helpers.py Normal file
View File

@ -0,0 +1,45 @@
import datetime
from ergast_py.constants.status_type import StatusType
class Helpers:
def construct_datetime_str(self, date: str, time: str) -> datetime.datetime:
new_datetime = datetime.datetime.strptime(f"{date} {time}", "%Y-%m-%d %H:%M:%SZ")
new_datetime = new_datetime.replace(tzinfo=datetime.timezone.utc)
return new_datetime
def construct_datetime_dict(self, dict: dict) -> datetime.datetime:
if "date" not in dict or "time" not in dict:
return None
return self.construct_datetime_str(dict["date"], dict["time"])
def construct_date(self, date: str) -> datetime.date:
elements = date.split("-")
return datetime.date(year=int(elements[0]), month=int(elements[1]), day=int(elements[2]))
def construct_lap_time_millis(self, millis: dict) -> datetime.time:
if "millis" in millis:
value = int(millis["millis"])
return datetime.datetime.fromtimestamp(value/1000.0).time()
return None
def format_lap_time(self, time: str) -> datetime.time:
if time != "":
return datetime.datetime.strptime(time, "%M:%S.%f").time()
return None
def construct_lap_time(self, time: dict) -> datetime.time:
if "time" in time:
value = time["time"]
return self.format_lap_time(value)
return None
def construct_local_time(self, time: str) -> datetime.time:
if "time" != "":
return datetime.datetime.strptime(f"{time}", "%H:%M:%S").time()
return None
def construct_pitstop_duration(self, time: str) -> datetime.time:
if "time" != "":
return datetime.datetime.strptime(f"{time}", "%S.%f").time()
return None

View File

View File

@ -0,0 +1,21 @@
from dataclasses import dataclass
@dataclass
class AverageSpeed:
"""
Representation of a Drivers Average Speed
Average Speeds may contain:
units: String
speed: Float
"""
def __init__(self, units: str, speed: float) -> None:
self.units = units
self.speed = speed
pass
def __str__(self):
return f"AverageSpeed(units={self.units}, speed={self.speed})"
def __repr__(self):
return f"AverageSpeed(units={self.units}, speed={self.speed})"

View File

@ -0,0 +1,26 @@
from dataclasses import dataclass
from ergast_py.models.location import Location
@dataclass
class Circuit:
"""
Representation of a Formula One Circuit
Circuits may contain:
circuitId: String
url: String
circuitName: String
location: Location
"""
def __init__(self, circuitId: str, url: str, circuitName: str, location: Location) -> None:
self.circuitId = circuitId
self.url = url
self.circuitName = circuitName
self.location = location
pass
def __str__(self):
return f"Circuit(circuitId={self.circuitId}, url={self.url}, circuitName={self.circuitName}, location={self.location})"
def __repr__(self):
return f"Circuit(circuitId={self.circuitId}, url={self.url}, circuitName={self.circuitName}, location={self.location})"

View File

@ -0,0 +1,25 @@
from dataclasses import dataclass
@dataclass
class Constructor:
"""
Representation of a Formula One Team
Constructors may contain:
constructorId: String
url: String
name: String
nationality: String
"""
def __init__(self, constructorId: str, url: str, name: str, nationality: str) -> None:
self.constructorId = constructorId
self.url = url
self.name = name
self.nationality = nationality
pass
def __str__(self):
return f"Constructor(constructorId={self.constructorId}, url={self.url}, name={self.name}, nationality={self.nationality})"
def __repr__(self):
return f"Constructor(constructorId={self.constructorId}, url={self.url}, name={self.name}, nationality={self.nationality})"

View File

@ -0,0 +1,29 @@
from dataclasses import dataclass
from ergast_py.models.constructor import Constructor
@dataclass
class ConstructorStanding:
"""
Representation of a Formula One Constructor's standing in a Season
Constructor Standings may contain:
position: Integer
positionText: String
points: Float
wins: Integer
constructor: Constructor
"""
def __init__(self, position: int, positionText: str, points: float, wins: int, constructor: Constructor) -> None:
self.position = position
self.positionText = positionText
self.points = points
self.wins = wins
self.constructor = constructor
pass
def __str__(self):
return f"ConstructorStanding(position={self.position}, positionText={self.positionText}, points={self.points}, wins={self.wins}, constructor={self.constructor})"
def __repr__(self):
return f"ConstructorStanding(position={self.position}, positionText={self.positionText}, points={self.points}, wins={self.wins}, constructor={self.constructor})"

View File

@ -0,0 +1,37 @@
from dataclasses import dataclass
from ergast_py.helpers import Helpers
import datetime
@dataclass
class Driver:
"""
Representation of a Formula One driver
Drivers may contain:
driverId: String
permanentNumber: Integer
code: String
url: String
givenName: String
familyName: String
dateOfBirth: datetime.date
nationality: String
"""
def __init__(self, driverId: str, code: str, url: str, givenName: str, familyName: str, dateOfBirth: datetime.date,
nationality: str, permanentNumber: int) -> None:
self.driverId = driverId
self.permanentNumber = permanentNumber
self.code = code
self.url = url
self.givenName = givenName
self.familyName = familyName
self.dateOfBirth = dateOfBirth
self.nationality = nationality
pass
def __str__(self):
return f"Driver(driverId={self.driverId}, permanentNumber={self.permanentNumber}, code={self.code}, url={self.url}, givenName={self.givenName}, familyName={self.familyName}, dateOfBirth={self.dateOfBirth}, nationality={self.nationality})"
def __repr__(self):
return f"Driver(driverId={self.driverId}, permanentNumber={self.permanentNumber}, code={self.code}, url={self.url}, givenName={self.givenName}, familyName={self.familyName}, dateOfBirth={self.dateOfBirth}, nationality={self.nationality})"

View File

@ -0,0 +1,33 @@
from dataclasses import dataclass
from ergast_py.models.constructor import Constructor
from ergast_py.models.driver import Driver
@dataclass
class DriverStanding:
"""
Representation of a Formula One Driver's standing in a Season
Driver Standings may contain:
position: Integer
positionText: String
points: Float
wins: Integer
driver: Driver
constructors: Constructor[]
"""
def __init__(self, position: int, positionText: str, points: float, wins: int, driver: Driver,
constructors: list[Constructor]) -> None:
self.position = position
self.positionText = positionText
self.points = points
self.wins = wins
self.driver = driver
self.constructors = constructors
pass
def __str__(self):
return f"DriverStanding(position={self.position}, positionText={self.positionText}, points={self.points}, wins={self.wins}, driver={self.driver}, constructors={self.constructors})"
def __repr__(self):
return f"DriverStanding(position={self.position}, positionText={self.positionText}, points={self.points}, wins={self.wins}, driver={self.driver}, constructors={self.constructors})"

View File

@ -0,0 +1,27 @@
from dataclasses import dataclass
from ergast_py.models.average_speed import AverageSpeed
import datetime
@dataclass
class FastestLap:
"""
Representation of a Fastest Lap for a Formula One Driver
Fastest Laps may contain:
rank: Integer
lap: Integer
time: datetime.time
averageSpeed: AverageSpeed
"""
def __init__(self, rank: int, lap: int, time: datetime.time, averageSpeed: AverageSpeed) -> None:
self.rank = rank
self.lap = lap
self.time = time
self.averageSpeed = averageSpeed
pass
def __str__(self):
return f"FastestLap(rank={self.rank}, lap={self.lap}, time={self.time}, averageSpeed={self.averageSpeed})"
def __repr__(self):
return f"FastestLap(rank={self.rank}, lap={self.lap}, time={self.time}, averageSpeed={self.averageSpeed})"

23
ergast_py/models/lap.py Normal file
View File

@ -0,0 +1,23 @@
from dataclasses import dataclass
from ergast_py.models.timing import Timing
@dataclass
class Lap:
"""
Representation of a single Lap from a Formula One race
Laps may contain:
number: Integer
timings: Timing[]
"""
def __init__(self, number: int, timings: list[Timing]) -> None:
self.number = number
self.timings = timings
pass
def __str__(self):
return f"Lap(number={self.number}, timings={self.timings})"
def __repr__(self):
return f"Lap(number={self.number}, timings={self.timings})"

View File

@ -0,0 +1,25 @@
from dataclasses import dataclass
@dataclass
class Location:
"""
Representation of a Location for a Formula One Circuit
Locations may contain:
lat: Float
long: Float
locality: String
country: String
"""
def __init__(self, latitude: float, longitude: float, locality: str, country: str) -> None:
self.latitude = latitude
self.longitude = longitude
self.locality = locality
self.country = country
pass
def __str__(self):
return f"Location(latitude={self.latitude},longitude={self.longitude}, locality={self.locality}, country={self.country})"
def __repr__(self):
return f"Location(latitude={self.latitude},longitude={self.longitude}, locality={self.locality}, country={self.country})"

View File

@ -0,0 +1,29 @@
from dataclasses import dataclass
import datetime
@dataclass
class PitStop:
"""
Representation of a single Pit Stop from a Formula One race
PitStops may contain:
driverId: String
lap: Integer
stop: Integer
localTime: datetime.datetime
duration: datetime.time
"""
def __init__(self, driverId: str, lap: int, stop: int, localTime: datetime.datetime,
duration: datetime.time) -> None:
self.driverId = driverId
self.lap = lap
self.stop = stop
self.localTime = localTime
self.duration = duration
pass
def __str__(self):
return f"PitStop(driverId={self.driverId}, lap={self.lap}, stop={self.stop}, localTime={self.localTime}, duration={self.duration})"
def __repr__(self):
return f"PitStop(driverId={self.driverId}, lap={self.lap}, stop={self.stop}, localTime={self.localTime}, duration={self.duration})"

61
ergast_py/models/race.py Normal file
View File

@ -0,0 +1,61 @@
from dataclasses import dataclass
import datetime
from ergast_py.helpers import Helpers
from ergast_py.models.circuit import Circuit
from ergast_py.models.lap import Lap
from ergast_py.models.result import Result
from ergast_py.models.pit_stop import PitStop
@dataclass
class Race:
"""
Representation of a single Race from a Formula One season
Races may contain:
season: Integer
round: Integer
url: String
raceName: String
circuit: Circuit
date: datetime.datetime
results: Result[]
firstPractice: datetime.datetime
secondPractice: datetime.datetime
thirdPractice: datetime.datetime
sprint: datetime.datetime
sprintResults: Result[]
qualifying: datetime.datetime
qualifyingResults: Result[]
pitStops: PitStop[]
laps: Lap[]
"""
def __init__(self, season: int, round: int, url: str, raceName: str, circuit: Circuit, date: datetime.datetime,
results: list[Result], firstPractice: datetime.datetime, secondPractice: datetime.datetime,
thirdPractice: datetime.datetime, sprint: datetime.datetime, sprintResults: list[Result],
qualifying: datetime.datetime, qualifyingResults: list[Result], pitStops: list[PitStop],
laps: list[Lap]) -> None:
self.season = season
self.round = round
self.url = url
self.raceName = raceName
self.circuit = circuit
self.date = date
self.results = results
self.firstPractice = firstPractice
self.secondPractice = secondPractice
self.thirdPractice = thirdPractice
self.sprint = sprint
self.sprintResults = sprintResults
self.qualifying = qualifying
self.qualifyingResults = qualifyingResults
self.pitStops = pitStops
self.laps = laps
pass
def __str__(self):
return f"Race(season={self.season}, round={self.round}, url={self.url}, raceName={self.raceName}, circuit={self.circuit}, date={self.date}, results={self.results}, firstPractice={self.firstPractice}, secondPractice={self.secondPractice}, thirdPractice={self.thirdPractice}, sprint={self.sprint}, sprintResults={self.sprintResults}, qualifying={self.qualifying}, qualifyingResults={self.qualifyingResults}, pitStops={self.pitStops}, laps={self.laps})"
def __repr__(self):
return f"Race(season={self.season}, round={self.round}, url={self.url}, raceName={self.raceName}, circuit={self.circuit}, date={self.date}, results={self.results}, firstPractice={self.firstPractice}, secondPractice={self.secondPractice}, thirdPractice={self.thirdPractice}, sprint={self.sprint}, sprintResults={self.sprintResults}, qualifying={self.qualifying}, qualifyingResults={self.qualifyingResults}, pitStops={self.pitStops}, laps={self.laps})"

View File

@ -0,0 +1,52 @@
from dataclasses import dataclass
import datetime
from ergast_py.models.constructor import Constructor
from ergast_py.models.driver import Driver
from ergast_py.models.fastest_lap import FastestLap
@dataclass
class Result:
"""
Representation of a single Result from a Formula One race
Results may contain:
number: Integer
position: Integer
positionText: String
points: Integer
driver: Driver
constructor: Constructor
grid: Integer
laps: Integer
status: Integer
time: datetime.time
fastestLap: FastestLap
q1: datetime.time
q2: datetime.time
q3: datetime.time
"""
def __init__(self, number: int, position: int, positionText: str, points: float, driver: Driver,
constructor: Constructor, grid: int, laps: int, status: int, time: datetime.time,
fastestLap: FastestLap, q1: datetime.time, q2: datetime.time, q3: datetime.time) -> None:
self.number = number
self.position = position
self.positionText = positionText
self.points = points
self.driver = driver
self.constructor = constructor
self.grid = grid
self.laps = laps
self.status = status
self.time = time
self.fastestLap = fastestLap
self.q1 = q1
self.q2 = q2
self.q3 = q3
pass
def __str__(self):
return f"Result(number={self.number}, position={self.position}, positionText={self.positionText}, points={self.points}, driver={self.driver}, constructor={self.constructor}, grid={self.grid}, laps={self.laps}, status={self.status}, time={self.time}, fastestLap={self.fastestLap}, q1={self.q1}, q2={self.q2}, q3={self.q3})"
def __repr__(self):
return f"Result(number={self.number}, position={self.position}, positionText={self.positionText}, points={self.points}, driver={self.driver}, constructor={self.constructor}, grid={self.grid}, laps={self.laps}, status={self.status}, time={self.time}, fastestLap={self.fastestLap}, q1={self.q1}, q2={self.q2}, q3={self.q3})"

View File

@ -0,0 +1,21 @@
from dataclasses import dataclass
@dataclass
class Season:
"""
Representation of a single Season in Formula One
Seasons may contain:
season: Integer
url: String
"""
def __init__(self, season: int, url: str) -> None:
self.season = season
self.url = url
pass
def __str__(self):
return f"Season(season={self.season}, url={self.url})"
def __repr__(self):
return f"Season(season={self.season}, url={self.url})"

View File

@ -0,0 +1,28 @@
from dataclasses import dataclass
from ergast_py.models.driver_standing import DriverStanding
from ergast_py.models.constructor_standing import ConstructorStanding
@dataclass
class StandingsList:
"""
Representation of a set of Standings from a time in Formula One
StandingsLists may contain:
season: Integer
round: Integer
driverStandings: DriverStanding[]
constructorStandings: ConstructorStanding[]
"""
def __init__(self, season: int, round: int, driverStandings: list[DriverStanding],
constructorStandings: list[ConstructorStanding]) -> None:
self.season = season
self.round = round
self.driverStandings = driverStandings
self.constructorStandings = constructorStandings
pass
def __str__(self):
return f"StandingsList(season={self.season}, round={self.round}, driverStandings={self.driverStandings}, constructorStandings={self.constructorStandings})"
def __repr__(self):
return f"StandingsList(season={self.season}, round={self.round}, driverStandings={self.driverStandings}, constructorStandings={self.constructorStandings})"

View File

@ -0,0 +1,23 @@
from dataclasses import dataclass
@dataclass
class Status:
"""
Representation of the finishing status of a Driver in a Race
Statuses may contain:
statusId: Integer
count: Integer
status: String
"""
def __init__(self, statusId: int, count: int, status: str) -> None:
self.statusId = statusId
self.count = count
self.status = status
pass
def __str__(self):
return f"Status(statusId={self.statusId}, count={self.count}, status={self.status})"
def __repr__(self):
return f"Status(statusId={self.statusId}, count={self.count}, status={self.status})"

View File

@ -0,0 +1,24 @@
from dataclasses import dataclass
import datetime
@dataclass
class Timing:
"""
Representation of a single timing from a lap in Formula One
Timings may contain:
driverId: String
position: Integer
time: datetime.time
"""
def __init__(self, driverId: str, position: int, time: datetime.time) -> None:
self.driverId = driverId
self.position = position
self.time = time
pass
def __str__(self):
return f"Timing(driverId={self.driverId}, position={self.position}, time={self.time})"
def __repr__(self):
return f"Timing(driverId={self.driverId}, position={self.position}, time={self.time})"

280
ergast_py/requester.py Normal file
View File

@ -0,0 +1,280 @@
import json
import requests
from ergast_py.models.circuit import Circuit
from ergast_py.models.constructor import Constructor
from ergast_py.models.driver import Driver
from uritemplate import URITemplate
host = 'https://ergast.com/api'
series = 'f1'
class Requester():
"""
Perform requests to the Ergast API
"""
def __init__(self) -> None:
pass
def _get_race_results_params(self, param: dict) -> dict:
""" Acquire only the appropriate filters for Race/Results data """
return {
"season": param["season"],
"round": param["round"],
"filters": {
"drivers": param["driver"],
"constructors": param["constructor"],
"grid": param["grid"],
"qualifying": param["qualifying"],
"sprint": param["sprint"],
"fastest": param["fastest"],
"circuits": param["circuit"],
"status": param["status"],
"results": param["result"],
"races": param["races"],
"seasons": param["seasons"],
},
"paging": {
"limit": param["limit"],
"offset": param["offset"],
}
}
def _get_race_results_criteria(self, params: dict, resource: str) -> dict:
""" Split the data into criteria and resource for Race/Results """
criteria = []
for key, value in params["filters"].items():
if (key != resource and value != None):
criteria.append(key)
criteria.append(value)
value = params["filters"][resource]
return {
"resource": resource,
"value": value,
"criteria": criteria
}
def _get_standings_params(self, param: dict) -> dict:
""" Acquire only the appropriate filters for Standings data """
return {
"season": param["season"],
"round": param["round"],
"filters": {
"standing": param["standing"],
"drivers": param["driver"],
"constructors": param["constructor"]
},
"paging": {
"limit": param["limit"],
"offset": param["offset"],
}
}
def _get_standings_criteria(self, params: dict, resource: str) -> dict:
""" Split the data into criteria and resource for standings """
criteria = []
for key, value in params["filters"].items():
if (key != "standing" and value != None):
criteria.append(key)
criteria.append(value)
value = params["filters"]["standing"]
return {
"resource": resource,
"value": value,
"criteria": criteria
}
def _get_laps_pit_stops_params(self, param: dict) -> dict:
""" Acquire only the appropriate filters for Laps and Pit Stops data """
return {
"season": param["season"],
"round": param["round"],
"filters": {
"pitstops": param["pit_stop"],
"laps": param["lap"],
"drivers": param["driver"],
},
"paging": {
"limit": param["limit"],
"offset": param["offset"],
}
}
def _get_laps_pit_stops_criteria(self, params: dict, resource: str) -> dict:
""" Split the data into criteria and resource for Laps and Pit Stops """
criteria = []
for key, value in params["filters"].items():
if (key != resource and value != None):
criteria.append(key)
criteria.append(value)
value = params["filters"][resource]
return {
"resource": resource,
"value": value,
"criteria": criteria
}
def run_request(self, season, round, criteria, resource, value, limit, offset) -> dict:
""" Takes values to run the request and return a dict """
url_tmpl = URITemplate('https://ergast.com/api{/series}{/season}{/round}'
'{/criteria*}{/resource}{/value}.json{?limit,offset}')
url = url_tmpl.expand(host=host, series=series,
season=season, round=round,
criteria=criteria, resource=resource,
value=value, limit=limit, offset=offset)
return json.loads(requests.get(url).text)
#
# Race and Results
#
def get_circuits(self, param: dict) -> dict:
params = self._get_race_results_params(param)
filters = self._get_race_results_criteria(params, "circuits")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["CircuitTable"]["Circuits"]
def get_constructors(self, param: dict) -> dict:
params = self._get_race_results_params(param)
filters = self._get_race_results_criteria(params, "constructors")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["ConstructorTable"]["Constructors"]
def get_drivers(self, param: dict) -> dict:
params = self._get_race_results_params(param)
filters = self._get_race_results_criteria(params, "drivers")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["DriverTable"]["Drivers"]
def get_qualifying(self, param: dict) -> dict:
params = self._get_race_results_params(param)
filters = self._get_race_results_criteria(params, "qualifying")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["RaceTable"]["Races"]
def get_sprints(self, param: dict) -> dict:
params = self._get_race_results_params(param)
filters = self._get_race_results_criteria(params, "sprint")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["RaceTable"]["Races"]
def get_results(self, param: dict) -> dict:
params = self._get_race_results_params(param)
filters = self._get_race_results_criteria(params, "results")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["RaceTable"]["Races"]
def get_races(self, param: dict) -> dict:
params = self._get_race_results_params(param)
filters = self._get_race_results_criteria(params, "races")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["RaceTable"]["Races"]
def get_seasons(self, param: dict) -> dict:
params = self._get_race_results_params(param)
filters = self._get_race_results_criteria(params, "seasons")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["SeasonTable"]["Seasons"]
def get_statuses(self, param: dict) -> dict:
params = self._get_race_results_params(param)
filters = self._get_race_results_criteria(params, "status")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["StatusTable"]["Status"]
#
# Standings
#
def get_driver_standings(self, param: dict) -> dict:
params = self._get_standings_params(param)
filters = self._get_standings_criteria(params, "driverStandings")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["StandingsTable"]["StandingsLists"]
def get_constructor_standings(self, param: dict) -> dict:
params = self._get_standings_params(param)
filters = self._get_standings_criteria(params, "constructorStandings")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["StandingsTable"]["StandingsLists"]
#
# Laps and Pit Stops
#
def get_laps(self, param: dict) -> dict:
params = self._get_laps_pit_stops_params(param)
filters = self._get_laps_pit_stops_criteria(params, "laps")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["RaceTable"]["Races"]
def get_pit_stops(self, param: dict) -> dict:
params = self._get_laps_pit_stops_params(param)
filters = self._get_laps_pit_stops_criteria(params, "pitstops")
json = self.run_request(season=params["season"], round=params["round"],
criteria=filters["criteria"], resource=filters["resource"], value=filters["value"],
limit=params["paging"]["limit"], offset=params["paging"]["offset"])
return json["MRData"]["RaceTable"]["Races"]

View File

@ -0,0 +1,301 @@
from ergast_py.models.average_speed import AverageSpeed
from ergast_py.models.constructor_standing import ConstructorStanding
from ergast_py.models.driver import Driver
from ergast_py.models.driver_standing import DriverStanding
from ergast_py.models.fastest_lap import FastestLap
from ergast_py.models.lap import Lap
from ergast_py.models.location import Location
from ergast_py.models.circuit import Circuit
from ergast_py.models.constructor import Constructor
from ergast_py.models.pit_stop import PitStop
from ergast_py.models.race import Race
from ergast_py.models.result import Result
from ergast_py.helpers import Helpers
from ergast_py.constants.status_type import StatusType
from ergast_py.models.season import Season
from ergast_py.models.standings_list import StandingsList
from ergast_py.models.status import Status
from ergast_py.models.timing import Timing
from ergast_py.constants.expected import Expected
class TypeConstructor():
"""
Class for constructing types out of dicts
"""
def __init__(self) -> None:
pass
#
# PRIVATE METHODS
#
def _populate_missing(self, expected: dict, actual: dict) -> dict:
for item in expected:
if item not in actual:
if expected[item] == "dict":
actual[item] = {}
elif expected[item] == "float":
actual[item] = "0.0"
elif expected[item] == "int":
actual[item] = "0"
else:
actual[item] = ""
return actual
def _populate_missing_location(self, location: dict) -> dict:
return self._populate_missing(expected=Expected().location, actual=location)
def _populate_missing_circuit(self, circuit: dict) -> dict:
return self._populate_missing(expected=Expected().circuit, actual=circuit)
def _populate_missing_constructor(self, constructor: dict) -> dict:
return self._populate_missing(expected=Expected().constructor, actual=constructor)
def _populate_missing_driver(self, driver: dict) -> dict:
return self._populate_missing(expected=Expected().driver, actual=driver)
def _populate_missing_race(self, race: dict) -> dict:
return self._populate_missing(expected=Expected().race, actual=race)
def _populate_missing_result(self, result: dict) -> dict:
return self._populate_missing(expected=Expected().result, actual=result)
def _populate_missing_fastest_lap(self, fastest_lap: dict) -> dict:
return self._populate_missing(expected=Expected().fastest_lap, actual=fastest_lap)
def _populate_missing_average_speed(self, average_speed: dict) -> dict:
return self._populate_missing(expected=Expected().average_speed, actual=average_speed)
def _populate_missing_pit_stop(self, pit_stop: dict) -> dict:
return self._populate_missing(expected=Expected().pit_stop, actual=pit_stop)
def _populate_missing_lap(self, lap: dict) -> dict:
return self._populate_missing(expected=Expected().lap, actual=lap)
def _populate_missing_timing(self, timing: dict) -> dict:
return self._populate_missing(expected=Expected().timing, actual=timing)
def _populate_missing_season(self, season: dict) -> dict:
return self._populate_missing(expected=Expected().season, actual=season)
def _populate_missing_status(self, status: dict) -> dict:
return self._populate_missing(expected=Expected().status, actual=status)
def _populate_missing_driver_standing(self, standing: dict) -> dict:
return self._populate_missing(expected=Expected().driver_standing, actual=standing)
def _populate_missing_constructor_standing(self, standing: dict) -> dict:
return self._populate_missing(expected=Expected().constructor_standing, actual=standing)
def _populate_missing_standings_list(self, standings_list: dict) -> dict:
return self._populate_missing(expected=Expected().standings_list, actual=standings_list)
#
# PUBLIC METHODS
#
def construct_location(self, location: dict) -> Location:
location = self._populate_missing_location(location=location)
return Location(
latitude=float(location["lat"]),
longitude=float(location["long"]),
locality=location["locality"],
country=location["country"]
)
def construct_circuit(self, circuit: dict) -> Circuit:
circuit = self._populate_missing_circuit(circuit)
return Circuit(
circuitId=circuit["circuitId"],
url=circuit["url"],
circuitName=circuit["circuitName"],
location=self.construct_location(circuit["Location"])
)
def construct_circuits(self, circuits: dict) -> list[Circuit]:
return [self.construct_circuit(circuit) for circuit in circuits]
def construct_constructor(self, constructor: dict) -> Constructor:
constructor = self._populate_missing_constructor(constructor)
return Constructor(
constructorId=constructor["constructorId"],
url=constructor["url"],
name=constructor["name"],
nationality=constructor["nationality"]
)
def construct_constructors(self, constructors: dict) -> list[Constructor]:
return [self.construct_constructor(constructor) for constructor in constructors]
def construct_driver(self, driver: dict) -> Driver:
driver = self._populate_missing_driver(driver)
return Driver(
driverId=driver["driverId"],
permanentNumber=int(driver["permanentNumber"]),
code=driver["code"],
url=driver["url"],
givenName=driver["givenName"],
familyName=driver["familyName"],
dateOfBirth=Helpers().construct_date(driver["dateOfBirth"]),
nationality=driver["nationality"]
)
def construct_drivers(self, drivers: dict) -> list[Driver]:
return [self.construct_driver(driver) for driver in drivers]
def construct_race(self, race: dict) -> Race:
race = self._populate_missing_race(race)
return Race(
season=int(race["season"]),
round=int(race["round"]),
url=race["url"],
raceName=race["raceName"],
circuit=self.construct_circuit(race["Circuit"]),
date=Helpers().construct_datetime_str(date=race["date"], time=race["time"]),
results=self.construct_results(race["Results"]),
firstPractice=Helpers().construct_datetime_dict(race["FirstPractice"]),
secondPractice=Helpers().construct_datetime_dict(race["SecondPractice"]),
thirdPractice=Helpers().construct_datetime_dict(race["ThirdPractice"]),
sprint=Helpers().construct_datetime_dict(race["Sprint"]),
sprintResults=self.construct_results(race["SprintResults"]),
qualifying=Helpers().construct_datetime_dict(race["Qualifying"]),
qualifyingResults=self.construct_results(race["QualifyingResults"]),
pitStops=self.construct_pit_stops(race["PitStops"]),
laps=self.construct_laps(race["Laps"])
)
def construct_races(self, races: dict) -> list[Race]:
return [self.construct_race(race) for race in races]
def construct_result(self, result: dict) -> Result:
result = self._populate_missing_result(result)
return Result(
number=int(result["number"]),
position=int(result["position"]),
positionText=result["positionText"],
points=float(result["points"]),
driver=self.construct_driver(result["Driver"]),
constructor=self.construct_constructor(result["Constructor"]),
grid=int(result["grid"]),
laps=int(result["laps"]),
status=int(StatusType().status_map[result["status"]]),
time=Helpers().construct_lap_time_millis(millis=result["Time"]),
fastestLap=self.construct_fastest_lap(result["FastestLap"]),
q1=Helpers().format_lap_time(time=result["Q1"]),
q2=Helpers().format_lap_time(time=result["Q2"]),
q3=Helpers().format_lap_time(time=result["Q3"]),
)
def construct_results(self, results: dict) -> list[Result]:
return [self.construct_result(result) for result in results]
def construct_fastest_lap(self, fastest_lap: dict) -> FastestLap:
fastest_lap = self._populate_missing_fastest_lap(fastest_lap)
return FastestLap(
rank=int(fastest_lap["rank"]),
lap=int(fastest_lap["lap"]),
time=Helpers().construct_lap_time(time=fastest_lap["Time"]),
averageSpeed=self.construct_average_speed(fastest_lap["AverageSpeed"])
)
def construct_average_speed(self, average_speed: dict) -> AverageSpeed:
average_speed = self._populate_missing_average_speed(average_speed)
return AverageSpeed(
units=average_speed["units"],
speed=float(average_speed["speed"])
)
def construct_pit_stop(self, pit_stop: dict) -> PitStop:
pit_stop = self._populate_missing_pit_stop(pit_stop)
return PitStop(
driverId=pit_stop["driverId"],
lap=int(pit_stop["lap"]),
stop=int(pit_stop["stop"]),
localTime=Helpers().construct_local_time(pit_stop["time"]),
duration=Helpers().construct_pitstop_duration(pit_stop["duration"])
)
def construct_pit_stops(self, pit_stops: dict) -> list[PitStop]:
return [self.construct_pit_stop(pit_stop) for pit_stop in pit_stops]
def construct_lap(self, lap: dict) -> Lap:
lap = self._populate_missing_lap(lap)
return Lap(
number=int(lap["number"]),
timings=self.construct_timings(lap["Timings"])
)
def construct_laps(self, laps: dict) -> list[Lap]:
return [self.construct_lap(lap) for lap in laps]
def construct_timing(self, timing: dict) -> Timing:
timing = self._populate_missing_timing(timing)
return Timing(
driverId=timing["driverId"],
position=int(timing["position"]),
time=Helpers().format_lap_time(time=timing["time"])
)
def construct_timings(self, timings: dict) -> list[Timing]:
return [self.construct_timing(timing) for timing in timings]
def construct_season(self, season: dict) -> Season:
season = self._populate_missing_season(season)
return Season(
season=int(season["season"]),
url=season["url"]
)
def construct_seasons(self, seasons: dict) -> list[Season]:
return [self.construct_season(season) for season in seasons]
def construct_status(self, status: dict) -> Status:
status = self._populate_missing_status(status)
return Status(
statusId=int(status["statusId"]),
count=int(status["count"]),
status=status["status"]
)
def construct_statuses(self, statuses: dict) -> list[Season]:
return [self.construct_status(status) for status in statuses]
def construct_driver_standing(self, standing: dict) -> DriverStanding:
standing = self._populate_missing_driver_standing(standing)
return DriverStanding(
position=int(standing["position"]),
positionText=standing["positionText"],
points=float(standing["points"]),
wins=int(standing["wins"]),
driver=self.construct_driver(standing["Driver"]),
constructors=self.construct_constructors(standing["Constructors"])
)
def construct_driver_standings(self, standings: dict) -> list[DriverStanding]:
return [self.construct_driver_standing(standing) for standing in standings]
def construct_constructor_standing(self, standing: dict) -> ConstructorStanding:
standing = self._populate_missing_constructor_standing(standing)
return ConstructorStanding(
position=int(standing["position"]),
positionText=standing["positionText"],
points=float(standing["points"]),
wins=int(standing["wins"]),
constructor=self.construct_constructor(standing["Constructor"])
)
def construct_constructor_standings(self, standings: dict) -> list[ConstructorStanding]:
return [self.construct_constructor_standing(standing) for standing in standings]
def construct_standings_list(self, standings_list: dict) -> StandingsList:
standings_list = self._populate_missing_standings_list(standings_list)
return StandingsList(
season=int(standings_list["season"]),
round=int(standings_list["round"]),
driverStandings=self.construct_driver_standings(standings_list["DriverStandings"]),
constructorStandings=self.construct_constructor_standings(standings_list["ConstructorStandings"])
)
def construct_standings_lists(self, standings_lists: dict) -> list[StandingsList]:
return [self.construct_standings_list(standings_list) for standings_list in standings_lists]

245
poetry.lock generated Normal file
View File

@ -0,0 +1,245 @@
[[package]]
name = "atomicwrites"
version = "1.4.0"
description = "Atomic file writes."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "attrs"
version = "21.4.0"
description = "Classes Without Boilerplate"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras]
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
[[package]]
name = "certifi"
version = "2022.5.18.1"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "charset-normalizer"
version = "2.0.12"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
python-versions = ">=3.5.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]]
name = "colorama"
version = "0.4.4"
description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "idna"
version = "3.3"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "more-itertools"
version = "8.13.0"
description = "More routines for operating on iterables, beyond itertools"
category = "dev"
optional = false
python-versions = ">=3.5"
[[package]]
name = "packaging"
version = "21.3"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "pluggy"
version = "0.13.1"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.extras]
dev = ["pre-commit", "tox"]
[[package]]
name = "py"
version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pyparsing"
version = "3.0.9"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
category = "dev"
optional = false
python-versions = ">=3.6.8"
[package.extras]
diagrams = ["railroad-diagrams", "jinja2"]
[[package]]
name = "pytest"
version = "5.4.3"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.5"
[package.dependencies]
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=17.4.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
more-itertools = ">=4.0.0"
packaging = "*"
pluggy = ">=0.12,<1.0"
py = ">=1.5.0"
wcwidth = "*"
[package.extras]
checkqa-mypy = ["mypy (==v0.761)"]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
name = "requests"
version = "2.27.1"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
name = "uritemplate"
version = "4.1.1"
description = "Implementation of RFC 6570 URI Templates"
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "urllib3"
version = "1.26.9"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "wcwidth"
version = "0.2.5"
description = "Measures the displayed width of unicode strings in a terminal"
category = "dev"
optional = false
python-versions = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "46627c9e8eb39de84bfe1ad76fabe2b766d95aaa41505a3351adb8de89dbe319"
[metadata.files]
atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
]
attrs = [
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
]
certifi = [
{file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"},
{file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"},
]
charset-normalizer = [
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
]
colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
]
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
more-itertools = [
{file = "more-itertools-8.13.0.tar.gz", hash = "sha256:a42901a0a5b169d925f6f217cd5a190e32ef54360905b9c39ee7db5313bfec0f"},
{file = "more_itertools-8.13.0-py3-none-any.whl", hash = "sha256:c5122bffc5f104d37c1626b8615b511f3427aa5389b94d61e5ef8236bfbc3ddb"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
]
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
pytest = [
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
]
requests = [
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
]
uritemplate = [
{file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
{file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
]
urllib3 = [
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
]
wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
]

17
pyproject.toml Normal file
View File

@ -0,0 +1,17 @@
[tool.poetry]
name = "ergast-py"
version = "0.1.0"
description = ""
authors = ["Samuel Roach <samuelroach.2000@gmail.com>"]
[tool.poetry.dependencies]
python = "^3.10"
requests = "^2.27.1"
uritemplate = "^4.1.1"
[tool.poetry.dev-dependencies]
pytest = "^5.2"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

0
tests/__init__.py Normal file
View File

5
tests/test_ergast_py.py Normal file
View File

@ -0,0 +1,5 @@
from ergast_py import __version__
def test_version():
assert __version__ == '0.1.0'