opensky-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
opensky/config.py ADDED
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ import tomllib
5
+ from datetime import date, timedelta
6
+ from pathlib import Path
7
+
8
+ from pydantic import BaseModel, field_validator
9
+
10
+
11
+ class DateRange(BaseModel):
12
+ start: str
13
+ end: str
14
+
15
+ @field_validator("start", "end")
16
+ @classmethod
17
+ def validate_date(cls, v: str) -> str:
18
+ date.fromisoformat(v)
19
+ return v
20
+
21
+ def dates(self) -> list[str]:
22
+ s = date.fromisoformat(self.start)
23
+ e = date.fromisoformat(self.end)
24
+ result = []
25
+ while s <= e:
26
+ result.append(s.isoformat())
27
+ s += timedelta(days=1)
28
+ return result
29
+
30
+
31
+ class SearchConfig(BaseModel):
32
+ origins: list[str]
33
+ destinations: list[str]
34
+ date_range: DateRange
35
+ cabin: str = "economy"
36
+ currency: str = "EUR"
37
+ stops: str = "any"
38
+ max_price: float = 0
39
+
40
+
41
+ _RISK_ALIASES: dict[str, str] = {
42
+ "avoid": "do_not_fly",
43
+ "risky": "high_risk",
44
+ "caution": "caution",
45
+ "do_not_fly": "do_not_fly",
46
+ "high_risk": "high_risk",
47
+ }
48
+
49
+
50
+ class SafetyConfig(BaseModel):
51
+ risk_threshold: str = "high_risk"
52
+
53
+ @field_validator("risk_threshold")
54
+ @classmethod
55
+ def normalize_risk(cls, v: str) -> str:
56
+ normalized = _RISK_ALIASES.get(v.lower().strip())
57
+ if normalized is None:
58
+ valid = ", ".join(_RISK_ALIASES.keys())
59
+ raise ValueError(f"Unknown risk_threshold '{v}'. Use: {valid}")
60
+ return normalized
61
+
62
+
63
+ class ScoringConfig(BaseModel):
64
+ price_weight: float = 1.0
65
+ duration_weight: float = 0.5
66
+
67
+
68
+ class ConnectionsConfig(BaseModel):
69
+ final_destination: str = ""
70
+ transit_hours: dict[str, float] = {}
71
+
72
+
73
+ class ScanConfig(BaseModel):
74
+ search: SearchConfig
75
+ safety: SafetyConfig = SafetyConfig()
76
+ scoring: ScoringConfig = ScoringConfig()
77
+ connections: ConnectionsConfig = ConnectionsConfig()
78
+
79
+
80
+ def _validate_date_range(cfg: ScanConfig) -> None:
81
+ """Validate date range: start <= end, warn if start is in the past."""
82
+ s = date.fromisoformat(cfg.search.date_range.start)
83
+ e = date.fromisoformat(cfg.search.date_range.end)
84
+ if s > e:
85
+ raise ValueError(
86
+ f"Start date {cfg.search.date_range.start} is after end date "
87
+ f"{cfg.search.date_range.end}. Start must be before end."
88
+ )
89
+ if s < date.today():
90
+ print(
91
+ f"Warning: start date {cfg.search.date_range.start} is in the past.",
92
+ file=sys.stderr,
93
+ )
94
+
95
+
96
+ def load_config(path: str | Path) -> ScanConfig:
97
+ p = Path(path)
98
+ with open(p, "rb") as f:
99
+ data = tomllib.load(f)
100
+ cfg = ScanConfig(**data)
101
+
102
+ _validate_date_range(cfg)
103
+
104
+ # Resolve city names to IATA codes in origins/destinations
105
+ from opensky.airports import resolve_airport
106
+
107
+ try:
108
+ cfg.search.origins = [resolve_airport(o, interactive=False) for o in cfg.search.origins]
109
+ cfg.search.destinations = [resolve_airport(d, interactive=False) for d in cfg.search.destinations]
110
+ except ValueError as e:
111
+ raise ValueError(str(e)) from None
112
+
113
+ return cfg
114
+
115
+
116
+ EXAMPLE_CONFIG = """\
117
+ [search]
118
+ # Airport codes (city names work in CLI: opensky search bangalore hamburg ...)
119
+ origins = ["BLR", "DEL", "BOM", "KUL", "BKK", "SIN"] # Bangalore, Delhi, Mumbai, Kuala Lumpur, Bangkok, Singapore
120
+ destinations = ["HAM", "FRA", "MUC", "BER", "AMS", "CPH"] # Hamburg, Frankfurt, Munich, Berlin, Amsterdam, Copenhagen
121
+ cabin = "economy" # economy, premium_economy, business, first
122
+ currency = "EUR"
123
+ stops = "any" # any, nonstop, 1, 2
124
+ # max_price = 500 # maximum price (0 = no limit)
125
+
126
+ [search.date_range]
127
+ start = "2026-03-10"
128
+ end = "2026-03-20"
129
+
130
+ [safety]
131
+ risk_threshold = "risky" # hide flights at this level or above: avoid, risky, caution
132
+
133
+ [scoring]
134
+ price_weight = 1.0
135
+ duration_weight = 0.5
136
+
137
+ # Optional: if your final destination is one city, add ground travel time
138
+ # from each arrival airport. This helps scoring rank "fly to nearby city +
139
+ # short train" vs "fly direct but expensive".
140
+ [connections]
141
+ final_destination = "Hamburg"
142
+
143
+ [connections.transit_hours]
144
+ HAM = 0 # direct, no ground travel
145
+ HAJ = 1.5 # Hannover: 1.5h train to Hamburg
146
+ BER = 2 # Berlin: 2h train to Hamburg
147
+ FRA = 4 # Frankfurt: 4h train to Hamburg
148
+ MUC = 5.5
149
+ AMS = 5
150
+ CPH = 5
151
+ """
@@ -0,0 +1,203 @@
1
+ {
2
+ "metadata": {
3
+ "version": "1.0.0",
4
+ "updated": "2026-03-01",
5
+ "sources": [
6
+ "EASA Conflict Zone Information Bulletins (CZIB)",
7
+ "safeairspace.net",
8
+ "FAA NOTAM database"
9
+ ]
10
+ },
11
+ "zones": [
12
+ {
13
+ "id": "ukraine",
14
+ "name": "Ukraine",
15
+ "risk_level": "do_not_fly",
16
+ "countries": ["UA"],
17
+ "airports": [],
18
+ "source": "EASA CZIB 2022-01 R16",
19
+ "details": "Active conflict zone. All Ukrainian airspace closed to civil aviation.",
20
+ "updated": "2026-02-28"
21
+ },
22
+ {
23
+ "id": "iran",
24
+ "name": "Iran",
25
+ "risk_level": "do_not_fly",
26
+ "countries": ["IR"],
27
+ "airports": [],
28
+ "source": "EASA CZIB 2026-002",
29
+ "details": "Military strikes Feb 2026. FIRs OIIX, OIBB closed to overflights.",
30
+ "updated": "2026-02-28"
31
+ },
32
+ {
33
+ "id": "iraq",
34
+ "name": "Iraq",
35
+ "risk_level": "do_not_fly",
36
+ "countries": ["IQ"],
37
+ "airports": [],
38
+ "source": "EASA CZIB 2026-002",
39
+ "details": "Airspace restrictions due to regional conflict.",
40
+ "updated": "2026-02-28"
41
+ },
42
+ {
43
+ "id": "syria",
44
+ "name": "Syria",
45
+ "risk_level": "do_not_fly",
46
+ "countries": ["SY"],
47
+ "airports": [],
48
+ "source": "EASA CZIB 2024-003",
49
+ "details": "Ongoing conflict. Damascus FIR closed to overflights.",
50
+ "updated": "2026-02-28"
51
+ },
52
+ {
53
+ "id": "israel",
54
+ "name": "Israel",
55
+ "risk_level": "do_not_fly",
56
+ "countries": ["IL"],
57
+ "airports": [],
58
+ "source": "EASA CZIB 2024-005",
59
+ "details": "Active conflict zone. Intermittent airspace closures.",
60
+ "updated": "2026-02-28"
61
+ },
62
+ {
63
+ "id": "lebanon",
64
+ "name": "Lebanon",
65
+ "risk_level": "do_not_fly",
66
+ "countries": ["LB"],
67
+ "airports": [],
68
+ "source": "EASA CZIB 2024-006",
69
+ "details": "Beirut FIR affected by regional conflict.",
70
+ "updated": "2026-02-28"
71
+ },
72
+ {
73
+ "id": "yemen",
74
+ "name": "Yemen",
75
+ "risk_level": "do_not_fly",
76
+ "countries": ["YE"],
77
+ "airports": [],
78
+ "source": "EASA CZIB 2024-007",
79
+ "details": "Ongoing conflict and Houthi activity affecting airspace.",
80
+ "updated": "2026-02-28"
81
+ },
82
+ {
83
+ "id": "libya",
84
+ "name": "Libya",
85
+ "risk_level": "do_not_fly",
86
+ "countries": ["LY"],
87
+ "airports": [],
88
+ "source": "EASA CZIB 2023-001",
89
+ "details": "Unstable security situation.",
90
+ "updated": "2026-02-28"
91
+ },
92
+ {
93
+ "id": "somalia",
94
+ "name": "Somalia",
95
+ "risk_level": "do_not_fly",
96
+ "countries": ["SO"],
97
+ "airports": [],
98
+ "source": "EASA CZIB 2023-002",
99
+ "details": "Ongoing conflict. Limited ATC capability.",
100
+ "updated": "2026-02-28"
101
+ },
102
+ {
103
+ "id": "afghanistan",
104
+ "name": "Afghanistan",
105
+ "risk_level": "do_not_fly",
106
+ "countries": ["AF"],
107
+ "airports": [],
108
+ "source": "EASA CZIB 2022-002",
109
+ "details": "No functioning civil aviation authority.",
110
+ "updated": "2026-02-28"
111
+ },
112
+ {
113
+ "id": "north_korea",
114
+ "name": "North Korea",
115
+ "risk_level": "do_not_fly",
116
+ "countries": ["KP"],
117
+ "airports": [],
118
+ "source": "FAA NOTAM",
119
+ "details": "Prohibited airspace. Missile launch risk.",
120
+ "updated": "2026-02-28"
121
+ },
122
+ {
123
+ "id": "sudan",
124
+ "name": "Sudan",
125
+ "risk_level": "do_not_fly",
126
+ "countries": ["SD"],
127
+ "airports": [],
128
+ "source": "EASA CZIB 2023-003",
129
+ "details": "Civil war. Khartoum FIR dangerous.",
130
+ "updated": "2026-02-28"
131
+ },
132
+ {
133
+ "id": "gulf_states",
134
+ "name": "Gulf States (Airspace Risk)",
135
+ "risk_level": "high_risk",
136
+ "countries": [],
137
+ "airports": ["DXB", "AUH", "SHJ", "RKT", "DOH", "BAH", "KWI", "MCT"],
138
+ "source": "safeairspace.net",
139
+ "details": "Adjacent to Iran/Iraq conflict. Overflights at risk during escalations. Gulf FIRs periodically restricted.",
140
+ "updated": "2026-02-28"
141
+ },
142
+ {
143
+ "id": "eritrea",
144
+ "name": "Eritrea",
145
+ "risk_level": "high_risk",
146
+ "countries": ["ER"],
147
+ "airports": [],
148
+ "source": "EASA CZIB 2023-004",
149
+ "details": "Military activity near borders.",
150
+ "updated": "2026-02-28"
151
+ },
152
+ {
153
+ "id": "ethiopia_partial",
154
+ "name": "Ethiopia (Northern Regions)",
155
+ "risk_level": "high_risk",
156
+ "countries": [],
157
+ "airports": ["MQX", "AXU", "GDQ", "SHC"],
158
+ "source": "EASA CZIB 2023-005",
159
+ "details": "Tigray region conflict aftermath. Northern airports affected.",
160
+ "updated": "2026-02-28"
161
+ },
162
+ {
163
+ "id": "mali",
164
+ "name": "Mali",
165
+ "risk_level": "high_risk",
166
+ "countries": ["ML"],
167
+ "airports": [],
168
+ "source": "EASA CZIB 2023-006",
169
+ "details": "Armed groups active. Limited ATC in northern regions.",
170
+ "updated": "2026-02-28"
171
+ },
172
+ {
173
+ "id": "niger",
174
+ "name": "Niger",
175
+ "risk_level": "high_risk",
176
+ "countries": ["NE"],
177
+ "airports": [],
178
+ "source": "EASA CZIB 2023-007",
179
+ "details": "Post-coup instability.",
180
+ "updated": "2026-02-28"
181
+ },
182
+ {
183
+ "id": "pakistan_partial",
184
+ "name": "Pakistan (Border Regions)",
185
+ "risk_level": "caution",
186
+ "countries": [],
187
+ "airports": ["PEW", "UET", "TUK"],
188
+ "source": "safeairspace.net",
189
+ "details": "Western border areas near Afghanistan/Iran. Major airports (ISB, LHE, KHI) unaffected.",
190
+ "updated": "2026-02-28"
191
+ },
192
+ {
193
+ "id": "russia_partial",
194
+ "name": "Russia (Southern/Western Border)",
195
+ "risk_level": "caution",
196
+ "countries": [],
197
+ "airports": ["ROV", "KRR", "VOZ", "BEL"],
198
+ "source": "EASA CZIB 2022-01",
199
+ "details": "Southern Russian airports near Ukraine conflict. Moscow/St. Petersburg unaffected.",
200
+ "updated": "2026-02-28"
201
+ }
202
+ ]
203
+ }