Skip to content

Commit d11107c

Browse files
add flight.py and test
1 parent 87f7338 commit d11107c

2 files changed

Lines changed: 207 additions & 0 deletions

File tree

examples/flight.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""Example of using Pydantic with pytz for flight time handling."""
2+
3+
from datetime import datetime
4+
from typing import Optional
5+
6+
from pydantic import BaseModel, Field, field_validator
7+
import pytz
8+
9+
10+
class Flight(BaseModel):
11+
"""Represents a flight with departure and arrival times in different timezones."""
12+
13+
flight_number: str = Field(..., pattern=r"^[A-Z]{2}\d{3,4}$")
14+
departure_airport: str = Field(..., min_length=3, max_length=3)
15+
arrival_airport: str = Field(..., min_length=3, max_length=3)
16+
departure_time: datetime
17+
arrival_time: datetime
18+
departure_timezone: str = Field(..., pattern=r"^[A-Za-z]+/[A-Za-z_]+$")
19+
arrival_timezone: str = Field(..., pattern=r"^[A-Za-z]+/[A-Za-z_]+$")
20+
duration_minutes: Optional[int] = None
21+
22+
@field_validator("departure_timezone", "arrival_timezone")
23+
@classmethod
24+
def validate_timezone(cls, v: str) -> str:
25+
"""Validate that the timezone exists in pytz."""
26+
try:
27+
pytz.timezone(v)
28+
except pytz.exceptions.UnknownTimeZoneError:
29+
raise ValueError(f"Unknown timezone: {v}")
30+
return v
31+
32+
@field_validator("arrival_time")
33+
@classmethod
34+
def validate_arrival_time(cls, v: datetime, info) -> datetime:
35+
"""Validate that arrival time is after departure time."""
36+
departure_time = info.data.get("departure_time")
37+
if departure_time and v <= departure_time:
38+
raise ValueError("Arrival time must be after departure time")
39+
return v
40+
41+
def calculate_duration(self) -> int:
42+
"""Calculate flight duration in minutes."""
43+
if self.duration_minutes is not None:
44+
return self.duration_minutes
45+
46+
# Convert times to UTC for comparison
47+
departure_tz = pytz.timezone(self.departure_timezone)
48+
arrival_tz = pytz.timezone(self.arrival_timezone)
49+
50+
departure_utc = departure_tz.localize(self.departure_time).astimezone(pytz.UTC)
51+
arrival_utc = arrival_tz.localize(self.arrival_time).astimezone(pytz.UTC)
52+
53+
duration = arrival_utc - departure_utc
54+
return int(duration.total_seconds() / 60)
55+
56+
def to_local_times(self) -> dict[str, datetime]:
57+
"""Convert times to their respective timezones."""
58+
departure_tz = pytz.timezone(self.departure_timezone)
59+
arrival_tz = pytz.timezone(self.arrival_timezone)
60+
61+
return {
62+
"departure_local": departure_tz.localize(self.departure_time),
63+
"arrival_local": arrival_tz.localize(self.arrival_time),
64+
}
65+
66+
67+
def main() -> None:
68+
"""Example usage of the Flight class."""
69+
# Example flight from New York to London
70+
flight = Flight(
71+
flight_number="BA178",
72+
departure_airport="JFK",
73+
arrival_airport="LHR",
74+
departure_time=datetime(2024, 3, 25, 20, 0), # 8 PM
75+
arrival_time=datetime(2024, 3, 26, 8, 0), # 8 AM next day
76+
departure_timezone="America/New_York",
77+
arrival_timezone="Europe/London",
78+
)
79+
80+
# Calculate duration
81+
duration = flight.calculate_duration()
82+
print(f"Flight duration: {duration} minutes")
83+
84+
# Get local times
85+
local_times = flight.to_local_times()
86+
print(f"Departure (local): {local_times['departure_local']}")
87+
print(f"Arrival (local): {local_times['arrival_local']}")
88+
89+
90+
if __name__ == "__main__":
91+
main()

tests/test_flight.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""Tests for the Flight class."""
2+
3+
from datetime import datetime
4+
5+
import pytest
6+
from examples.flight import Flight
7+
8+
9+
def test_valid_flight() -> None:
10+
"""Test creating a valid flight."""
11+
flight = Flight(
12+
flight_number="BA178",
13+
departure_airport="JFK",
14+
arrival_airport="LHR",
15+
departure_time=datetime(2024, 3, 25, 20, 0),
16+
arrival_time=datetime(2024, 3, 26, 8, 0),
17+
departure_timezone="America/New_York",
18+
arrival_timezone="Europe/London",
19+
)
20+
21+
assert flight.flight_number == "BA178"
22+
assert flight.departure_airport == "JFK"
23+
assert flight.arrival_airport == "LHR"
24+
assert flight.duration_minutes is None
25+
26+
27+
def test_invalid_flight_number() -> None:
28+
"""Test invalid flight number format."""
29+
with pytest.raises(ValueError):
30+
Flight(
31+
flight_number="123", # Invalid format
32+
departure_airport="JFK",
33+
arrival_airport="LHR",
34+
departure_time=datetime(2024, 3, 25, 20, 0),
35+
arrival_time=datetime(2024, 3, 26, 8, 0),
36+
departure_timezone="America/New_York",
37+
arrival_timezone="Europe/London",
38+
)
39+
40+
41+
def test_invalid_airport_code() -> None:
42+
"""Test invalid airport code length."""
43+
with pytest.raises(ValueError):
44+
Flight(
45+
flight_number="BA178",
46+
departure_airport="JFKX", # Too long
47+
arrival_airport="LHR",
48+
departure_time=datetime(2024, 3, 25, 20, 0),
49+
arrival_time=datetime(2024, 3, 26, 8, 0),
50+
departure_timezone="America/New_York",
51+
arrival_timezone="Europe/London",
52+
)
53+
54+
55+
def test_invalid_timezone() -> None:
56+
"""Test invalid timezone."""
57+
with pytest.raises(ValueError):
58+
Flight(
59+
flight_number="BA178",
60+
departure_airport="JFK",
61+
arrival_airport="LHR",
62+
departure_time=datetime(2024, 3, 25, 20, 0),
63+
arrival_time=datetime(2024, 3, 26, 8, 0),
64+
departure_timezone="Invalid/Timezone",
65+
arrival_timezone="Europe/London",
66+
)
67+
68+
69+
def test_arrival_before_departure() -> None:
70+
"""Test that arrival time must be after departure time."""
71+
with pytest.raises(ValueError):
72+
Flight(
73+
flight_number="BA178",
74+
departure_airport="JFK",
75+
arrival_airport="LHR",
76+
departure_time=datetime(2024, 3, 25, 20, 0),
77+
arrival_time=datetime(2024, 3, 25, 19, 0), # Before departure
78+
departure_timezone="America/New_York",
79+
arrival_timezone="Europe/London",
80+
)
81+
82+
83+
def test_duration_calculation() -> None:
84+
"""Test flight duration calculation."""
85+
flight = Flight(
86+
flight_number="BA178",
87+
departure_airport="JFK",
88+
arrival_airport="LHR",
89+
departure_time=datetime(2024, 3, 25, 20, 0),
90+
arrival_time=datetime(2024, 3, 26, 8, 0),
91+
departure_timezone="America/New_York",
92+
arrival_timezone="Europe/London",
93+
)
94+
95+
duration = flight.calculate_duration()
96+
assert duration > 0 # Should be positive
97+
assert duration < 1440 # Should be less than 24 hours
98+
99+
100+
def test_local_times() -> None:
101+
"""Test local time conversion."""
102+
flight = Flight(
103+
flight_number="BA178",
104+
departure_airport="JFK",
105+
arrival_airport="LHR",
106+
departure_time=datetime(2024, 3, 25, 20, 0),
107+
arrival_time=datetime(2024, 3, 26, 8, 0),
108+
departure_timezone="America/New_York",
109+
arrival_timezone="Europe/London",
110+
)
111+
112+
local_times = flight.to_local_times()
113+
assert "departure_local" in local_times
114+
assert "arrival_local" in local_times
115+
assert local_times["departure_local"].tzinfo is not None
116+
assert local_times["arrival_local"].tzinfo is not None

0 commit comments

Comments
 (0)