Caching your MD5 hash is premature optimisation, and indeed there are more important things you should be caring about. For instance, you're serialising JSON twice; instead you should use a Requests prepared request that does the serialisation once. Even this is insignificant in comparison to the time in flight on the network.
I don't know that __all__
is all that important to define for something that isn't a module. If you made a proper module with an __init__.py
and __main__.py
perhaps that would change.
QUERY
can be condensed somewhat while still staying legible, since whitespace is insignificant in GraphQL.
Don't set content-type
- Requests does that for you when it sees the json
kwarg.
The number of parameters on news_query
is bordering on needing a class instance for convenience. A named tuple will be lightweight for this purpose.
It's marginally less common to differentiate command-line switches by capitalisation and more common to just choose a different letter from the long-form argument name.
Add some help for arguments that need it, particularly country
.
I don't know why you've set state
as a mandatory parameter, because it doesn't seem that way in the API.
Either as an alternative, or as a straight-up replacement, your command-line program should be outputting results in human-friendly, localised text rather than machine-friendly JSON. I'm on the fence as to whether the command-line arguments should also be localised to German; for now I've left them as English but (perhaps incongruously) shown the result headers in German.
Remove the space on the inside of your shebang line.
Remove your redundant metavar
declarations.
Add first-class support for a Requests Session
, even if you only use it once here. It will make writing a library easier, if that ever happens.
Page number is an internal API implementation detail and should not be exposed to the user. Instead, offer them an optional max-items count, and depaginate.
Note that you're not forming your hash the way this site does. This site hashes the result of the following expression in https://www.adac.de/assets/ui/client....js
:
JSON.stringify({
operationName: i,
variables: a,
query: o,
uri: c,
environment: e,
previewMode: n,
noCacheValue: l
})
which produces (after reformatting)
{
"operationName": "StreetSuggestions",
"variables": {
"filter": "A12",
"country": "D",
"type": "Highway"
},
"query": {
"kind": "Document",
"definitions": [
{
"kind": "OperationDefinition",
"operation": "query",
"name": {
"kind": "Name",
"value": "StreetSuggestions"
},
"variableDefinitions": [
{
"kind": "VariableDefinition",
"variable": {
"kind": "Variable",
"name": {
"kind": "Name",
"value": "filter"
}
},
"type": {
"kind": "NonNullType",
"type": {
"kind": "NamedType",
"name": {
"kind": "Name",
"value": "String"
}
}
},
"directives": []
},
{
"kind": "VariableDefinition",
"variable": {
"kind": "Variable",
"name": {
"kind": "Name",
"value": "country"
}
},
"type": {
"kind": "NonNullType",
"type": {
"kind": "NamedType",
"name": {
"kind": "Name",
"value": "String"
}
}
},
"directives": []
},
{
"kind": "VariableDefinition",
"variable": {
"kind": "Variable",
"name": {
"kind": "Name",
"value": "type"
}
},
"type": {
"kind": "NonNullType",
"type": {
"kind": "NamedType",
"name": {
"kind": "Name",
"value": "StreetType"
}
}
},
"directives": []
}
],
"directives": [],
"selectionSet": {
"kind": "SelectionSet",
"selections": [
{
"kind": "Field",
"name": {
"kind": "Name",
"value": "streets"
},
"arguments": [
{
"kind": "Argument",
"name": {
"kind": "Name",
"value": "filter"
},
"value": {
"kind": "Variable",
"name": {
"kind": "Name",
"value": "filter"
}
}
},
{
"kind": "Argument",
"name": {
"kind": "Name",
"value": "country"
},
"value": {
"kind": "Variable",
"name": {
"kind": "Name",
"value": "country"
}
}
},
{
"kind": "Argument",
"name": {
"kind": "Name",
"value": "type"
},
"value": {
"kind": "Variable",
"name": {
"kind": "Name",
"value": "type"
}
}
}
],
"directives": [],
"selectionSet": {
"kind": "SelectionSet",
"selections": [
{
"kind": "FragmentSpread",
"name": {
"kind": "Name",
"value": "StreetSuggestion"
},
"directives": []
},
{
"kind": "Field",
"name": {
"kind": "Name",
"value": "__typename"
}
}
]
}
}
]
}
},
{
"kind": "FragmentDefinition",
"name": {
"kind": "Name",
"value": "StreetSuggestion"
},
"typeCondition": {
"kind": "NamedType",
"name": {
"kind": "Name",
"value": "Street"
}
},
"directives": [],
"selectionSet": {
"kind": "SelectionSet",
"selections": [
{
"kind": "Field",
"name": {
"kind": "Name",
"value": "name"
},
"arguments": [],
"directives": []
},
{
"kind": "Field",
"name": {
"kind": "Name",
"value": "type"
},
"arguments": [],
"directives": []
},
{
"kind": "Field",
"name": {
"kind": "Name",
"value": "country"
},
"arguments": [],
"directives": []
},
{
"kind": "Field",
"name": {
"kind": "Name",
"value": "__typename"
}
}
]
}
}
],
"loc": {
"start": 0,
"end": 264
}
},
"environment": "prod",
"previewMode": false
}
This might not matter.
Suggested
#!/usr/bin/env python3
"""ADAC traffic news API Client."""
from argparse import ArgumentParser, Namespace
from hashlib import md5
from itertools import count, islice
from sys import stdout
from typing import Any, NamedTuple, Optional, Iterator, TextIO
from requests import Session, Request
GRAPHQL = '''query TrafficNews($filter: TrafficNewsFilterInput!) {
trafficNews(filter: $filter) {...TrafficNewsItems}
}
fragment TrafficNewsItems on TrafficNews {
size
items {...TrafficNewsItem}
}
fragment TrafficNewsItem on TrafficNewsItem {
id type details street timeLoss
streetSign {streetNumber country}
headline {
...TrafficNewsDirectionHeadline
...TrafficNewsNonDirectionHeadline
}
}
fragment TrafficNewsDirectionHeadline on TrafficNewsDirectionHeadline {
from to
}
fragment TrafficNewsNonDirectionHeadline on TrafficNewsNonDirectionHeadline {
text
}
'''
class NewsRequest(NamedTuple):
country: str = 'D'
state: str = ''
street: str = ''
construction_sites: bool = False
traffic_news: bool = True
@classmethod
def from_args(cls, args: Namespace) -> 'NewsRequest':
return cls(
country=args.country,
state=args.state,
street=args.street,
construction_sites=args.construction_sites,
)
def query(self, page: int) -> dict[str, Any]:
return {
'operationName': 'TrafficNews',
'variables': {
'filter': {
'country': {
'country': self.country,
'federalState': self.state,
'street': self.street,
'showConstructionSites': self.construction_sites,
'showTrafficNews': self.traffic_news,
'pageNumber': page,
}
}
},
'query': GRAPHQL,
}
class NewsResponse(NamedTuple):
id: int
type: str
country: Optional[str]
street: str
street_number: Optional[str]
headline: Optional[str]
details: str
@classmethod
def from_json(cls, json: dict[str, Any]) -> 'NewsResponse':
street_info = json.get('streetSign') or {}
return cls(
id=json['id'],
type=json['type'],
details=json['details'],
street_number=street_info.get('streetNumber'),
street=json['street'],
country=street_info.get('country'),
headline=json['headline'].get('text'),
)
def print(self, f: TextIO = stdout) -> None:
print(f'Sorte: {self.type}', file=f)
if self.country:
print(f'Land: {self.country}', file=f)
if self.street_number:
print(f'Straße: {self.street_number} {self.street}', file=f)
else:
print(f'Straße: {self.street}', file=f)
if self.headline:
print(f'Überschrift: {self.headline}', file=f)
print(f'Einzelheiten: {self.details}\n', file=f)
def get_traffic_news_page(
session: Session,
news_request: NewsRequest,
page: int,
) -> dict[str, Any]:
request = Request(
method='POST',
url='https://www.adac.de/bff',
headers={'Accept': 'application/json'},
json=news_request.query(page),
)
prepared = session.prepare_request(request)
prepared.headers['x-graphql-query-hash'] = md5(prepared.body).hexdigest()
with session.send(prepared) as response:
response.raise_for_status()
return response.json()['data']['trafficNews']
def get_traffic_news(
session: Session,
request: NewsRequest,
) -> Iterator[NewsResponse]:
# There are apparently 10 items per page, but let's not need to rely on this
n_items = 0
for page in count(1):
data = get_traffic_news_page(session, request, page)
for news in data['items']:
yield NewsResponse.from_json(news)
n_items += 1
if n_items >= data['size']:
break
def get_args(*, description: str = __doc__) -> Namespace:
parser = ArgumentParser(description=description)
parser.add_argument(
'-c', '--country', default='D',
help='Country, one of: D (Germany), A (Austria), I (Italy), CH (Switzerland)',
)
parser.add_argument('-s', '--state')
parser.add_argument('-r', '--street')
parser.add_argument('-t', '--traffic-news', action='store_true')
parser.add_argument('-o', '--construction-sites', action='store_true')
parser.add_argument('-m', '--max-items', type=int)
return parser.parse_args()
def main() -> None:
args = get_args()
request = NewsRequest.from_args(args)
with Session() as session:
all_news = get_traffic_news(session, request)
if args.max_items is not None:
all_news = islice(all_news, args.max_items)
for news in all_news:
news.print()
if __name__ == '__main__':
main()
Output
This is the fully-depaginated output with all default arguments.
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 1 A1
Einzelheiten: Zwischen Köln-Lövenich und Köln-Bocklemünd, Gefahr durch defektes Fahrzeug auf der rechten Seite liegengebliebenes Motorrad
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 1 A1
Überschrift: Dortmund - Köln
Einzelheiten: In beiden Richtungen, Leverkusener Brücke, gesperrt für LKW über 3.5 t, vorübergehende Begrenzung der Breite auf 2.3 m, bis 31.12.2025
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 1 A1
Einzelheiten: Zwischen Lengerich und Kreuz Lotte/Osnabrück, Gefahr durch ein totes Tier auf der Fahrbahn
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 2 A2
Einzelheiten: Ausfahrt zur Raststätte Schafstrift, Tank- und Rastanlage, Raststätte geschlossen, bis 28.02.2022 ca. 18:00 Uhr Wasserschaden in der Raststätte
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 3 A3
Einzelheiten: Zwischen Anschlussstelle Goldbach und Anschlussstelle Aschaffenburg-Ost, Verkehrsstörung, mittlere Geschwindigkeit 30 km/h
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 3 A3
Einzelheiten: Zwischen Aschaffenburg-Ost und Aschaffenburg-West, Unfall mit mehreren Fahrzeugen, linker Fahrstreifen blockiert, mittlerer Fahrstreifen blockiert, Kräfte zur Störungsbeseitigung sind vor Ort, langsam fahren
Sorte: stau
Land: Deutschland
Straße: 5 A5
Einzelheiten: Zwischen Kronau und Kreuz Walldorf, 3 km Stau
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 8 A8
Einzelheiten: Zwischen Dreieck Friedrichsthal und Elversberg, linker Fahrstreifen gesperrt, bis 05.02.2022 Mitternacht
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 8 A8
Einzelheiten: Zwischen Grenzübergang Bad Reichenhall und Anschlussstelle Bad Reichenhall, Verkehrsstörung, mittlere Geschwindigkeit 30 km/h
Sorte: stau
Land: Deutschland
Straße: 8 A8
Einzelheiten: Zwischen Anschlussstelle Irschenberg und Anschlussstelle Weyarn, 6 km Stau, Verkehrsstörung, mittlere Geschwindigkeit 10 km/h, mindestens 33 Minuten Zeitverlust
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 9 A9
Einzelheiten: Zwischen Rudolphstein und Bad Lobenstein, defekter LKW auf dem Standstreifen, bitte vorsichtig fahren
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 24 A24
Einzelheiten: Zwischen Neuruppin und Neuruppin-Süd, 1 defekter PKW auf dem Standstreifen, Gefahr durch 2 Personen auf dem Standstreifen
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 43 A43
Überschrift: Recklinghausen - Wuppertal
Einzelheiten: In beiden Richtungen, zwischen Kreuz Recklinghausen und Kreuz Herne, gesperrt für LKW über 3.5 t
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 44 A44
Einzelheiten: Ausfahrt Heiligenhaus, Unfall, Ausfahrt gesperrt
Sorte: stau
Land: Deutschland
Straße: 45 A45
Einzelheiten: Zwischen Anschlussstelle Hagen-Süd und Anschlussstelle Lüdenscheid-Nord, 1 km Stau, Verkehrsstörung, mittlere Geschwindigkeit 10 km/h, mindestens 5 Minuten Zeitverlust
Sorte: verkehrsmeldung
Land: Deutschland
Straße: 73 A73
Einzelheiten: Einfahrt Buttenheim, Unfall im Kurvenbereich
Sorte: stau
Land: Deutschland
Straße: 95 A95
Einzelheiten: Zwischen Anschlussstelle München-Fürstenried und Anschlussstelle München-Sendling-Süd, 3 km stockender Verkehr, Verkehrsstörung, mittlere Geschwindigkeit 30 km/h, mindestens 3 Minuten Zeitverlust
Sorte: verkehrsmeldung
Straße: B1
Überschrift: Potsdam Glienicker Brücke
Einzelheiten: Gefahr durch 1 Person auf der Fahrbahn, fahren Sie bitte besonders vorsichtig
Sorte: stau
Straße: B2
Einzelheiten: Tunnel Farchant, Stau, Blockabfertigung
Sorte: verkehrsmeldung
Straße: B2
Einzelheiten: Zwischen Garmisch-Partenkirchen Nord und Übergang Anschluss A95, dichter Verkehr, mindestens 11 Minuten Zeitverlust
Sorte: vollsperrung
Straße: B2
Überschrift: B5 Berlin, Straße des 17. Juni
Einzelheiten: Zwischen Kreisverkehr Großer Stern und Scheidemannstraße gesperrt, Veranstaltung, bis 02.01.2022 23:59 Uhr
Sorte: verkehrsmeldung
Straße: B2
Überschrift: B2, Ebertstraße
Einzelheiten: Zwischen Einmündung Dorotheenstraße und Friedrichstraße, dichter Verkehr, mindestens 6 Minuten Zeitverlust
Sorte: vollsperrung
Straße: B2
Überschrift: Berlin, Greifswalder Straße
Einzelheiten: Zwischen Storkower Straße und Thomas-Mann-Straße gesperrt, Wasserrohrbruch
Sorte: verkehrsmeldung
Straße: B5
Überschrift: B5, Barmbeker Straße
Einzelheiten: Zwischen Kreuzung Borgweg und Weidestraße, dichter Verkehr, mindestens 3 Minuten Zeitverlust
Sorte: vollsperrung
Straße: B5
Überschrift: Straße des 17. Juni zwischen Yitzhak-Rabin-Straße und Scheidemannstraße in beiden Richtungen
Einzelheiten: Gesperrt, Veranstaltung, bis 02.01.2022 23:59 Uhr
Sorte: verkehrsmeldung
Straße: B9
Einzelheiten: Ausfahrt Waldsee, Gefahr durch Gegenstände auf der Fahrbahn (ein PKW-Rad)
Sorte: verkehrsmeldung
Straße: B9
Einzelheiten: Ausfahrt Limburgerhof-Neuhofen, Gefahr durch Gegenstände auf der Fahrbahn (ein PKW-Rad)
Sorte: verkehrsmeldung
Straße: B10
Überschrift: B10, Uferstraße
Einzelheiten: Zwischen Anschlussstelle Stuttgart-Ost und Rosensteinbrücke, dichter Verkehr, mindestens 10 Minuten Zeitverlust
Sorte: verkehrsmeldung
Straße: B19
Einzelheiten: Zwischen Fischen und Anschlussstelle Sonthofen, dichter Verkehr, mindestens 8 Minuten Zeitverlust
Sorte: verkehrsmeldung
Straße: B23
Einzelheiten: Zwischen Grainau und Garmisch-Partenkirchen, dichter Verkehr, mindestens 7 Minuten Zeitverlust
Sorte: verkehrsmeldung
Straße: B42
Einzelheiten: Zwischen Königswinter und Anschlussstelle Oberdollendorf, dichter Verkehr, mindestens 4 Minuten Zeitverlust
Sorte: verkehrsmeldung
Straße: B59
Überschrift: B59, Venloer Straße
Einzelheiten: Zwischen Kreuzung Innere Kanalstraße und Äußere Kanalstraße, dichter Verkehr, mindestens 5 Minuten Zeitverlust
Sorte: verkehrsmeldung
Straße: B73
Überschrift: B73, Buxtehuder Straße
Einzelheiten: Zwischen Kreuzung Schloßmühlendamm und Moorburger Straße, dichter Verkehr, mindestens 9 Minuten Zeitverlust
Sorte: verkehrsmeldung
Straße: B246
Einzelheiten: Zwischen Schönhagen und Berliner Straße, dichter Verkehr, mindestens 3 Minuten Zeitverlust
Sorte: verkehrsmeldung
Straße: B292
Überschrift: Sinsheim - Mosbach
Einzelheiten: Zwischen Bahnhof Finkenhof und B27, B37, Mosbacher Kreuz/Abzweig B37, in beiden Richtungen, die Geschwindigkeit ist begrenzt, zulässige Höchstgeschwindigkeit von 30 km/h, vorübergehende Begrenzung der Fahrbahnbreite auf 3,50 m, Straßenschäden auf Brücken, bis 03.01.2022 Mitternacht
Sorte: verkehrsmeldung
Straße: B96a
Überschrift: Berlin Elsenbrücke
Einzelheiten: In beiden Richtungen, Fahrbahn auf einen Fahrstreifen verengt, Staugefahr Brückenschäden
Sorte: vollsperrung
Straße:
Überschrift: Berlin
Einzelheiten: Conrad-Blenkle-Straße zwischen Kniprodestraße und Erich-Boltze-Straße in beiden Richtungen, gesperrt, Wasserrohrbruch, bis 31.03.2022
Sorte: vollsperrung
Straße:
Überschrift: Stadtgebiet Hamburg
Einzelheiten: Grelckstraße, von Stapelstraße zur Rütersbarg, Richtungsfahrbahn gesperrt, Einbahnstraßenregelung Richtung Stapelstraße, bis 22.04.2022 18:00 Uhr, Mo-Fr zwischen 06:00 Uhr und 18:00 Uhr
Sorte: vollsperrung
Straße:
Überschrift: Stadtgebiet Duisburg
Einzelheiten: Schifferstraße, von Max-Peters-Straße zur Am Innenhof, Richtungsfahrbahn gesperrt, gesperrt für LKW über 7.5 t, Einbahnstraßenregelung Richtung Max-Peters-Straße
Sorte: vollsperrung
Straße:
Überschrift: Stadtgebiet Duisburg
Einzelheiten: An der Cölve, in beiden Richtungen, zwischen Altenbruchstraße und Güterstraße, Brücke gesperrt, eine Umleitung ist eingerichtet, bis auf weiteres
Sorte: verkehrsmeldung
Straße: S2073
Einzelheiten: Zwischen Kleinpienzenau und Weyarn, dichter Verkehr, mindestens 10 Minuten Zeitverlust
Sorte: verkehrsmeldung
Straße: L52
Überschrift: Mecumstraße
Einzelheiten: Zwischen Kreuzung Auf m Hennekamp und Herzogstraße, dichter Verkehr, mindestens 3 Minuten Zeitverlust
Sorte: verkehrsmeldung
Straße: GN2
Überschrift: Würzburger Straße und Fürth-Fürberg
Einzelheiten: In beiden Richtungen, Achtung, Ihnen kommt ein Falschfahrer entgegen, nicht überholen, Vorsicht auf beiden Richtungsfahrbahnen
Sorte: vollsperrung
Straße: GF4
Überschrift: Sachsenhäuserufer bis Schaumainkai
Einzelheiten: Zwischen Alte Brücke und Untermainbrücke gesperrt, bis 03.01.2022 08:00 Uhr Sachsenhausen, Sperrung Sachsenhäuser Ufer und Schaumainkai (südliches Mainufer), Sachsenhäuser Ufer und Schaumainkai zwischen Alter Brücke und Untermainbrücke werden für den Jahreswechsel gesperrt. Verkehrsteilnehmer werden gebeten den genannten Bereich weiträumig zu umfahren. Vom 30.12.2021 bis 03.01.2022 ca. 07:00 Uhr.
Sorte: vollsperrung
Straße: L1070
Überschrift: Ebertstraße zwischen Hannah-Arendt-Straße und Scheidemannstraße in beiden Richtungen
Einzelheiten: Gesperrt, Veranstaltung, bis 02.01.2022 23:59 Uhr
Sorte: verkehrsmeldung
Straße:
Überschrift: Höchst Mainfähre Höchst
Einzelheiten: In beiden Richtungen, Personenfähre außer Betrieb, bis 19.01.2022 Mitternacht
Sorte: verkehrsmeldung
Straße:
Überschrift: Fähre „Arneburg“ bei Arneburg
Einzelheiten: In beiden Richtungen, über die Elbe, Einstellung des Fährbetriebs -Ende der Saison 2021, von 01.12.2021 bis 01.03.2022
Sorte: verkehrsmeldung
Straße: L111
Überschrift: Deutzer Brücke
Einzelheiten: In beiden Richtungen, gesperrt für LKW über 3.5 t, bis auf weiteres
Sorte: verkehrsmeldung
Straße: L1075
Überschrift: Rummelsburger Straße bis Minna-Todenhagen-Straße zwischen Rummelsburger Straße und Minna-Todenhagen-Straße in beiden Richtungen
Einzelheiten: Staugefahr, Verkehrsbehinderung, Baustelle, bis voraussichtlich 31.03.2022 Nur eine Linksabbiegespur von der Rummelsburger Straße auf die Minna-Todenhagen-Straße! Nur eine Rechsabbiegespur von der Minna-Todenhagen-Straße auf die Rummelsburger Straße!
Sorte: vollsperrung
Straße: GHB08
Überschrift: Am Wall
Einzelheiten: Von Kreuzung Ostertorstraße zum Herdentor, Richtungsfahrbahn gesperrt, Einbahnstraßenregelung Richtung Ostertorstraße, bis 31.03.2022
Sorte: vollsperrung
Straße: GF96
Überschrift: Untermainkai bis Mainkai
Einzelheiten: Zwischen Neue Mainzer Straße und Schöne Aussicht gesperrt, bis 03.01.2022 08:00 Uhr Innenstadt, Sperrung Mainkai und Untermainkai (nördliches Mainufer), Mainkai und Untermainkai zwischen Alter Brücke und Untermainbrücke werden für den Jahreswechsel gesperrt. Verkehrsteilnehmer werden gebeten den genannten Bereich weiträumig zu umfahren. Vom 30.12.2021 bis 03.01.2022 ca. 07:00 Uhr.