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.