pyopensky 2.16__tar.gz → 2.18__tar.gz

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.
Files changed (52) hide show
  1. pyopensky-2.18/.gitattributes +4 -0
  2. {pyopensky-2.16 → pyopensky-2.18}/.github/workflows/build-docs.yml +3 -3
  3. {pyopensky-2.16 → pyopensky-2.18}/.github/workflows/pypi_publish.yml +3 -3
  4. pyopensky-2.18/.github/workflows/run_tests.yml +123 -0
  5. {pyopensky-2.16 → pyopensky-2.18}/.gitignore +1 -0
  6. {pyopensky-2.16 → pyopensky-2.18}/PKG-INFO +31 -3
  7. {pyopensky-2.16 → pyopensky-2.18}/agents.md +1 -1
  8. {pyopensky-2.16 → pyopensky-2.18}/docs/source/credentials.rst +3 -3
  9. {pyopensky-2.16 → pyopensky-2.18}/docs/source/trino_advanced.rst +154 -3
  10. {pyopensky-2.16 → pyopensky-2.18}/pyproject.toml +18 -18
  11. {pyopensky-2.16 → pyopensky-2.18}/readme.md +27 -0
  12. pyopensky-2.18/scripts/benchmark_trino_routes.py +214 -0
  13. pyopensky-2.18/src/pyopensky/api.py +77 -0
  14. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/config.py +5 -5
  15. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/decoders/pymodes.py +1 -1
  16. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/rebuild.py +21 -21
  17. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/rest.py +41 -36
  18. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/s3.py +11 -8
  19. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/schema.py +4 -2
  20. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/time.py +13 -12
  21. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/trino.py +268 -141
  22. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/tzdata.py +10 -2
  23. {pyopensky-2.16 → pyopensky-2.18}/tests/test_s3.py +2 -1
  24. {pyopensky-2.16 → pyopensky-2.18}/tests/test_trino.py +14 -4
  25. pyopensky-2.18/tests/test_trino_offline.py +642 -0
  26. pyopensky-2.18/uv.lock +4735 -0
  27. pyopensky-2.16/.github/workflows/run_tests.yml +0 -83
  28. pyopensky-2.16/src/pyopensky/api.py +0 -76
  29. pyopensky-2.16/uv.lock +0 -4675
  30. {pyopensky-2.16 → pyopensky-2.18}/.github/dependabot.yml +0 -0
  31. {pyopensky-2.16 → pyopensky-2.18}/.github/renovate.json5 +0 -0
  32. {pyopensky-2.16 → pyopensky-2.18}/docs/.gitignore +0 -0
  33. {pyopensky-2.16 → pyopensky-2.18}/docs/Makefile +0 -0
  34. {pyopensky-2.16 → pyopensky-2.18}/docs/make.bat +0 -0
  35. {pyopensky-2.16 → pyopensky-2.18}/docs/source/_static/main_stylesheet.css +0 -0
  36. {pyopensky-2.16 → pyopensky-2.18}/docs/source/conf.py +0 -0
  37. {pyopensky-2.16 → pyopensky-2.18}/docs/source/index.rst +0 -0
  38. {pyopensky-2.16 → pyopensky-2.18}/docs/source/installation.rst +0 -0
  39. {pyopensky-2.16 → pyopensky-2.18}/docs/source/rebuild.rst +0 -0
  40. {pyopensky-2.16 → pyopensky-2.18}/docs/source/rest.rst +0 -0
  41. {pyopensky-2.16 → pyopensky-2.18}/docs/source/trino.rst +0 -0
  42. {pyopensky-2.16 → pyopensky-2.18}/license.txt +0 -0
  43. {pyopensky-2.16 → pyopensky-2.18}/scripts/decode_pymodes.py +0 -0
  44. {pyopensky-2.16 → pyopensky-2.18}/scripts/decode_rs1090.py +0 -0
  45. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/__init__.py +0 -0
  46. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/decoders/__init__.py +0 -0
  47. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/decoders/base.py +0 -0
  48. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/decoders/rs1090.py +0 -0
  49. {pyopensky-2.16 → pyopensky-2.18}/src/pyopensky/py.typed +0 -0
  50. {pyopensky-2.16 → pyopensky-2.18}/tests/test_rebuild.py +0 -0
  51. {pyopensky-2.16 → pyopensky-2.18}/tests/test_rest.py +0 -0
  52. {pyopensky-2.16 → pyopensky-2.18}/tests/test_tzdata.py +0 -0
@@ -0,0 +1,4 @@
1
+ # Ensure consistent line endings across platforms
2
+ # uv.lock must hash identically on Linux, macOS and Windows so that
3
+ # the GitHub Actions cache key is the same on all runners.
4
+ uv.lock text eol=lf
@@ -15,7 +15,7 @@ jobs:
15
15
  UV_CACHE_DIR: /tmp/.uv-cache
16
16
 
17
17
  steps:
18
- - uses: actions/checkout@v6
18
+ - uses: actions/checkout@v7
19
19
  with:
20
20
  fetch-depth: 0
21
21
 
@@ -23,13 +23,13 @@ jobs:
23
23
  run: curl -LsSf https://astral.sh/uv/install.sh | sh
24
24
 
25
25
  - name: Set up Python
26
- uses: actions/setup-python@v6
26
+ uses: actions/setup-python@v7.1.0
27
27
  with:
28
28
  python-version: ${{ env.PYTHON_VERSION }}
29
29
 
30
30
 
31
31
  - name: Cache folder for uv
32
- uses: actions/cache@v5
32
+ uses: actions/cache@v6
33
33
  with:
34
34
  path: /tmp/.uv-cache
35
35
  key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
@@ -9,16 +9,16 @@ jobs:
9
9
  deploy:
10
10
  runs-on: ubuntu-latest
11
11
  steps:
12
- - uses: actions/checkout@v6
12
+ - uses: actions/checkout@v7
13
13
 
14
14
  - name: Set up uv
15
15
  run: curl -LsSf https://astral.sh/uv/install.sh | sh
16
16
 
17
17
  - name: Set up Python
18
- uses: actions/setup-python@v6
18
+ uses: actions/setup-python@v7.1.0
19
19
 
20
20
  - name: Cache folder for uv
21
- uses: actions/cache@v5
21
+ uses: actions/cache@v6
22
22
  with:
23
23
  path: /tmp/.uv-cache
24
24
  key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
@@ -0,0 +1,123 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ pull_request_target:
6
+ schedule: # keep the cache warm
7
+ - cron: "42 3 * * 0" # every Sunday morning
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ lint:
12
+ name: lint and format
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v7
16
+
17
+ - uses: astral-sh/setup-uv@v10.3.2
18
+ with:
19
+ enable-cache: true
20
+
21
+ - run: uv sync --dev --extra rs1090 --extra cartes
22
+ - run: uv run ruff check
23
+ - run: uv run ruff format --check
24
+ - run: uv cache prune --ci
25
+
26
+ typing:
27
+ name: type checking
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@v7
31
+
32
+ - uses: astral-sh/setup-uv@v10.3.2
33
+ with:
34
+ enable-cache: true
35
+
36
+ - run: uv sync --dev --extra rs1090 --extra cartes
37
+ - run: uv run ty check src tests
38
+ - run: uv cache prune --ci
39
+
40
+ fill_cache:
41
+ name: fill trino cache
42
+ runs-on: ubuntu-latest
43
+ steps:
44
+ - uses: actions/checkout@v7
45
+ with:
46
+ fetch-depth: 0
47
+
48
+ - uses: astral-sh/setup-uv@v10.3.2
49
+ with:
50
+ enable-cache: true
51
+
52
+ - uses: actions/cache@v6
53
+ id: cache
54
+ with:
55
+ path: cache_opensky
56
+ enableCrossOsArchive: true
57
+ key: opensky-${{ hashFiles('uv.lock') }}
58
+
59
+ - run: uv sync --dev --extra rs1090 --extra cartes
60
+
61
+ - name: Fill cache with Trino queries
62
+ if: steps.cache.outputs.cache-hit != 'true'
63
+ env:
64
+ OPENSKY_USERNAME: ${{ secrets.OPENSKY_USERNAME }}
65
+ OPENSKY_PASSWORD: ${{ secrets.OPENSKY_PASSWORD }}
66
+ OPENSKY_CACHE: ${{ github.workspace }}/cache_opensky
67
+ run: uv run pytest tests/test_trino.py tests/test_rebuild.py
68
+
69
+ - run: uv cache prune --ci
70
+
71
+ tests:
72
+ name: python-${{ matrix.python-version }} on ${{ matrix.os }}
73
+ needs: fill_cache
74
+ runs-on: ${{ matrix.os }}
75
+ strategy:
76
+ fail-fast: false
77
+ matrix:
78
+ os:
79
+ - ubuntu-latest
80
+ - windows-latest
81
+ - macos-latest
82
+ python-version:
83
+ - "3.10"
84
+ - "3.11"
85
+ - "3.12"
86
+ - "3.13"
87
+ - "3.14"
88
+ # `trino` (v0.336.0) depends on `orjson` (v3.11.5)
89
+ # which does not support Python 3.14t
90
+ # https://github.com/ijl/orjson/issues/591#issuecomment-3160351169
91
+ # - "3.14t"
92
+ exclude:
93
+ # greenlet v3.2.4 is not available for Python 3.14t on Windows
94
+ # and fails to compile there. Not much we can do about that right now.
95
+ - os: windows-latest
96
+ python-version: "3.14t"
97
+ include:
98
+ - os: ubuntu-latest
99
+ python-version: "3.13"
100
+ rest: true
101
+ steps:
102
+ - uses: actions/checkout@v7
103
+ with:
104
+ fetch-depth: 0
105
+
106
+ - uses: astral-sh/setup-uv@v10.3.2
107
+ with:
108
+ enable-cache: true
109
+
110
+ - uses: actions/cache@v6
111
+ with:
112
+ path: cache_opensky
113
+ enableCrossOsArchive: true
114
+ key: opensky-${{ hashFiles('uv.lock') }}
115
+
116
+ - run: uv sync --dev --python ${{ matrix.python-version }} --extra rs1090 --extra cartes
117
+
118
+ - name: Run tests
119
+ env:
120
+ OPENSKY_CACHE: ${{ github.workspace }}/cache_opensky
121
+ run: uv run pytest ${{ ! matrix.rest && '--ignore=tests/test_rest.py' || '' }}
122
+
123
+ - run: uv cache prune --ci
@@ -33,3 +33,4 @@ var/
33
33
  *.manifest
34
34
  *.spec
35
35
 
36
+ .secrets
@@ -1,11 +1,11 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: pyopensky
3
- Version: 2.16
3
+ Version: 2.18
4
4
  Summary: A Python interface for OpenSky database
5
5
  Project-URL: Repository, https://github.com/open-aviation/pyopensky/
6
6
  Author-email: Xavier Olive <git@xoolive.org>, Junzi Sun <j.sun-1@tudelft.nl>
7
7
  License-Expression: MIT
8
- Requires-Python: >=3.9
8
+ Requires-Python: >=3.10
9
9
  Requires-Dist: appdirs>=1.4.4
10
10
  Requires-Dist: httpx>=0.27.2
11
11
  Requires-Dist: minio>=7.2.8
@@ -14,6 +14,7 @@ Requires-Dist: pandas>=2.2.2
14
14
  Requires-Dist: pyarrow>=17.0.0
15
15
  Requires-Dist: pyjwt>=2.9.0
16
16
  Requires-Dist: python-dotenv>=1.0.1
17
+ Requires-Dist: stamina>=25.1.0
17
18
  Requires-Dist: tqdm>=4.66.5
18
19
  Requires-Dist: trino[sqlalchemy]>=0.329.0
19
20
  Requires-Dist: typing-extensions>=4.12.2
@@ -137,5 +138,32 @@ trino.history(start, stop, *, callsign, icao24, bounds)
137
138
  trino.rebuild(start, stop, *, icao24, bounds)
138
139
  ```
139
140
 
141
+ For several routes in the same time window, use one query with exact directed
142
+ airport pairs:
143
+
144
+ ```python
145
+ df = trino.history(
146
+ "2025-01-01",
147
+ "2025-01-02",
148
+ routes=[("KJFK", "EGLL"), ("KMIA", "EIDW"), ("LFPG", "KJFK")],
149
+ selected_columns=("time", "icao24", "callsign", "lat", "lon"),
150
+ )
151
+ ```
152
+
153
+ The same `routes` filter is available in `flightlist()` for metadata queries and
154
+ `rawdata()` for raw messages.
155
+ `history()` clips trajectories to the requested time window, including flights
156
+ that cross midnight.
157
+
158
+ Optional Trino session settings apply to all queries on a client. For selective
159
+ flight joins whose filtered flight table fits in each worker's memory:
160
+
161
+ ```python
162
+ trino = Trino(session_properties={"join_distribution_type": "BROADCAST"})
163
+ ```
164
+
165
+ `Trino()` uses the server's default settings. Use `cached=False` on each call when
166
+ benchmarking query performance.
167
+
140
168
  > [!IMPORTANT]
141
169
  > Refer to the [documentation](https://mode-s.org/pyopensky) for advanced usage and parameter details.
@@ -7,7 +7,7 @@
7
7
  ## Coding style
8
8
 
9
9
  - Run `uv run ruff check` and `uv run ruff format` to check and format code
10
- - Run `uvx ty check src/ tests/` to check types
10
+ - Run `uv run ty check src/ tests/` to check types
11
11
 
12
12
  ## Documentation commands
13
13
 
@@ -5,9 +5,9 @@ Apply for access
5
5
  ----------------
6
6
 
7
7
  Access to the Trino database will be granted provided you meet the `conditions
8
- defined by The OpenSky Network <https://opensky-network.org/about/faq#q5>`_. Please create an account on
9
- https://trino.opensky-network.org, and then apply at https://opensky-network.org/data/apply
10
- with your Trino username to request for activation.
8
+ defined by The OpenSky Network <https://opensky-network.org/about/faq>`_. Please create an account on
9
+ https://trino.opensky-network.org, and then apply at https://opensky-network.org/my-opensky/request-data
10
+ to request activation.
11
11
 
12
12
  Configuration
13
13
  -------------
@@ -70,6 +70,155 @@ Use ``with_only_columns()`` to reduce data transfer and improve query performanc
70
70
  - Schema attribute: ``StateVectorsData4.lat``
71
71
  - Qualified string: ``"StateVectorsData4.lat"`` or ``"FlightsData4.estdepartureairport"``
72
72
 
73
+ Configuring Trino sessions
74
+ ---------------------------
75
+
76
+ Pass ``session_properties`` to ``Trino`` to configure each connection opened by
77
+ that client. Names and values are strings. For example, a broadcast join can
78
+ reduce CPU and worker network traffic for selective flight queries:
79
+
80
+ .. code-block:: python
81
+
82
+ from pyopensky.trino import Trino
83
+
84
+ trino = Trino(
85
+ session_properties={"join_distribution_type": "BROADCAST"}
86
+ )
87
+ df = trino.history(
88
+ "2025-06-01 12:00",
89
+ "2025-06-01 13:00",
90
+ routes=[("EGLL", "KJFK"), ("LFPG", "KJFK"), ("EHAM", "KJFK")],
91
+ selected_columns=("time", "icao24", "callsign", "lat", "lon"),
92
+ )
93
+
94
+ ``Trino()`` uses the server's default session settings. Broadcast joins are
95
+ optional: the filtered build table must fit in each worker's memory. For large
96
+ flight selections, let the server choose the join strategy or choose another
97
+ session configuration. See `Trino join distribution selection
98
+ <https://trino.io/docs/current/optimizer/cost-based-optimizations.html#join-distribution-selection>`_.
99
+
100
+ Session properties apply to ``query()``, ``history()``, ``flightlist()``, and
101
+ ``rawdata()`` on that client. The constructor copies the supplied mapping.
102
+ Result-cache keys include configured properties, so different configurations do
103
+ not reuse each other's results. To measure query performance, pass
104
+ ``cached=False`` to every measured call.
105
+
106
+ Batching directed routes
107
+ ------------------------
108
+
109
+ Use ``routes`` to retrieve several directed airport pairs in one query for the
110
+ same time window. This avoids submitting one state-vector query per route.
111
+
112
+ .. code-block:: python
113
+
114
+ from pyopensky.trino import Trino
115
+
116
+ trino = Trino()
117
+ routes = [
118
+ ("KJFK", "EGLL"),
119
+ ("KMIA", "EIDW"),
120
+ ("LFPG", "KJFK"),
121
+ ]
122
+ df = trino.history(
123
+ "2025-01-01",
124
+ "2025-01-02",
125
+ routes=routes,
126
+ selected_columns=(
127
+ "time", "icao24", "callsign", "lat", "lon", "baroaltitude",
128
+ "FlightsData4.firstseen", "FlightsData4.lastseen",
129
+ "FlightsData4.estdepartureairport",
130
+ "FlightsData4.estarrivalairport",
131
+ ),
132
+ )
133
+
134
+ Each pair contains the estimated departure and arrival ICAO identifiers, in that
135
+ order. Only the listed pairs match. For example, ``[("KJFK", "EGLL"),
136
+ ("KMIA", "EIDW")]`` does not include KJFK to EIDW or the reverse directions.
137
+ Separate ``departure_airport`` and ``arrival_airport`` lists select every
138
+ combination between those lists.
139
+
140
+ Route identifiers match exactly; SQL wildcards are not expanded. An empty route
141
+ list selects no flights. Repeating a pair does not duplicate its results. Other
142
+ filters, such as ``icao24``, ``callsign``, bounds and airport filters, further
143
+ restrict the selected routes.
144
+
145
+ For long periods, loop over time windows and include all required routes in each
146
+ call. Select the columns needed for the analysis. Actual scan savings depend on
147
+ the Trino query plan and table layout.
148
+
149
+ If only flight metadata is needed, use the same route list with
150
+ ``trino.flightlist(start, stop, routes=routes)``. With routes alone,
151
+ ``flightlist()`` filters by ``lastseen``. Setting ``departure_airport`` also
152
+ selects its existing departure-time behavior.
153
+
154
+ .. note::
155
+
156
+ ``history()`` returns state vectors within ``start`` and ``stop``. These
157
+ timestamps do not select flights by their departure or arrival time. A flight
158
+ that crosses midnight can be partial in a daily result. ``time_buffer``
159
+ extends the bounds around each flight's ``firstseen`` and ``lastseen`` but
160
+ does not extend the requested state-vector window. Each call submits one
161
+ query; pyopensky does not split that window automatically.
162
+
163
+ Filtering flight metadata in history queries
164
+ --------------------------------------------
165
+
166
+ ``history()`` accepts ``FlightsData4`` conditions alongside state-vector
167
+ conditions. Flight columns refer to the flight table joined to the state vectors.
168
+ A flight condition also enables this join when no airport or route is specified.
169
+ Selecting flight columns also enables this join. Flight schema attributes can
170
+ be used directly in ``selected_columns``.
171
+
172
+ .. code-block:: python
173
+
174
+ from pyopensky.schema import FlightsData4, StateVectorsData4
175
+ from pyopensky.trino import Trino
176
+
177
+ df = Trino().history(
178
+ "2025-01-01",
179
+ "2025-01-02",
180
+ FlightsData4.estarrivalairporthorizdistance < 5000,
181
+ routes=[("KJFK", "EGLL"), ("LFPG", "KJFK")],
182
+ selected_columns=(
183
+ StateVectorsData4.time,
184
+ StateVectorsData4.icao24,
185
+ FlightsData4.estdepartureairport,
186
+ FlightsData4.estarrivalairport,
187
+ ),
188
+ )
189
+
190
+ Raw messages for directed routes
191
+ --------------------------------
192
+
193
+ ``rawdata()`` also accepts exact ``routes``. It joins raw messages to matching
194
+ flights by aircraft address and the flight's ``firstseen``/``lastseen`` interval,
195
+ then clips messages to the requested time window. FlightsData4 conditions and
196
+ extra columns use this joined flight table.
197
+
198
+ .. code-block:: python
199
+
200
+ from pyopensky.schema import FlightsData4, PositionData4
201
+ from pyopensky.trino import Trino
202
+
203
+ df = Trino().rawdata(
204
+ "2025-01-01",
205
+ "2025-01-02",
206
+ FlightsData4.estarrivalairporthorizdistance < 5000,
207
+ routes=[("KJFK", "EGLL"), ("LFPG", "KJFK")],
208
+ Table=PositionData4,
209
+ extra_columns=(FlightsData4.estdepartureairport,),
210
+ )
211
+
212
+ An empty route list selects no messages. Repeated pairs do not duplicate results.
213
+ Airport filters and ``icao24`` further restrict the selected routes. Flight
214
+ conditions or flight extra columns enable a flight join even without an airport
215
+ or route argument.
216
+
217
+ ``rawdata()`` supports one selection mode per call: flight metadata (airports,
218
+ routes, flight conditions or flight extra columns), geographic bounds, or
219
+ callsign. These modes cannot be combined. Conditions on the raw table can
220
+ further restrict any mode. Its existing raw-column output is preserved.
221
+
73
222
 
74
223
  Spatial queries with bounds
75
224
  ---------------------------
@@ -181,7 +330,8 @@ Filter by sensor serial numbers to get data from specific ground stations:
181
330
  Extending temporal range for airport queries
182
331
  --------------------------------------------
183
332
 
184
- Use ``time_buffer`` to include aircraft that departed/arrived slightly outside the time window:
333
+ Use ``time_buffer`` to include state vectors just before a flight's ``firstseen``
334
+ or after its ``lastseen``, within the requested time window:
185
335
 
186
336
  .. code-block:: python
187
337
 
@@ -189,7 +339,7 @@ Use ``time_buffer`` to include aircraft that departed/arrived slightly outside t
189
339
 
190
340
  trino = Trino()
191
341
 
192
- # Get flights with 30-minute buffer before/after time window
342
+ # Allow samples up to 30 minutes before/after each flight's time bounds
193
343
  df = trino.history(
194
344
  start="2024-03-16 09:00",
195
345
  stop="2024-03-16 11:00",
@@ -197,7 +347,8 @@ Use ``time_buffer`` to include aircraft that departed/arrived slightly outside t
197
347
  time_buffer="30m", # Also accepts "1h", "45min", etc.
198
348
  )
199
349
 
200
- This is useful when you want complete flight trajectories that may start or end outside your time range.
350
+ The result remains clipped to 09:00 through 11:00. To retrieve samples outside
351
+ that window, extend ``start`` or ``stop`` explicitly.
201
352
 
202
353
  Filtering with boolean expressions
203
354
  ----------------------------------
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "pyopensky"
3
- version = "2.16"
3
+ version = "2.18"
4
4
  description = "A Python interface for OpenSky database"
5
5
  authors = [{ name = "Xavier Olive", email = "git@xoolive.org" }, { name = "Junzi Sun", email = "j.sun-1@tudelft.nl" }]
6
6
  license = "MIT"
7
7
  readme = "readme.md"
8
- requires-python = ">=3.9"
8
+ requires-python = ">=3.10"
9
9
  dependencies = [
10
10
  "appdirs >= 1.4.4",
11
11
  "httpx >= 0.27.2",
@@ -15,6 +15,7 @@ dependencies = [
15
15
  "pyarrow >= 17.0.0",
16
16
  "pyjwt >= 2.9.0",
17
17
  "python-dotenv>=1.0.1",
18
+ "stamina>=25.1.0",
18
19
  "tqdm >= 4.66.5",
19
20
  "trino[sqlalchemy] >= 0.329.0",
20
21
  "typing-extensions >= 4.12.2",
@@ -35,8 +36,6 @@ build-backend = "hatchling.build"
35
36
 
36
37
  [dependency-groups]
37
38
  dev = [
38
- "pre-commit >= 3.8.0",
39
- "mypy >= 1.11.2",
40
39
  "ruff >= 0.6.3",
41
40
  "pytest >= 8.3.2",
42
41
  "ipykernel >= 7.1.0; python_version>='3.10'",
@@ -44,6 +43,7 @@ dev = [
44
43
  "sphinx-design >= 0.6.1; python_version>'3.11'",
45
44
  "sphinx-rtd-theme >= 2.0.0; python_version>'3.11'",
46
45
  "sphinx-autodoc-typehints >= 2.3.0; python_version>'3.11'",
46
+ "ty>=0.0.17",
47
47
  ]
48
48
 
49
49
  [tool.ruff]
@@ -58,21 +58,21 @@ lint.select = [
58
58
  "RUF",
59
59
  ]
60
60
  line-length = 80
61
- target-version = "py39"
61
+ target-version = "py310"
62
62
 
63
63
  [tool.ruff.lint.isort]
64
64
  known-first-party = ["numpy", "pandas"]
65
65
 
66
- [tool.mypy]
67
- python_version = "3.9"
68
- platform = "posix"
69
- color_output = true
70
- pretty = true
71
- show_column_numbers = true
72
- strict = true
73
- check_untyped_defs = true
74
- ignore_missing_imports = true
75
- warn_no_return = true
76
- warn_redundant_casts = true
77
- warn_unused_configs = true
78
- warn_unused_ignores = true
66
+ [tool.ty.src]
67
+ exclude = ["src/pyopensky/decoders/pymodes.py"]
68
+
69
+ [[tool.ty.overrides]]
70
+ # SQLAlchemy models expose their columns dynamically. Ty cannot verify these
71
+ # attributes for the heterogeneous raw-message table union.
72
+ include = ["src/pyopensky/rest.py", "src/pyopensky/trino.py"]
73
+ rules = { "unresolved-attribute" = "ignore" }
74
+
75
+ [tool.pytest.ini_options]
76
+ log_cli = true
77
+ log_cli_level = "DEBUG"
78
+ log_cli_format = "%(levelname)s %(name)s:%(filename)s:%(lineno)d %(message)s"
@@ -109,5 +109,32 @@ trino.history(start, stop, *, callsign, icao24, bounds)
109
109
  trino.rebuild(start, stop, *, icao24, bounds)
110
110
  ```
111
111
 
112
+ For several routes in the same time window, use one query with exact directed
113
+ airport pairs:
114
+
115
+ ```python
116
+ df = trino.history(
117
+ "2025-01-01",
118
+ "2025-01-02",
119
+ routes=[("KJFK", "EGLL"), ("KMIA", "EIDW"), ("LFPG", "KJFK")],
120
+ selected_columns=("time", "icao24", "callsign", "lat", "lon"),
121
+ )
122
+ ```
123
+
124
+ The same `routes` filter is available in `flightlist()` for metadata queries and
125
+ `rawdata()` for raw messages.
126
+ `history()` clips trajectories to the requested time window, including flights
127
+ that cross midnight.
128
+
129
+ Optional Trino session settings apply to all queries on a client. For selective
130
+ flight joins whose filtered flight table fits in each worker's memory:
131
+
132
+ ```python
133
+ trino = Trino(session_properties={"join_distribution_type": "BROADCAST"})
134
+ ```
135
+
136
+ `Trino()` uses the server's default settings. Use `cached=False` on each call when
137
+ benchmarking query performance.
138
+
112
139
  > [!IMPORTANT]
113
140
  > Refer to the [documentation](https://mode-s.org/pyopensky) for advanced usage and parameter details.