pyopensky 2.17__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.
- {pyopensky-2.17 → pyopensky-2.18}/.github/workflows/build-docs.yml +1 -1
- {pyopensky-2.17 → pyopensky-2.18}/.github/workflows/pypi_publish.yml +1 -1
- {pyopensky-2.17 → pyopensky-2.18}/.github/workflows/run_tests.yml +4 -4
- {pyopensky-2.17 → pyopensky-2.18}/.gitignore +1 -0
- {pyopensky-2.17 → pyopensky-2.18}/PKG-INFO +28 -1
- {pyopensky-2.17 → pyopensky-2.18}/docs/source/trino_advanced.rst +154 -3
- {pyopensky-2.17 → pyopensky-2.18}/pyproject.toml +1 -1
- {pyopensky-2.17 → pyopensky-2.18}/readme.md +27 -0
- pyopensky-2.18/scripts/benchmark_trino_routes.py +214 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/api.py +9 -7
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/trino.py +196 -89
- pyopensky-2.18/tests/test_trino_offline.py +642 -0
- {pyopensky-2.17 → pyopensky-2.18}/uv.lock +101 -101
- {pyopensky-2.17 → pyopensky-2.18}/.gitattributes +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/.github/dependabot.yml +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/.github/renovate.json5 +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/agents.md +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/docs/.gitignore +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/docs/Makefile +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/docs/make.bat +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/docs/source/_static/main_stylesheet.css +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/docs/source/conf.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/docs/source/credentials.rst +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/docs/source/index.rst +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/docs/source/installation.rst +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/docs/source/rebuild.rst +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/docs/source/rest.rst +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/docs/source/trino.rst +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/license.txt +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/scripts/decode_pymodes.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/scripts/decode_rs1090.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/__init__.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/config.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/decoders/__init__.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/decoders/base.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/decoders/pymodes.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/decoders/rs1090.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/py.typed +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/rebuild.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/rest.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/s3.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/schema.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/time.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/src/pyopensky/tzdata.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/tests/test_rebuild.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/tests/test_rest.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/tests/test_s3.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/tests/test_trino.py +0 -0
- {pyopensky-2.17 → pyopensky-2.18}/tests/test_tzdata.py +0 -0
|
@@ -14,7 +14,7 @@ jobs:
|
|
|
14
14
|
steps:
|
|
15
15
|
- uses: actions/checkout@v7
|
|
16
16
|
|
|
17
|
-
- uses: astral-sh/setup-uv@v10.
|
|
17
|
+
- uses: astral-sh/setup-uv@v10.3.2
|
|
18
18
|
with:
|
|
19
19
|
enable-cache: true
|
|
20
20
|
|
|
@@ -29,7 +29,7 @@ jobs:
|
|
|
29
29
|
steps:
|
|
30
30
|
- uses: actions/checkout@v7
|
|
31
31
|
|
|
32
|
-
- uses: astral-sh/setup-uv@v10.
|
|
32
|
+
- uses: astral-sh/setup-uv@v10.3.2
|
|
33
33
|
with:
|
|
34
34
|
enable-cache: true
|
|
35
35
|
|
|
@@ -45,7 +45,7 @@ jobs:
|
|
|
45
45
|
with:
|
|
46
46
|
fetch-depth: 0
|
|
47
47
|
|
|
48
|
-
- uses: astral-sh/setup-uv@v10.
|
|
48
|
+
- uses: astral-sh/setup-uv@v10.3.2
|
|
49
49
|
with:
|
|
50
50
|
enable-cache: true
|
|
51
51
|
|
|
@@ -103,7 +103,7 @@ jobs:
|
|
|
103
103
|
with:
|
|
104
104
|
fetch-depth: 0
|
|
105
105
|
|
|
106
|
-
- uses: astral-sh/setup-uv@v10.
|
|
106
|
+
- uses: astral-sh/setup-uv@v10.3.2
|
|
107
107
|
with:
|
|
108
108
|
enable-cache: true
|
|
109
109
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyopensky
|
|
3
|
-
Version: 2.
|
|
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>
|
|
@@ -138,5 +138,32 @@ trino.history(start, stop, *, callsign, icao24, bounds)
|
|
|
138
138
|
trino.rebuild(start, stop, *, icao24, bounds)
|
|
139
139
|
```
|
|
140
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
|
+
|
|
141
168
|
> [!IMPORTANT]
|
|
142
169
|
> Refer to the [documentation](https://mode-s.org/pyopensky) for advanced usage and parameter details.
|
|
@@ -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
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
----------------------------------
|
|
@@ -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.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Compare separate route queries at a Git revision with one routes query.
|
|
2
|
+
|
|
3
|
+
Run from the repository root, for example:
|
|
4
|
+
uv run --env-file .secrets scripts/benchmark_trino_routes.py \
|
|
5
|
+
--baseline-ref origin/master --output /tmp/trino-routes-benchmark.json
|
|
6
|
+
|
|
7
|
+
This runs live Trino queries. Every query uses cached=False and an isolated,
|
|
8
|
+
temporary result-cache directory. Server and storage caches are not controlled.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import hashlib
|
|
15
|
+
import json
|
|
16
|
+
import platform
|
|
17
|
+
import subprocess
|
|
18
|
+
import time
|
|
19
|
+
import types
|
|
20
|
+
from contextlib import contextmanager
|
|
21
|
+
from importlib.metadata import version
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from tempfile import TemporaryDirectory
|
|
24
|
+
from typing import Any, Iterable, Iterator
|
|
25
|
+
from unittest.mock import patch
|
|
26
|
+
|
|
27
|
+
from sqlalchemy import CursorResult
|
|
28
|
+
|
|
29
|
+
import pandas as pd
|
|
30
|
+
import pyopensky.trino as current
|
|
31
|
+
from pyopensky.trino import Trino
|
|
32
|
+
|
|
33
|
+
COLUMNS = ("time", "icao24", "callsign", "lat", "lon", "baroaltitude")
|
|
34
|
+
DEFAULT_ROUTES = [("EGLL", "KJFK"), ("LFPG", "KJFK"), ("EHAM", "KJFK")]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@contextmanager
|
|
38
|
+
def instrument(client: Trino, records: list[dict[str, Any]]) -> Iterator[None]:
|
|
39
|
+
process_result = client.process_result
|
|
40
|
+
|
|
41
|
+
def record(
|
|
42
|
+
result: CursorResult[Any], batch_size: int = 50_000
|
|
43
|
+
) -> Iterable[pd.DataFrame]:
|
|
44
|
+
cursor = result.cursor
|
|
45
|
+
yield from process_result(result, batch_size)
|
|
46
|
+
assert cursor is not None
|
|
47
|
+
records.append(dict(cursor.stats))
|
|
48
|
+
|
|
49
|
+
with patch.object(client, "process_result", record):
|
|
50
|
+
yield
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def run(
|
|
54
|
+
client: Trino,
|
|
55
|
+
mode: str,
|
|
56
|
+
start: str,
|
|
57
|
+
stop: str,
|
|
58
|
+
routes: list[tuple[str, str]],
|
|
59
|
+
) -> tuple[pd.DataFrame, float]:
|
|
60
|
+
frames = []
|
|
61
|
+
begin = time.perf_counter()
|
|
62
|
+
if mode == "before":
|
|
63
|
+
for departure, arrival in routes:
|
|
64
|
+
frame = client.history(
|
|
65
|
+
start,
|
|
66
|
+
stop,
|
|
67
|
+
departure_airport=departure,
|
|
68
|
+
arrival_airport=arrival,
|
|
69
|
+
selected_columns=COLUMNS,
|
|
70
|
+
cached=False,
|
|
71
|
+
)
|
|
72
|
+
if frame is not None:
|
|
73
|
+
frames.append(frame)
|
|
74
|
+
else:
|
|
75
|
+
frame = client.history(
|
|
76
|
+
start, stop, routes=routes, selected_columns=COLUMNS, cached=False
|
|
77
|
+
)
|
|
78
|
+
if frame is not None:
|
|
79
|
+
frames.append(frame)
|
|
80
|
+
result = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
|
|
81
|
+
return result, time.perf_counter() - begin
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main() -> None:
|
|
85
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
86
|
+
parser.add_argument("--baseline-ref", required=True)
|
|
87
|
+
parser.add_argument("--start", default="2025-06-01 12:00")
|
|
88
|
+
parser.add_argument("--stop", default="2025-06-01 13:00")
|
|
89
|
+
parser.add_argument("--route", action="append", help="DEPARTURE:ARRIVAL")
|
|
90
|
+
parser.add_argument("--rounds", type=int, default=2)
|
|
91
|
+
parser.add_argument(
|
|
92
|
+
"--after-session-property",
|
|
93
|
+
action="append",
|
|
94
|
+
default=[],
|
|
95
|
+
help="NAME=VALUE; configure the batched client only (repeatable)",
|
|
96
|
+
)
|
|
97
|
+
parser.add_argument("--output", type=Path, required=True)
|
|
98
|
+
args = parser.parse_args()
|
|
99
|
+
if args.rounds < 1:
|
|
100
|
+
parser.error("--rounds must be at least 1")
|
|
101
|
+
session_properties = {}
|
|
102
|
+
for setting in args.after_session_property:
|
|
103
|
+
name, separator, value = setting.partition("=")
|
|
104
|
+
if not separator or not name or not value:
|
|
105
|
+
parser.error(
|
|
106
|
+
"--after-session-property must have the form NAME=VALUE"
|
|
107
|
+
)
|
|
108
|
+
session_properties[name] = value
|
|
109
|
+
routes = DEFAULT_ROUTES
|
|
110
|
+
if args.route:
|
|
111
|
+
routes = []
|
|
112
|
+
for route in args.route:
|
|
113
|
+
parts = route.split(":")
|
|
114
|
+
if len(parts) != 2 or not all(parts):
|
|
115
|
+
parser.error("--route must have the form DEPARTURE:ARRIVAL")
|
|
116
|
+
routes.append((parts[0], parts[1]))
|
|
117
|
+
routes = list(dict.fromkeys(routes))
|
|
118
|
+
|
|
119
|
+
baseline_commit = subprocess.check_output(
|
|
120
|
+
["git", "rev-parse", "--verify", f"{args.baseline_ref}^{{commit}}"],
|
|
121
|
+
text=True,
|
|
122
|
+
).strip()
|
|
123
|
+
source = subprocess.check_output(
|
|
124
|
+
["git", "show", f"{baseline_commit}:src/pyopensky/trino.py"], text=True
|
|
125
|
+
)
|
|
126
|
+
baseline = types.ModuleType("pyopensky._routes_benchmark_before")
|
|
127
|
+
baseline.__package__ = "pyopensky"
|
|
128
|
+
exec(
|
|
129
|
+
compile(source, f"{baseline_commit}:trino.py", "exec"),
|
|
130
|
+
baseline.__dict__,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
report: dict[str, Any] = {
|
|
134
|
+
"baseline_commit": baseline_commit,
|
|
135
|
+
"after_source_sha256": hashlib.sha256(
|
|
136
|
+
Path(current.__file__).read_bytes()
|
|
137
|
+
).hexdigest(),
|
|
138
|
+
"started_at_utc": pd.Timestamp.now(tz="UTC").isoformat(),
|
|
139
|
+
"start": args.start,
|
|
140
|
+
"stop": args.stop,
|
|
141
|
+
"timezone": "UTC",
|
|
142
|
+
"routes": routes,
|
|
143
|
+
"selected_columns": COLUMNS,
|
|
144
|
+
"cached": False,
|
|
145
|
+
"after_session_properties": session_properties,
|
|
146
|
+
"cache_directory": "isolated temporary directory",
|
|
147
|
+
"server_and_storage_caches": "not controlled",
|
|
148
|
+
"python": platform.python_version(),
|
|
149
|
+
"packages": {
|
|
150
|
+
name: version(name) for name in ("pyopensky", "trino", "sqlalchemy")
|
|
151
|
+
},
|
|
152
|
+
"runs": [],
|
|
153
|
+
}
|
|
154
|
+
with TemporaryDirectory(prefix="pyopensky-routes-benchmark-") as directory:
|
|
155
|
+
current.cache_path = Path(directory)
|
|
156
|
+
setattr(baseline, "cache_path", Path(directory))
|
|
157
|
+
after = Trino(session_properties=session_properties)
|
|
158
|
+
after.token() # Authenticate before timing either workload.
|
|
159
|
+
before = baseline.Trino()
|
|
160
|
+
before._token = after._token
|
|
161
|
+
clients = {"before": before, "after": after}
|
|
162
|
+
reference = None
|
|
163
|
+
for round_number in range(args.rounds):
|
|
164
|
+
order = (
|
|
165
|
+
("before", "after")
|
|
166
|
+
if round_number % 2 == 0
|
|
167
|
+
else ("after", "before")
|
|
168
|
+
)
|
|
169
|
+
for mode in order:
|
|
170
|
+
records: list[dict[str, Any]] = []
|
|
171
|
+
client = clients[mode]
|
|
172
|
+
with instrument(client, records):
|
|
173
|
+
frame, seconds = run(
|
|
174
|
+
client, mode, args.start, args.stop, routes
|
|
175
|
+
)
|
|
176
|
+
canonical = (
|
|
177
|
+
frame.sort_values(list(COLUMNS)).reset_index(drop=True)
|
|
178
|
+
if len(frame)
|
|
179
|
+
else frame
|
|
180
|
+
)
|
|
181
|
+
if reference is None:
|
|
182
|
+
reference = canonical
|
|
183
|
+
else:
|
|
184
|
+
pd.testing.assert_frame_equal(reference, canonical)
|
|
185
|
+
assert not list(Path(directory).iterdir()), "Cache was written"
|
|
186
|
+
run_report = {
|
|
187
|
+
"round": round_number + 1,
|
|
188
|
+
"mode": mode,
|
|
189
|
+
"rows": len(frame),
|
|
190
|
+
"seconds": seconds,
|
|
191
|
+
"query_count": len(records),
|
|
192
|
+
"physical_input_bytes": sum(
|
|
193
|
+
record["physicalInputBytes"] for record in records
|
|
194
|
+
),
|
|
195
|
+
"cpu_ms": sum(
|
|
196
|
+
record["cpuTimeMillis"] for record in records
|
|
197
|
+
),
|
|
198
|
+
"queries": records,
|
|
199
|
+
}
|
|
200
|
+
report["runs"].append(run_report)
|
|
201
|
+
args.output.write_text(json.dumps(report, indent=2))
|
|
202
|
+
print(
|
|
203
|
+
json.dumps(
|
|
204
|
+
{k: v for k, v in run_report.items() if k != "queries"}
|
|
205
|
+
),
|
|
206
|
+
flush=True,
|
|
207
|
+
)
|
|
208
|
+
report["identical_results"] = True
|
|
209
|
+
report["result_cache_files_written"] = 0
|
|
210
|
+
args.output.write_text(json.dumps(report, indent=2))
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
if __name__ == "__main__":
|
|
214
|
+
main()
|
|
@@ -26,6 +26,7 @@ class OpenSkyDBAPI(Protocol):
|
|
|
26
26
|
departure_airport: str | list[str] | None = None,
|
|
27
27
|
arrival_airport: str | list[str] | None = None,
|
|
28
28
|
airport: str | list[str] | None = None,
|
|
29
|
+
routes: list[tuple[str, str]] | None = None,
|
|
29
30
|
callsign: str | list[str] | None = None,
|
|
30
31
|
icao24: str | list[str] | None = None,
|
|
31
32
|
cached: bool = True,
|
|
@@ -39,16 +40,16 @@ class OpenSkyDBAPI(Protocol):
|
|
|
39
40
|
start: timelike,
|
|
40
41
|
stop: timelike | None = None,
|
|
41
42
|
*args: Any,
|
|
42
|
-
# date_delta: timedelta = timedelta(hours=1),
|
|
43
43
|
callsign: str | list[str] | None = None,
|
|
44
44
|
icao24: str | list[str] | None = None,
|
|
45
45
|
serials: int | Iterable[int] | None = None,
|
|
46
46
|
bounds: (
|
|
47
47
|
str | HasBounds | tuple[float, float, float, float] | None
|
|
48
48
|
) = None,
|
|
49
|
-
departure_airport: str | None = None,
|
|
50
|
-
arrival_airport: str | None = None,
|
|
51
|
-
airport: str | None = None,
|
|
49
|
+
departure_airport: str | list[str] | None = None,
|
|
50
|
+
arrival_airport: str | list[str] | None = None,
|
|
51
|
+
airport: str | list[str] | None = None,
|
|
52
|
+
routes: list[tuple[str, str]] | None = None,
|
|
52
53
|
time_buffer: str | pd.Timedelta | None = None,
|
|
53
54
|
cached: bool = True,
|
|
54
55
|
compress: bool = False,
|
|
@@ -65,9 +66,10 @@ class OpenSkyDBAPI(Protocol):
|
|
|
65
66
|
serials: int | Iterable[int] | None = None,
|
|
66
67
|
bounds: HasBounds | tuple[float, float, float, float] | None = None,
|
|
67
68
|
callsign: str | list[str] | None = None,
|
|
68
|
-
departure_airport: str | None = None,
|
|
69
|
-
arrival_airport: str | None = None,
|
|
70
|
-
airport: str | None = None,
|
|
69
|
+
departure_airport: str | list[str] | None = None,
|
|
70
|
+
arrival_airport: str | list[str] | None = None,
|
|
71
|
+
airport: str | list[str] | None = None,
|
|
72
|
+
routes: list[tuple[str, str]] | None = None,
|
|
71
73
|
cached: bool = True,
|
|
72
74
|
compress: bool = False,
|
|
73
75
|
limit: int | None = None,
|