diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8f9a8d7c..1b297185 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,30 +1,12 @@ repos: - - repo: https://github.com/asottile/pyupgrade - rev: v2.31.1 - hooks: - - id: pyupgrade - args: - - --py311-plus - - repo: https://github.com/charliermarsh/ruff-pre-commit - # Ruff version. - rev: "v0.0.275" + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: 'v0.5.6' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - - repo: local - hooks: - - id: isort - name: isort - language: system - entry: isort - types: [python] - - id: black - name: black - language: system - entry: black - types: [python] + - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.5.0 hooks: - id: trailing-whitespace exclude: ^src/api/client.js$ diff --git a/catalog-info.yaml b/catalog-info.yaml index fae90d86..3ffeff90 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -3,7 +3,7 @@ apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: nowplaying - title: Nowplaging Service + title: Nowplaying Service description: | The nowplaying daemon is the python server central to our songticker. annotations: diff --git a/conftest.py b/conftest.py deleted file mode 100644 index 9e4f6f16..00000000 --- a/conftest.py +++ /dev/null @@ -1,63 +0,0 @@ -import os -import sys - -import pytest - -from nowplaying.show.show import Show -from nowplaying.track.observers.base import TrackObserver -from nowplaying.track.track import Track - -PACKAGE_PARENT = "nowplaying" -SCRIPT_DIR = os.path.dirname( - os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__))) -) -sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT))) - - -def new_show(name="Hairmare Traveling Medicine Show"): - s = Show() - s.set_name("Hairmare Traveling Medicine Show") - return s - - -@pytest.fixture() -def show_factory(): - """Return a method to help creating new show objects for tests.""" - return new_show - - -def new_track( - artist="Hairmare and the Band", - title="An Ode to legacy Python Code", - album="Live at the Refactoring Club", - duration=128, -): - t = Track() - t.set_artist(artist) - t.set_title(title) - t.set_album(album) - t.set_duration(duration) - return t - - -@pytest.fixture() -def track_factory(): - """Return a method to help creating new track objects for tests.""" - return new_track - - -class DummyObserver(TrackObserver): - """Shunt class for testing the abstract TrackObserver.""" - - pass - - def track_started(self, track): - pass - - def track_finished(self, track): - pass - - -@pytest.fixture() -def dummy_observer(): - return DummyObserver() diff --git a/docs/gen_ref_pages.py b/docs/gen_ref_pages.py index b1b8882a..d247a4ee 100644 --- a/docs/gen_ref_pages.py +++ b/docs/gen_ref_pages.py @@ -16,20 +16,20 @@ parts = list(module_path.parts) - if parts[-1] == "__init__": - continue - elif parts[-1] == "__main__": + if parts[-1] in ["__init__", "__main__"]: continue with mkdocs_gen_files.open(full_doc_path, "w") as fd: identifier = ".".join(parts) print("::: " + identifier, file=fd) - mkdocs_gen_files.set_edit_path(full_doc_path, path) # + mkdocs_gen_files.set_edit_path(full_doc_path, path) with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file: nav_file.writelines(nav.build_literate_nav()) -readme = Path("README.md").open("r") -with mkdocs_gen_files.open("index.md", "w") as index_file: +with Path("README.md").open("r") as readme, mkdocs_gen_files.open( + "index.md", + "w", +) as index_file: index_file.writelines(readme.read()) diff --git a/nowplaying/__main__.py b/nowplaying/__main__.py index e1062634..d77152ab 100644 --- a/nowplaying/__main__.py +++ b/nowplaying/__main__.py @@ -9,7 +9,8 @@ from .main import NowPlaying -def main(): +def main() -> None: + """Run nowplaying.""" NowPlaying().run() diff --git a/nowplaying/api.py b/nowplaying/api.py index 58c85d9f..35735cb0 100644 --- a/nowplaying/api.py +++ b/nowplaying/api.py @@ -1,8 +1,12 @@ +"""Nowplaying ApiServer.""" + +from __future__ import annotations + import json import logging -from queue import Queue +from typing import TYPE_CHECKING, Iterable, Self -import cherrypy +import cherrypy # type: ignore[import-untyped] import cridlib from cloudevents.exceptions import GenericException as CloudEventException from cloudevents.http import from_http @@ -10,6 +14,13 @@ from werkzeug.routing import Map, Rule from werkzeug.wrappers import Request, Response +if TYPE_CHECKING: # pragma: no cover + from queue import Queue + from wsgiref.types import StartResponse, WSGIEnvironment + + from nowplaying.options import Options + + logger = logging.getLogger(__name__) _RABE_CLOUD_EVENTS_SUBS = ( @@ -25,21 +36,27 @@ class ApiServer: """The API server.""" - def __init__(self, options, event_queue: Queue, realm: str = "nowplaying"): + def __init__( + self: Self, + options: Options, + event_queue: Queue, + realm: str = "nowplaying", + ) -> None: + """Create ApiServer.""" self.options = options self.event_queue = event_queue self.realm = realm self.url_map = Map([Rule("/webhook", endpoint="webhook")]) - def run_server(self): + def run_server(self: Self) -> None: """Run the API server.""" if self.options.debug: from werkzeug.serving import run_simple - self._server = run_simple( - self.options.apiBindAddress, - self.options.apiPort, + run_simple( + self.options.api_bind_address, + self.options.api_port, self, use_debugger=True, use_reloader=True, @@ -48,25 +65,35 @@ def run_server(self): cherrypy.tree.graft(self, "/") cherrypy.server.unsubscribe() - self._server = cherrypy._cpserver.Server() + self._server = cherrypy._cpserver.Server() # noqa: SLF001 - self._server.socket_host = self.options.apiBindAddress - self._server.socket_port = self.options.apiPort + self._server.socket_host = self.options.api_bind_address + self._server.socket_port = self.options.api_port self._server.subscribe() cherrypy.engine.start() cherrypy.engine.block() - def stop_server(self): + def stop_server(self: Self) -> None: """Stop the server.""" self._server.stop() cherrypy.engine.exit() - def __call__(self, environ, start_response): + def __call__( + self: Self, + environ: WSGIEnvironment, + start_response: StartResponse, + ) -> Iterable[bytes]: + """Forward calls to wsgi_app.""" return self.wsgi_app(environ, start_response) - def wsgi_app(self, environ, start_response): + def wsgi_app( + self: Self, + environ: WSGIEnvironment, + start_response: StartResponse, + ) -> Iterable[bytes]: + """Return a wsgi app.""" request = Request(environ) auth = request.authorization if auth and self.check_auth(auth.username, auth.password): @@ -75,13 +102,18 @@ def wsgi_app(self, environ, start_response): response = self.auth_required(request) return response(environ, start_response) - def check_auth(self, username, password): - return ( - username in self.options.apiAuthUsers - and self.options.apiAuthUsers[username] == password + def check_auth(self: Self, username: str | None, password: str | None) -> bool: + """Check if auth is valid.""" + return str( + username, + ) in self.options.api_auth_users and self.options.api_auth_users[ + str(username) + ] == str( + password, ) - def auth_required(self, request): + def auth_required(self: Self, _: Request) -> Response: + """Check if auth is required.""" return Response( "Could not verify your access level for that URL.\n" "You have to login with proper credentials", @@ -89,7 +121,8 @@ def auth_required(self, request): {"WWW-Authenticate": f'Basic realm="{self.realm}"'}, ) - def dispatch_request(self, request): + def dispatch_request(self: Self, request: Request) -> Response | HTTPException: + """Dispatch requests to handlers.""" adapter = self.url_map.bind_to_environ(request.environ) try: endpoint, values = adapter.match() @@ -101,25 +134,25 @@ def dispatch_request(self, request): {"Content-Type": "application/json"}, ) - def on_webhook(self, request): + def on_webhook(self: Self, request: Request) -> Response: """Receive a CloudEvent and put it into the event queue.""" logger.warning("Received a webhook") if ( request.headers.get("Content-Type") not in _RABE_CLOUD_EVENTS_SUPPORTED_MEDIA_TYPES ): - raise UnsupportedMediaType() + raise UnsupportedMediaType try: event = from_http(request.headers, request.data) except CloudEventException as error: - raise BadRequest(description=f"{error}") + raise BadRequest(description=str(error)) from error try: crid = cridlib.parse(event["id"]) logger.debug("Detected CRID: %s", crid) except cridlib.CRIDError as error: raise BadRequest( - description=f"CRID '{event['id']}' is not a RaBe CRID" + description=f"CRID '{event['id']}' is not a RaBe CRID", ) from error logger.info("Received event: %s", event) diff --git a/nowplaying/daemon.py b/nowplaying/daemon.py index 2f17333c..c2283d9d 100644 --- a/nowplaying/daemon.py +++ b/nowplaying/daemon.py @@ -1,3 +1,5 @@ +"""Nowplaying Daemon.""" + import logging import os import signal @@ -5,19 +7,24 @@ import time from queue import Queue from threading import Thread - -from cloudevents.http.event import CloudEvent +from typing import TYPE_CHECKING, Any, Self from .api import ApiServer -from .input import observer as inputObservers +from .input import observer as input_observers from .input.handler import InputHandler from .misc.saemubox import SaemuBox +from .options import Options from .track.handler import TrackEventHandler from .track.observers.dab_audio_companion import DabAudioCompanionTrackObserver from .track.observers.icecast import IcecastTrackObserver from .track.observers.smc_ftp import SmcFtpTrackObserver from .track.observers.ticker import TickerTrackObserver +if TYPE_CHECKING: # pragma: no cover + from cloudevents.http.event import CloudEvent + +_EXCEPTION_NOWPLAYING_MAIN = "Error in main" + logger = logging.getLogger(__name__) @@ -27,16 +34,20 @@ class NowPlayingDaemon: """initialize last_input to a know value.""" last_input = 1 - def __init__(self, options): + def __init__(self: Self, options: Options) -> None: + """Create NowPlayingDaemon.""" self.options = options - self.event_queue = Queue() + self.event_queue: Queue = Queue() self.saemubox = SaemuBox( - self.options.saemubox_ip, self.options.check_saemubox_sender + self.options.saemubox_ip, + self.options.check_saemubox_sender, ) - def main(self): # pragma: no cover - # TODO test once there is not saemubox in the loop + def main(self: Self) -> None: # pragma: no cover + """Run Daemon.""" + # TODO(hairmare): test once there is not saemubox in the loop + # https://github.com/radiorabe/nowplaying/issues/179 logger.info("Starting up now-playing daemon") self.saemubox.run() @@ -44,8 +55,8 @@ def main(self): # pragma: no cover self.register_signal_handlers() input_handler = self.get_input_handler() - except Exception as e: - logger.exception("Error: %s", e) + except Exception: + logger.exception(_EXCEPTION_NOWPLAYING_MAIN) sys.exit(-1) _thread = Thread(target=self._main_loop, args=(input_handler,)) @@ -54,19 +65,18 @@ def main(self): # pragma: no cover self._start_apiserver() # blocking - def _start_apiserver(self): + def _start_apiserver(self: Self) -> None: """Start the API server.""" self._api = ApiServer(self.options, self.event_queue) self._api.run_server() # blocking - def _stop_apiserver(self): + def _stop_apiserver(self: Self) -> None: """Stop the API server.""" logger.info("Stopping API server") self._api.stop_server() - def _main_loop(self, input_handler: InputHandler): # pragma: no cover - """ - Run main loop of the daemon. + def _main_loop(self: Self, input_handler: InputHandler) -> None: # pragma: no cover + """Run main loop of the daemon. Should be run in a thread. """ @@ -76,66 +86,68 @@ def _main_loop(self, input_handler: InputHandler): # pragma: no cover saemubox_id = self.poll_saemubox() while not self.event_queue.empty(): - logger.debug("Queue size: %i" % self.event_queue.qsize()) + logger.debug("Queue size: %i", self.event_queue.qsize()) event: CloudEvent = self.event_queue.get() logger.info( - "Handling update from event: %s, source: %s" - % (event["type"], event["source"]) + "Handling update from event: %s, source: %s", + event["type"], + event["source"], ) input_handler.update(saemubox_id, event) input_handler.update(saemubox_id) - except Exception as e: - logger.exception("Error: %s", e) + except Exception: + logger.exception(_EXCEPTION_NOWPLAYING_MAIN) - time.sleep(self.options.sleepSeconds) + time.sleep(self.options.sleep_seconds) - def register_signal_handlers(self): + def register_signal_handlers(self: Self) -> None: + """Register signal handler.""" logger.debug("Registering signal handler") signal.signal(signal.SIGINT, self.signal_handler) - # signal.signal(signal.SIGKIL, self.signal_handler) - def signal_handler(self, signum, frame): - logger.debug("Signal %i caught" % signum) + def signal_handler(self: Self, signum: int, *_: Any) -> None: # noqa: ANN401 + """Handle signals.""" + logger.debug("Signal %i caught", signum) - if signum == signal.SIGINT or signum == signal.SIGKILL: - logger.info("Signal %i caught, terminating." % signum) + if signum in [signal.SIGINT, signal.SIGKILL]: + logger.info("Signal %i caught, terminating.", signum) self._stop_apiserver() sys.exit(os.EX_OK) - def get_track_handler(self): # pragma: no cover - # TODO test once options have been refactored with v3 + def get_track_handler(self: Self) -> TrackEventHandler: # pragma: no cover + """Get TrackEventHandler.""" + # TODO(hairmare): test once options have been refactored with v3 + # https://github.com/radiorabe/nowplaying/issues/179 handler = TrackEventHandler() - [ + for url in self.options.icecast: handler.register_observer( IcecastTrackObserver( - # TODO v3 remove uername and password - # because we mandate specifying via url + # TODO(hairmare): v3 remove uername and password + # because we mandate specifying via url + # https://github.com/radiorabe/nowplaying/issues/179 options=IcecastTrackObserver.Options( url=url, username="source", - password=self.options.icecastPassword, - ) - ) + password=self.options.icecast_password, + ), + ), ) - for url in self.options.icecast - ] - [ + for url in self.options.dab: handler.register_observer( DabAudioCompanionTrackObserver( options=DabAudioCompanionTrackObserver.Options( - url=url, dl_plus=self.options.dab_send_dls - ) - ) + url=url, + dl_plus=self.options.dab_send_dls, + ), + ), ) - for url in self.options.dab - ] handler.register_observer( TickerTrackObserver( options=TickerTrackObserver.Options( - file_path=self.options.tickerOutputFile - ) - ) + file_path=self.options.ticker_output_file, + ), + ), ) if self.options.dab_smc: handler.register_observer( @@ -144,50 +156,50 @@ def get_track_handler(self): # pragma: no cover hostname=self.options.dab_smc_ftp_hostname, username=self.options.dab_smc_ftp_username, password=self.options.dab_smc_ftp_password, - ) - ) + ), + ), ) return handler - def get_input_handler(self): # pragma: no cover - # TODO test once options have been refactored with v3 + def get_input_handler(self: Self) -> InputHandler: # pragma: no cover + """Get InputHandler.""" + # TODO(hairmare): test once options have been refactored with v3 + # https://github.com/radiorabe/nowplaying/issues/179 handler = InputHandler() track_handler = self.get_track_handler() - klangbecken = inputObservers.KlangbeckenInputObserver( - self.options.currentShowUrl, self.options.inputFile + klangbecken = input_observers.KlangbeckenInputObserver( + self.options.current_show_url, + self.options.input_file, ) klangbecken.add_track_handler(track_handler) handler.register_observer(klangbecken) - nonklangbecken = inputObservers.NonKlangbeckenInputObserver( - self.options.currentShowUrl + nonklangbecken = input_observers.NonKlangbeckenInputObserver( + self.options.current_show_url, ) nonklangbecken.add_track_handler(track_handler) handler.register_observer(nonklangbecken) return handler - def poll_saemubox(self) -> int: # pragma: no cover - """ - Poll Saemubox for new data. + def poll_saemubox(self: Self) -> int: # pragma: no cover + """Poll Saemubox for new data. Should be run once per main loop. - TODO v3 remove once replaced with pathfinder + TODO(hairmare) v3 remove once replaced with pathfinder + https://github.com/radiorabe/nowplaying/issues/179 """ - saemubox_id = self.saemubox.get_active_output_id() - logger.debug("Sämubox id: %i" % saemubox_id) + logger.debug("Sämubox id: %i", saemubox_id) if self.last_input != saemubox_id: logger.info( - 'Sämubox changed from "%s" to "%s"' - % ( - self.saemubox.get_id_as_name(self.last_input), - self.saemubox.get_id_as_name(saemubox_id), - ) + 'Sämubox changed from "%s" to "%s"', + self.saemubox.get_id_as_name(self.last_input), + self.saemubox.get_id_as_name(saemubox_id), ) self.last_input = saemubox_id diff --git a/nowplaying/input/handler.py b/nowplaying/input/handler.py index abf51579..fdd3c4ff 100644 --- a/nowplaying/input/handler.py +++ b/nowplaying/input/handler.py @@ -1,12 +1,20 @@ +"""Observe all input.""" + +from __future__ import annotations + import logging import logging.handlers +from typing import TYPE_CHECKING, Self -from cloudevents.http.event import CloudEvent +if TYPE_CHECKING: # pragma: no cover + from cloudevents.http.event import CloudEvent -from .observer import InputObserver + from nowplaying.input.observer import InputObserver logger = logging.getLogger(__name__) +_EXCEPTION_INPUT_UPDATE_FAIL = "Failed to update observer." + class InputHandler: """Inform all registered input-event observers about an input status. @@ -14,23 +22,27 @@ class InputHandler: This is the subject of the classical observer pattern. """ - def __init__(self): + def __init__(self: Self) -> None: + """Create InputHandler.""" self._observers: list[InputObserver] = [] - def register_observer(self, observer: InputObserver): - logger.info("Registering InputObserver '%s'" % observer.__class__.__name__) + def register_observer(self: Self, observer: InputObserver) -> None: + """Register an observer.""" + logger.info("Registering InputObserver '%s'", observer.__class__.__name__) self._observers.append(observer) - def remove_observer(self, observer: InputObserver): + def remove_observer(self: Self, observer: InputObserver) -> None: + """Remove an observer.""" self._observers.remove(observer) - def update(self, saemubox_id: int, event: CloudEvent = None): + def update(self: Self, saemubox_id: int, event: CloudEvent | None = None) -> None: + """Update all observers.""" for observer in self._observers: - logger.debug("Sending update event to observer %s" % observer.__class__) + logger.debug("Sending update event to observer %s", observer.__class__) try: observer.update(saemubox_id, event) - except Exception as e: # pragma: no cover - # TODO test once replaced with non generic exception - logger.error(f"InputObserver ({observer.__class__}): {e}") - logger.exception(e) + except Exception: # pragma: no cover + # TODO(hairmare): test once replaced with non generic exception + # https://github.com/radiorabe/nowplaying/issues/180 + logger.exception(_EXCEPTION_INPUT_UPDATE_FAIL) diff --git a/nowplaying/input/observer.py b/nowplaying/input/observer.py index 881eafe8..6eaf988a 100644 --- a/nowplaying/input/observer.py +++ b/nowplaying/input/observer.py @@ -1,22 +1,33 @@ +"""Nowplaying input observer.""" + +from __future__ import annotations + import logging import logging.handlers -import os import time import warnings import xml.dom.minidom from abc import ABC, abstractmethod +from pathlib import Path +from typing import TYPE_CHECKING, Self -import isodate +import isodate # type: ignore[import-untyped] import pytz -from cloudevents.http.event import CloudEvent -from ..show import client -from ..show.show import Show -from ..track.handler import TrackEventHandler -from ..track.track import DEFAULT_ARTIST, DEFAULT_TITLE, Track +from nowplaying.show import client +from nowplaying.show.show import Show +from nowplaying.track.track import DEFAULT_ARTIST, DEFAULT_TITLE, Track + +if TYPE_CHECKING: # pragma: no cover + from cloudevents.http.event import CloudEvent + + from nowplaying.track.handler import TrackEventHandler logger = logging.getLogger(__name__) +_EXCEPTION_INPUT_MISSING_SONG_TAG = "No tag found" +_EXCEPTION_INPUT_MISSING_TIMESTAMP = "Song timestamp attribute is missing" + class InputObserver(ABC): """Abstract base for all InputObservers.""" @@ -24,7 +35,8 @@ class InputObserver(ABC): _SHOW_NAME_KLANGBECKEN = "Klangbecken" _SHOW_URL_KLANGBECKEN = "http://www.rabe.ch/sendungen/musik/klangbecken.html" - def __init__(self, current_show_url: str): + def __init__(self: Self, current_show_url: str) -> None: + """Create InputObserver.""" self.show: Show self.track_handler: TrackEventHandler self.previous_saemubox_id: int = -1 @@ -36,95 +48,129 @@ def __init__(self, current_show_url: str): self.showclient = client.ShowClient(current_show_url) self.show = self.showclient.get_show_info() - def add_track_handler(self, track_handler: TrackEventHandler): + def add_track_handler(self: Self, track_handler: TrackEventHandler) -> None: + """Add Track handler.""" self.track_handler = track_handler - def update(self, saemubox_id: int, event: CloudEvent = None): - # TODO v3-prep refactor to use :meth:`handles` instead of :meth:`handle_id` + def update(self: Self, saemubox_id: int, event: CloudEvent | None = None) -> None: + """Handle update.""" + # TODO(hairmare): v3-prep refactor to use :meth:`handles` + # instead of :meth:`handle_id` + # https://github.com/radiorabe/nowplaying/issues/180 if self.handle_id(saemubox_id, event): self.handle(event) @abstractmethod - # TODO v3 remove this method + # TODO(hairmare): v3 remove this method + # https://github.com/radiorabe/nowplaying/issues/179 def handle_id( - self, saemubox_id: int, event: CloudEvent = None - ): # pragma: no coverage - pass + self: Self, + saemubox_id: int, + event: CloudEvent | None = None, + ) -> bool: # pragma: no coverage + """Handle ID.""" @abstractmethod - # TODO v3 remove this method - def handle(self, event: CloudEvent = None): # pragma: no coverage - pass + # TODO(hairmare): v3 remove this method + # https://github.com/radiorabe/nowplaying/issues/179 + def handle( + self: Self, + event: CloudEvent | None = None, + ) -> None: # pragma: no coverage + """Handle event.""" @abstractmethod - def handles(self, event: CloudEvent) -> bool: # pragma: no coverage - pass + def handles(self: Self, event: CloudEvent) -> bool: # pragma: no coverage + """Handle event.""" @abstractmethod - def event(self, event: CloudEvent): # pragma: no coverage - pass + def event(self: Self, event: CloudEvent) -> None: # pragma: no coverage + """Handle event.""" class KlangbeckenInputObserver(InputObserver): """Observe when Sämu Box says Klangbecken we have now-playing.xml input.""" def __init__( - self, current_show_url: str, input_file: str = None - ): # pragma: no coverage - # TODO test once input file is replaced with api + self: Self, + current_show_url: str, + input_file: str | None = None, + ) -> None: # pragma: no coverage + """Create KlangbeckenInputObserver.""" + # TODO(hairmare): test once input file is replaced with api + # https://github.com/radiorabe/nowplaying/issues/180 if input_file: warnings.warn( "The now-playing.xml format from Loopy/Klangbecken " "will be replaced in the future", PendingDeprecationWarning, + stacklevel=2, ) self.input_file = input_file - self.last_modify_time = os.stat(self.input_file).st_mtime + self.last_modify_time = Path(self.input_file).stat().st_mtime self.track: Track super().__init__(current_show_url) - def handles(self, event: CloudEvent) -> bool: - # TODO v3-prep call :meth:`handle_id` from here - # needs saemubox_id compat workaround - # TODO v3 remove call to :meth:`handle_id` - # TODO make magic string configurable - # TODO check if source is currently on-air + def handles(self: Self, event: CloudEvent | None) -> bool: + """Check if we need to handle the event.""" + # TODO(hairmare): v3-prep call :meth:`handle_id` from here + # needs saemubox_id compat workaround + # https://github.com/radiorabe/nowplaying/issues/180 + # TODO(hairmare): v3 remove call to :meth:`handle_id` + # https://github.com/radiorabe/nowplaying/issues/179 + # TODO(hairmare): make magic string configurable + # https://github.com/radiorabe/nowplaying/issues/179 + # TODO(hairmare): check if source is currently on-air + # https://github.com/radiorabe/nowplaying/issues/179 if not event: # pragma: no coverage - # TODO remove checking for None once only events exist + # TODO(hairmare): remove checking for None once only events exist + # https://github.com/radiorabe/nowplaying/issues/179 return False return event["source"] == "https://github/radiorabe/klangbecken" - def event(self, event: CloudEvent): + def event(self: Self, event: CloudEvent) -> None: + """Handle event.""" self._handle(event) - def handle_id(self, saemubox_id: int, event: CloudEvent = None): + def handle_id( + self: Self, + saemubox_id: int, + event: CloudEvent | None = None, + ) -> bool: + """Handle ID.""" # only handle Klangbecken output if saemubox_id == 1: return True - # TODO v3-prep make this get called from :meth:`handles` + # TODO(hairmare): v3-prep make this get called from :meth:`handles` + # https://github.com/radiorabe/nowplaying/issues/180 return self.handles(event) - def handle(self, event: CloudEvent = None): + def handle(self: Self, event: CloudEvent | None = None) -> None: + """Handle RaBe CloudEevent.""" self._handle(event) - def _handle(self, event: CloudEvent = None): + def _handle(self: Self, event: CloudEvent | None = None) -> None: """Handle actual RaBe CloudEevent. - TODO v3: move into :meth:`event` - once :meth:`handle` and :meth:`handle_id` have been yeeted - TODO v3: remove all refs to input_file and it's modify time - once we use event handlers + TODO(hairmare): v3: move into :meth:`event` + once :meth:`handle` and :meth:`handle_id` have been yeeted + https://github.com/radiorabe/nowplaying/issues/179 + TODO(hairmare): v3: remove all refs to input_file and it's modify time + once we use event handlers + https://github.com/radiorabe/nowplaying/issues/179 """ if not event: - # @TODO: replace the stat method with inotify - modify_time = os.stat(self.input_file).st_mtime + # @TODO(hairmare): replace the stat method with inotify + # https://github.com/radiorabe/nowplaying/issues/179 + modify_time = Path(self.input_file).stat().st_mtime - # @TODO: Need to check if we have a stale file and send default - # track infos in this case. This might happend if loopy - # went out for lunch... - # pseudo code: now > modify_time + self.track.get_duration() + # @TODO(hairmare): Need to check if we have a stale file and send default + # track infos in this case. This might happend if loopy + # went out for lunch... + # pseudo code: now > modify_time + self.track.get_duration() + # https://github.com/radiorabe/nowplaying/issues/179 if self.first_run or event or modify_time > self.last_modify_time: logger.info("Now playing file changed") @@ -137,16 +183,18 @@ def _handle(self, event: CloudEvent = None): self.track = self.parse_event(event) self.first_run = False - logger.info("First run: %s" % self.first_run) + logger.info("First run: %s", self.first_run) if not self.first_run: # pragma: no coverage - # TODO test once we don't have to care about - # mtime/inotify because it's an api + # TODO(hairmare): test once we don't have to care about + # mtime/inotify because it's an api + # https://github.com/radiorabe/nowplaying/issues/179 logger.info("calling track_finished") self.track_handler.track_finished(self.track) if not event: - # TODO remove once legacy xml is gone + # TODO(hairmare): remove once legacy xml is gone + # https://github.com/radiorabe/nowplaying/issues/179 self.track = self.get_track_info() # Klangbecken acts as a failover and last resort input, if other @@ -155,8 +203,9 @@ def _handle(self, event: CloudEvent = None): # of what loopy thinks. if self.show.name != self._SHOW_NAME_KLANGBECKEN: logger.info( - "Klangbecken Input active, overriding current show '%s' with '%s'" - % (self.show.name, self._SHOW_NAME_KLANGBECKEN) + "Klangbecken Input active, overriding current show '%s' with '%s'", + self.show.name, + self._SHOW_NAME_KLANGBECKEN, ) self.show = Show() @@ -170,14 +219,17 @@ def _handle(self, event: CloudEvent = None): self.track.set_show(self.show) - # TODO: or finished? + # TODO(hairmare): or finished? + # https://github.com/radiorabe/nowplaying/issues/179 self.track_handler.track_started(self.track) self.first_run = False - def get_track_info(self): - # TODO v3 remove method once legacy xml is gone - dom = xml.dom.minidom.parse(self.input_file) + def get_track_info(self: Self) -> Track: + """Get Track info.""" + # TODO(hairmare): v3 remove method once legacy xml is gone + # https://github.com/radiorabe/nowplaying/issues/179 + dom = xml.dom.minidom.parse(self.input_file) # noqa: S318 # default track info track_info = { @@ -188,38 +240,41 @@ def get_track_info(self): "time": "", } - song = dom.getElementsByTagName("song") + songs = dom.getElementsByTagName("song") - if len(song) == 0 or song[0].hasChildNodes() is False: # pragma: no coverage - # TODO replace with non generic exception and test - raise Exception("No tag found") + if len(songs) == 0 or songs[0].hasChildNodes() is False: # pragma: no coverage + # TODO(hairmare): replace with non generic exception and test + # https://github.com/radiorabe/nowplaying/issues/179 + raise Exception(_EXCEPTION_INPUT_MISSING_SONG_TAG) # noqa: TRY002 - song = song[0] + song = songs[0] for name in list(track_info.keys()): elements = song.getElementsByTagName(name) if len(elements) == 0: # pragma: no coverage - # TODO replace with non generic exception and test - raise Exception("No <%s> tag found" % name) - elif elements[0].hasChildNodes(): - element_data = elements[0].firstChild.data.strip() + # TODO(hairmare): replace with non generic exception and test + # https://github.com/radiorabe/nowplaying/issues/179 + raise Exception("No <%s> tag found" % name) # noqa: TRY002, UP031 + if elements[0].hasChildNodes(): + element_data = elements[0].firstChild.data.strip() # type: ignore[attr-defined,union-attr] if element_data != "": track_info[name] = element_data else: # pragma: no coverage - logger.info("Element %s has empty value, ignoring" % name) + logger.info("Element %s has empty value, ignoring", name) if not song.hasAttribute("timestamp"): # pragma: no coverage - # TODO replace with non generic exception and test - raise Exception("Song timestamp attribute is missing") + # TODO(hairmare): replace with non generic exception and test + # https://github.com/radiorabe/nowplaying/issues/179 + raise Exception(_EXCEPTION_INPUT_MISSING_TIMESTAMP) # noqa: TRY002 # set the start time and append the missing UTC offset # @TODO: The UTC offset should be provided by the now playing XML # generated by Thomas # ex.: 2012-05-15T09:47:07+02:00 track_info["start_timestamp"] = song.getAttribute("timestamp") + time.strftime( - "%z" + "%z", ) current_track = Track() @@ -231,17 +286,18 @@ def get_track_info(self): # Store as UTC datetime object current_track.set_starttime( isodate.parse_datetime(track_info["start_timestamp"]).astimezone( - pytz.timezone("UTC") - ) + pytz.timezone("UTC"), + ), ) - current_track.set_duration(track_info["time"]) + current_track.set_duration(int(track_info["time"])) return current_track - def parse_event(self, event: CloudEvent) -> Track: + def parse_event(self: Self, event: CloudEvent) -> Track: + """Parse event.""" track = Track() - logger.info("Parsing event: %s" % event) + logger.info("Parsing event: %s", event) track.set_artist(event.data["item.artist"]) track.set_title(event.data["item.title"]) @@ -250,13 +306,12 @@ def parse_event(self, event: CloudEvent) -> Track: if event["type"] == "ch.rabe.api.events.track.v1.trackStarted": track.set_starttime(event_time) elif event["type"] == "ch.rabe.api.events.track.v1.trackFinished": - # TODO consider using now() instead of event['time'] track.set_endtime(event_time) if "item.length" in event.data: track.set_duration(event.data["item.length"]) - logger.info("Track: %s" % track) + logger.info("Track: %s", track) return track @@ -266,7 +321,7 @@ class NonKlangbeckenInputObserver(InputObserver): Uses the show's name instead of the actual track infos """ - def handles(self, event: CloudEvent) -> bool: # pragma: no coverage + def handles(self: Self, _: CloudEvent) -> bool: # pragma: no coverage """Do not handle events yet. TODO implement this method @@ -276,29 +331,27 @@ def handles(self, event: CloudEvent) -> bool: # pragma: no coverage """ return False - def event(self, event: CloudEvent): # pragma: no coverage + def event(self: Self, event: CloudEvent) -> None: # pragma: no coverage """Do not handle events yet. TODO implement this method """ - super().event(event) - def handle_id(self, saemubox_id: int, event: CloudEvent = None): + def handle_id(self: Self, saemubox_id: int, _: CloudEvent | None = None) -> bool: + """Handle new ID from Saemubox.""" if saemubox_id != self.previous_saemubox_id: # If sämubox changes, force a show update, this acts as # a self-healing measurement in case the show web service provides # nonsense ;) - self.show = self.showclient.get_show_info(True) + self.show = self.showclient.get_show_info(force_update=True) self.previous_saemubox_id = saemubox_id # only handle non-Klangbecken - if saemubox_id != 1: - return True - - return False + return saemubox_id != 1 - def handle(self, event: CloudEvent = None): + def handle(self: Self, _: CloudEvent | None = None) -> None: + """Handle Track.""" self.show = self.showclient.get_show_info() # only handle if a new show has started @@ -307,7 +360,8 @@ def handle(self, event: CloudEvent = None): self.track_handler.track_started(self.get_track_info()) self.previous_show_uuid = self.show.uuid - def get_track_info(self): + def get_track_info(self: Self) -> Track: + """Get Track info.""" current_track = Track() current_track.set_artist(DEFAULT_ARTIST) diff --git a/nowplaying/main.py b/nowplaying/main.py index 821758c9..b211b8d3 100644 --- a/nowplaying/main.py +++ b/nowplaying/main.py @@ -1,4 +1,7 @@ +"""Nowplaying entrypoint.""" + import socket +from typing import Self from .daemon import NowPlayingDaemon from .options import Options @@ -6,20 +9,22 @@ class NowPlaying: - def run(self): + """Nowplaying main class.""" + + def run(self: Self) -> None: """Load configuration, initialize environment and start nowplaying daemon.""" self.options = Options() self.options.parse_known_args() self._setup_otel() - socket.setdefaulttimeout(self.options.socketDefaultTimeout) + socket.setdefaulttimeout(self.options.socket_default_timeout) self._run_daemon() - def _setup_otel(self): # pragma: no cover + def _setup_otel(self: Self) -> None: # pragma: no cover if not self.options.debug: - setup_otel(self.options.otlp_enable) + setup_otel(otlp_enable=self.options.otlp_enable) - def _run_daemon(self): + def _run_daemon(self: Self) -> None: """Start nowplaying daemon.""" NowPlayingDaemon(self.options).main() diff --git a/nowplaying/misc/saemubox.py b/nowplaying/misc/saemubox.py index 61a481e6..fc500ebd 100644 --- a/nowplaying/misc/saemubox.py +++ b/nowplaying/misc/saemubox.py @@ -20,8 +20,6 @@ class SaemuBoxError(Exception): """SaemuBox related exception.""" - pass - class SaemuBox: """Receive and validate info from Sämu Box for nowplaying.""" @@ -37,7 +35,8 @@ class SaemuBox: def __init__(self, saemubox_ip, check_sender=True): warnings.warn( - "Saemubox will be replaced with Pathfinder", PendingDeprecationWarning + "Saemubox will be replaced with Pathfinder", + PendingDeprecationWarning, ) self.output = "" @@ -70,11 +69,11 @@ def _setup_socket(self): # pragma: no cover except OSError as e: # pragma: no cover self.sock = None logger.error("SaemuBox: cannot bind to %s:%i." % (self.bind_ip, self.port)) - raise SaemuBoxError() from e + raise SaemuBoxError from e def __update(self): # pragma: no cover if self.sock is None or (hasattr(self.sock, "_closed") and self.sock._closed): - logger.warn("SaemuBox: socket closed unexpectedly, retrying...") + logger.warning("SaemuBox: socket closed unexpectedly, retrying...") self._setup_socket() output = None @@ -84,7 +83,9 @@ def __update(self): # pragma: no cover while select.select([self.sock], [], [], 0)[0]: data, addr = self.sock.recvfrom(1024) if self.check_sender and addr[0] not in self.senders: - logger.warn("SaemuBox: receiving data from invalid host: %s " % addr[0]) + logger.warning( + "SaemuBox: receiving data from invalid host: %s " % addr[0], + ) continue ids = data.split() # several saemubox ids might come in one packet @@ -94,7 +95,7 @@ def __update(self): # pragma: no cover seen_senders.add(addr[0]) output = id else: - logger.warn("SaemuBox: received invalid data: %s" % data) + logger.warning("SaemuBox: received invalid data: %s" % data) if output is None: logger.error("SaemuBox: could not read current status.") @@ -102,7 +103,7 @@ def __update(self): # pragma: no cover raise SaemuBoxError("Cannot read data from SaemuBox") elif seen_senders != self.senders: for missing_sender in self.senders - seen_senders: - logger.warn("SaemuBox: missing sender: %s" % missing_sender) + logger.warning("SaemuBox: missing sender: %s" % missing_sender) self.output = int(output) @@ -126,7 +127,7 @@ def get_id_as_name(self, number): # pragma: no cover logger.addHandler(logging.StreamHandler(sys.stdout)) logger.setLevel(logging.INFO) - sb = SaemuBox() + sb = SaemuBox(saemubox_ip="127.0.0.1") localhost = socket.gethostbyname(socket.gethostname()) sb.senders.add(localhost) diff --git a/nowplaying/options.py b/nowplaying/options.py index da212c94..c206d257 100644 --- a/nowplaying/options.py +++ b/nowplaying/options.py @@ -1,4 +1,10 @@ -import configargparse +"""Options for Nowplaying.""" + +from __future__ import annotations + +from typing import Self + +import configargparse # type: ignore[import-untyped] from nowplaying.track.observers.dab_audio_companion import ( DabAudioCompanionTrackObserver, @@ -12,17 +18,19 @@ class Options: """Contain all hardcoded and loaded from configargparse options.""" """How many seconds the main daemon loop sleeps.""" - sleepSeconds = 1 + sleep_seconds = 1 """Default socket of 2 minutes, to prevent endless hangs on HTTP requests.""" - socketDefaultTimeout = 120 + socket_default_timeout = 120 - def __init__(self): + def __init__(self: Self) -> None: """Configure configargparse.""" self.__args = configargparse.ArgParser( - default_config_files=["/etc/nowplaying/conf.d/*.conf", "~/.nowplayingrc"] + default_config_files=["/etc/nowplaying/conf.d/*.conf", "~/.nowplayingrc"], ) - # TODO v3 remove this option + # TODO(hairmare): v3 remove this option + # https://github.com/radiorabe/nowplaying/issues/179 + self.saemubox_ip: str | None = None self.__args.add_argument( "-b", "--saemubox-ip", @@ -30,52 +38,75 @@ def __init__(self): help="IP address of SAEMUBOX", default="", ) - # TODO v3 remove this option + # TODO(hairmare): v3 remove this option + # https://github.com/radiorabe/nowplaying/issues/179 + self.check_saemubox_sender: bool = True self.__args.add_argument( "--check-saemubox-sender", dest="check_saemubox_sender", help="Check SRC SAEMUBOX IP", default=True, ) + + self.icecast: list[str] = [] + self.icecast_password: str = "" IcecastTrackObserver.Options.args(self.__args) + + self.dab: list[str] = [] + self.dab_send_dls: bool = False DabAudioCompanionTrackObserver.Options.args(self.__args) + + self.dab_smc: bool = False + self.dab_smc_ftp_hostname: str = "" + self.dab_smc_ftp_username: str = "" + self.dab_smc_ftp_password: str = "" SmcFtpTrackObserver.Options.args(self.__args) + + self.ticker_output_file: str = "" TickerTrackObserver.Options.args(self.__args) + + self.current_show_url: str = "" self.__args.add_argument( "-s", "--show", - dest="currentShowUrl", + dest="current_show_url", help="Current Show URL e.g. 'https://libretime.int.example.org/api/live-info-v2/format/json'", ) - # TODO v3 remove this option + # TODO(hairmare): v3 remove this option + # https://github.com/radiorabe/nowplaying/issues/179 + self.input_file: str = "/home/endlosplayer/Eingang/now-playing.xml" self.__args.add_argument( "--input-file", - dest="inputFile", + dest="input_file", help=( "XML 'now-playing' input file location, " "disable input by passing empty string, ie. --input-file=''" ), default="/home/endlosplayer/Eingang/now-playing.xml", ) + self.api_bind_address: str = "127.0.0.1" self.__args.add_argument( "--api-bind-address", - dest="apiBindAddress", + dest="api_bind_address", help="Bind address for the API server", - default="0.0.0.0", + default="127.0.0.1", ) + self.api_port: int = 8080 self.__args.add_argument( "--api-port", type=int, - dest="apiPort", + dest="api_port", help="Bind port for the API server", default=8080, ) + self.api_auth_users: dict[str, str] = {} self.__args.add_argument( "--api-auth-users", - dest="apiAuthUsers", + dest="api_auth_users", help="API Auth Users", default={"rabe": "rabe"}, ) + self.otlp_enable: bool = False self.__args.add_argument( "--instrumentation-otlp-enable", type=bool, @@ -86,6 +117,7 @@ def __init__(self): default=False, env_var="NOWPLAYING_INSTRUMENTATION_OTLP_ENABLE", ) + self.debug: bool = False self.__args.add_argument( "--debug", type=bool, @@ -96,6 +128,6 @@ def __init__(self): default=False, ) - def parse_known_args(self): + def parse_known_args(self: Self) -> None: """Parse known args with configargparse.""" self.__args.parse_known_args(namespace=self) diff --git a/nowplaying/otel.py b/nowplaying/otel.py index 0c5429bd..67c8f274 100644 --- a/nowplaying/otel.py +++ b/nowplaying/otel.py @@ -6,10 +6,13 @@ import logging import os from datetime import datetime +from pathlib import Path +from typing import Self, no_type_check from opentelemetry._logs import set_logger_provider from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler +from opentelemetry.sdk._logs._internal import LogRecord from opentelemetry.sdk._logs.export import ( BatchLogRecordProcessor, ConsoleLogExporter, @@ -18,9 +21,10 @@ from opentelemetry.sdk.resources import Resource -def _log_formatter(record): # pragma: no cover +@no_type_check +def _log_formatter(record: LogRecord) -> str: # pragma: no cover return ( - f"{datetime.fromtimestamp(record.timestamp/1000000000)} " + f"{datetime.fromtimestamp(record.timestamp/1000000000)} " # noqa: DTZ006 f"- {record.severity_text[:4]:4} " f"- {record.attributes['source_name'][11:]:14} " f"- {record.body} " @@ -32,16 +36,19 @@ def _log_formatter(record): # pragma: no cover class SourceAttributeFilter(logging.Filter): # pragma: no cover """Used on the handler to ensure that some attributes are carried over to otel.""" - def filter(self, record) -> bool: + @no_type_check + def filter(self: Self, record: LogRecord) -> bool: + """Carry over attributes to otel.""" record.source_name = record.name record.source_pathname = os.path.relpath( - record.pathname, os.path.dirname(os.path.dirname(__file__)) + record.pathname, + Path(__file__).parent.parent, ) record.source_lineno = record.lineno return True -def setup_otel(otlp_enable=False): # pragma: no cover +def setup_otel(*, otlp_enable: bool = False) -> None: # pragma: no cover """Configure opentelemetry logging to stdout and collector.""" root = logging.getLogger() root.setLevel(logging.INFO) @@ -51,12 +58,12 @@ def setup_otel(otlp_enable=False): # pragma: no cover { "service.name": "nowplaying", }, - ) + ), ) set_logger_provider(logger_provider) console_exporter = ConsoleLogExporter( - formatter=lambda record: _log_formatter(record) + formatter=lambda record: _log_formatter(record), ) logger_provider.add_log_record_processor(SimpleLogRecordProcessor(console_exporter)) diff --git a/nowplaying/show/client.py b/nowplaying/show/client.py index 5f22f57d..f08d19e3 100644 --- a/nowplaying/show/client.py +++ b/nowplaying/show/client.py @@ -1,8 +1,13 @@ +"""Show client interacts with LibreTime.""" + +from __future__ import annotations + import datetime import logging import logging.handlers import re from html.entities import entitydefs +from typing import Match, Self import pytz import requests @@ -11,12 +16,15 @@ logger = logging.getLogger(__name__) +_EXCEPTION_SHOWCLIENT_NO_SHOW = "Unable to get current show information" +_EXCEPTION_SHOWCLIENT_NO_NAME = "Missing show name" +_EXCEPTION_SHOWCLIENT_NO_START = "Missing show start time" +_EXCEPTION_SHOWCLIENT_NO_END = "Missing show end time" + class ShowClientError(Exception): """ShowClient related exception.""" - pass - class ShowClient: """Fetches the show info from LibreTime now-playing v2 endpoint. @@ -28,15 +36,15 @@ class ShowClient: __cleanup_show_name_regexp = re.compile(r"&(\w+?);") __show_datetime_format = "%Y-%m-%d %H:%M:%S" - def __init__(self, current_show_url): + def __init__(self: Self, current_show_url: str) -> None: + """Create Show.""" self.current_show_url = current_show_url self.show = Show() - self.showtz = None + self.showtz = pytz.timezone(zone="UTC") - def get_show_info(self, force_update=False): + def get_show_info(self: Self, *, force_update: bool = False) -> Show: """Return a Show object.""" - if force_update: self.update() else: @@ -44,8 +52,8 @@ def get_show_info(self, force_update=False): return self.show - def lazy_update(self): - # only update the info if we expect that a new show has started + def lazy_update(self: Self) -> None: + """Only update the info if we expect that a new show has started.""" if datetime.datetime.now(pytz.timezone("UTC")) > self.show.endtime: logger.info("Show expired, going to update show info") self.update() @@ -53,7 +61,8 @@ def lazy_update(self): else: logger.debug("Show still running, won't update show info") - def update(self): + def update(self: Self) -> None: + """Update state.""" self.show = Show() # Create a new show object # Set the show's default end time to now + 30 seconds to prevent updates @@ -61,25 +70,22 @@ def update(self): # goes wrong later. self.show.set_endtime( datetime.datetime.now(pytz.timezone("UTC")) - + datetime.timedelta(seconds=self.__DEFAULT_SHOW_DURATION) + + datetime.timedelta(seconds=self.__DEFAULT_SHOW_DURATION), ) try: # try to get the current show informations from loopy's cast web # service - data = requests.get(self.current_show_url).json() - - logger.debug("Got show info: %s" % data) + data = requests.get(self.current_show_url, timeout=60).json() - except Exception as e: - logger.error("Unable to get current show informations") + logger.debug("Got show info: %s", data) - logger.exception(e) - # LSB 2017: ignoring missing show update - # raise ShowClientError('Unable to get show informations: %s' % e) + except Exception: + logger.exception(_EXCEPTION_SHOWCLIENT_NO_SHOW) + # ignoring missing show update return - self.showtz = pytz.timezone(data["station"]["timezone"]) + self.showtz = pytz.timezone(zone=data["station"]["timezone"]) # pick the current show show_data = self.__pick_current_show(data) @@ -94,8 +100,7 @@ def update(self): if len(real_name) == 0: # keep the default show information - logger.error("No show name found") - raise ShowClientError("Missing show name") + raise ShowClientError(_EXCEPTION_SHOWCLIENT_NO_NAME) real_name = self.__cleanup_show_name(real_name) self.show.set_name(real_name) @@ -105,11 +110,13 @@ def update(self): end_time = show_data["ends"] if len(end_time) == 0: - logger.error("No end found") - raise ShowClientError("Missing show end time") + raise ShowClientError(_EXCEPTION_SHOWCLIENT_NO_END) endtime = self.showtz.localize( - datetime.datetime.strptime(end_time, self.__show_datetime_format) + datetime.datetime.strptime( # noqa: DTZ007 + end_time, + self.__show_datetime_format, + ), ) # store as UTC datetime object @@ -121,10 +128,13 @@ def update(self): if len(start_time) == 0: logger.error("No start found") - raise ShowClientError("Missing show start time") + raise ShowClientError(_EXCEPTION_SHOWCLIENT_NO_START) starttime = self.showtz.localize( - datetime.datetime.strptime(start_time, self.__show_datetime_format) + datetime.datetime.strptime( # noqa: DTZ007 + start_time, + self.__show_datetime_format, + ), ) # store as UTC datetime object @@ -134,10 +144,10 @@ def update(self): # This prevents stale (wrong) show informations from beeing pushed to # the live stream and stops hammering the service every second if self.show.endtime < datetime.datetime.now(pytz.timezone("UTC")): - logger.error("Show endtime %s is in the past" % self.show.endtime) + logger.error("Show endtime %s is in the past", self.show.endtime) - raise ShowClientError( - "Show end time (%s) is in the past" % self.show.endtime + raise ShowClientError( # noqa: TRY003 + f"Show end time ({self.show.endtime}) is in the past", # noqa: EM102 ) # get the show's URL @@ -150,15 +160,17 @@ def update(self): self.show.set_url(url) logger.info( - 'Show "%s" started and runs from %s till %s' - % (self.show.name, starttime, endtime) + 'Show "%s" started and runs from %s till %s', + self.show.name, + starttime, + endtime, ) logger.debug(self.show) - def __cleanup_show_name(self, name) -> str: + def __cleanup_show_name(self: Self, name: str) -> str: """Cleanup name by undoing htmlspecialchars from libretime zf1 mvc.""" - def __entityref_decode(m): + def __entityref_decode(m: Match[str]) -> str: try: return entitydefs[m.group(1)] except KeyError: @@ -166,7 +178,7 @@ def __entityref_decode(m): return self.__cleanup_show_name_regexp.sub(__entityref_decode, name) - def __pick_current_show(self, data): + def __pick_current_show(self: Self, data: dict[str, dict]) -> dict[str, str] | None: """Pick the current show from the data. If there is no current show and the next one starts reasonably soon, pick that. @@ -176,21 +188,25 @@ def __pick_current_show(self, data): logger.info("No current show is playing, checking next show") if data["shows"]["next"] and data["shows"]["next"][0]: show = data["shows"]["next"][0] - logger.info("Next show is %s" % show["name"]) + logger.info("Next show is %s", show["name"]) next_start = self.showtz.localize( - datetime.datetime.strptime( - show["starts"], self.__show_datetime_format - ) + datetime.datetime.strptime( # noqa: DTZ007 + show["starts"], + self.__show_datetime_format, + ), ) logger.warning( - datetime.datetime.now(pytz.timezone("UTC")) - + datetime.timedelta(minutes=15) + "%s", + ( + datetime.datetime.now(pytz.timezone("UTC")) + + datetime.timedelta(minutes=15) + ), ) logger.warning(next_start) if next_start < datetime.datetime.now( - pytz.timezone("UTC") + pytz.timezone("UTC"), ) + datetime.timedelta(minutes=15): logger.info("Next show starts soon enough, using it") return show - return + return None return data["shows"]["current"] diff --git a/nowplaying/show/show.py b/nowplaying/show/show.py index 305bb203..e21fa50b 100644 --- a/nowplaying/show/show.py +++ b/nowplaying/show/show.py @@ -1,7 +1,10 @@ +"""Nowplaying Show model.""" + import datetime import logging import logging.handlers import uuid +from typing import Self import pytz @@ -9,17 +12,19 @@ DEFAULT_SHOW_URL = "https://www.rabe.ch" +_EXCEPTION_SHOW_ERROR_STARTTIME_NO_DATETIME = "starttime has to be a datatime object" +_EXCEPTION_SHOW_ERROR_ENDTIME_NO_DATETIME = "endtime has to be a datatime object" + class ShowError(Exception): """Show related exception.""" - pass - class Show: """Show object which has a start and end time and an optional URL.""" - def __init__(self): + def __init__(self: Self) -> None: + """Create Show.""" self.name = "" self.url = DEFAULT_SHOW_URL @@ -36,29 +41,34 @@ def __init__(self): # The show's end time, initially set to to now self.endtime = now - def set_name(self, name): + def set_name(self: Self, name: str) -> None: + """Set Show name.""" # The name of the show self.name = name - def set_url(self, url): + def set_url(self: Self, url: str) -> None: + """Set Show URL.""" # The URL of the show self.url = url - def set_starttime(self, starttime): + def set_starttime(self: Self, starttime: datetime.datetime) -> None: + """Set Show start time.""" if not isinstance(starttime, datetime.datetime): - raise ShowError("starttime has to be a datatime object") + raise ShowError(_EXCEPTION_SHOW_ERROR_STARTTIME_NO_DATETIME) # The show's start time as a datetime object self.starttime = starttime - def set_endtime(self, endtime): + def set_endtime(self: Self, endtime: datetime.datetime) -> None: + """Set Show end time.""" if not isinstance(endtime, datetime.datetime): - raise ShowError("endtime has to be a datatime object") + raise ShowError(_EXCEPTION_SHOW_ERROR_ENDTIME_NO_DATETIME) # The show's end time as a datetime object self.endtime = endtime - def __str__(self): + def __str__(self: Self) -> str: + """Stringify Show.""" return ( f"Show '{self.name}' ({self.uuid}), " f"start: '{self.starttime}', end: '{self.endtime}', url: {self.url}" diff --git a/nowplaying/track/handler.py b/nowplaying/track/handler.py index 9aab92e3..04bf19c9 100644 --- a/nowplaying/track/handler.py +++ b/nowplaying/track/handler.py @@ -1,13 +1,21 @@ """Track event handling subject of the observer.""" +from __future__ import annotations + import logging import logging.handlers +from typing import TYPE_CHECKING, Self + +if TYPE_CHECKING: # pragma: no cover + from .observers.base import TrackObserver + from .track import Track -from .observers.base import TrackObserver -from .track import Track logger = logging.getLogger(__name__) +_EXCEPTION_TRACK_HANDLER_ERROR_START = "Observer failed to start track" +_EXCEPTION_TRACK_HANDLER_ERROR_FINISH = "Observer failed to finish track" + class TrackEventHandler: """Inform all registered track-event observers about a track change. @@ -15,24 +23,24 @@ class TrackEventHandler: This is the subject of the classical observer pattern """ - def __init__(self): + def __init__(self: Self) -> None: """Initialize the track event handler.""" - self.__observers = [] + self.__observers: list[TrackObserver] = [] - def register_observer(self, observer: TrackObserver): + def register_observer(self: Self, observer: TrackObserver) -> None: """Register an observer to be informed about track changes.""" logger.info("Registering TrackObserver '%s'", observer.__class__.__name__) self.__observers.append(observer) - def remove_observer(self, observer: TrackObserver): + def remove_observer(self: Self, observer: TrackObserver) -> None: """Remove an observer from the list of observers.""" self.__observers.remove(observer) - def get_observers(self) -> list: + def get_observers(self: Self) -> list: """Return register observers to allow inspecting them.""" return self.__observers - def track_started(self, track: Track): + def track_started(self: Self, track: Track) -> None: """Inform all registered track-event observers about a track started event.""" logger.info( "Sending track-started event to %s observers: %s", @@ -42,15 +50,16 @@ def track_started(self, track: Track): for observer in self.__observers: logger.debug( - "Sending track-started event to observer %s", observer.__class__ + "Sending track-started event to observer %s", + observer.__class__, ) try: observer.track_started(track) - except Exception as error: - logger.exception(error) + except Exception: + logger.exception(_EXCEPTION_TRACK_HANDLER_ERROR_START) - def track_finished(self, track: Track): + def track_finished(self: Self, track: Track) -> None: """Inform all registered track-event observers about a track finished event.""" logger.info( "Sending track-finished event to %s observers: %s", @@ -60,10 +69,11 @@ def track_finished(self, track: Track): for observer in self.__observers: logger.debug( - "Sending track-finished event to observer %s", observer.__class__ + "Sending track-finished event to observer %s", + observer.__class__, ) try: observer.track_finished(track) - except Exception as error: - logger.exception(error) + except Exception: + logger.exception(_EXCEPTION_TRACK_HANDLER_ERROR_FINISH) diff --git a/nowplaying/track/observers/base.py b/nowplaying/track/observers/base.py index 87b0cea6..d524043c 100644 --- a/nowplaying/track/observers/base.py +++ b/nowplaying/track/observers/base.py @@ -1,6 +1,16 @@ +"""Abstract base for TrackObservers.""" + +from __future__ import annotations + from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Self, TypeVar + +if TYPE_CHECKING: # pragma: no cover + import configargparse # type: ignore[import-untyped] -import configargparse + from nowplaying.track.track import Track + +TTrackObserverOptions = TypeVar("TTrackObserverOptions", bound="TrackObserver.Options") class TrackObserver(ABC): @@ -9,18 +19,24 @@ class TrackObserver(ABC): name = "TrackObserver" class Options(ABC): + """Abstract base class for add TrackObserver.Options.""" + @classmethod @abstractmethod - def args(cls, args: configargparse.ArgParser) -> None: # pragma: no cover - pass - - def get_name(self): + def args( + cls: type[TTrackObserverOptions], + args: configargparse.ArgParser, + ) -> None: # pragma: no cover + """Get args for Options.""" + + def get_name(self: Self) -> str: + """Get name.""" return self.name @abstractmethod - def track_started(self, track): # pragma: no cover - pass + def track_started(self: Self, track: Track) -> None: # pragma: no cover + """Track started.""" @abstractmethod - def track_finished(self, track): # pragma: no cover - pass + def track_finished(self: Self, track: Track) -> None: # pragma: no cover + """Track finished.""" diff --git a/nowplaying/track/observers/dab_audio_companion.py b/nowplaying/track/observers/dab_audio_companion.py index ee6b5744..67994321 100644 --- a/nowplaying/track/observers/dab_audio_companion.py +++ b/nowplaying/track/observers/dab_audio_companion.py @@ -1,3 +1,4 @@ +# mypy: ignore-errors import logging import urllib from datetime import timedelta @@ -48,7 +49,7 @@ def __init__(self, options: Options): self.dls_enabled = self.last_frame_was_dl_plus = self._options.dl_plus logger.info( "DAB+ Audio Companion initialised with URL: %s, DLS+ enabled: %r" - % (self.base_url, self.dls_enabled) + % (self.base_url, self.dls_enabled), ) def track_started(self, track: Track): @@ -61,9 +62,9 @@ def track_started(self, track: Track): if track.get_duration() < timedelta(seconds=5): logger.info( - "Track is less than 5 seconds, not sending to DAB+ Audio Companion" + "Track is less than 5 seconds, not sending to DAB+ Audio Companion", ) - return + return None if not track.has_default_title() and not track.has_default_artist(): params["artist"] = track.artist @@ -71,7 +72,7 @@ def track_started(self, track: Track): self.last_frame_was_dl_plus = True elif self.last_frame_was_dl_plus: logger.info( - "Track has default info, using show instead. Sending DLS+ delete tags." + "Track has default info, using show instead. Sending DLS+ delete tags.", ) message = DLPlusMessage() # track.artist contains station name if no artist is set @@ -91,7 +92,7 @@ def track_started(self, track: Track): logger.info( f"DAB+ Audio Companion URL: {self.base_url} " - f"data: {params} is DL+: {self.last_frame_was_dl_plus}" + f"data: {params} is DL+: {self.last_frame_was_dl_plus}", ) resp = requests.post(self.base_url, params) @@ -104,7 +105,7 @@ def _track_started_plain(self, track): if track.has_default_title() and track.has_default_artist(): logger.info( - "%s: Track has default info, using show instead" % self.__class__ + "%s: Track has default info, using show instead" % self.__class__, ) title = track.show.name @@ -112,7 +113,7 @@ def _track_started_plain(self, track): # artist is an unicode string which we have to encode into UTF-8 # http://bugs.python.org/issue216716 song_string = urllib.parse.quote_plus( - f"{track.artist.encode('utf8')} - {title.encode('utf8')}" + f"{track.artist.encode('utf8')} - {title.encode('utf8')}", ) update_url = f"{self.base_url}?dls={song_string}" diff --git a/nowplaying/track/observers/icecast.py b/nowplaying/track/observers/icecast.py index c1231df2..e6b79709 100644 --- a/nowplaying/track/observers/icecast.py +++ b/nowplaying/track/observers/icecast.py @@ -1,14 +1,25 @@ +"""Send PAD to icecast endpoints.""" + +from __future__ import annotations + import logging +from typing import TYPE_CHECKING, Self -import configargparse import requests -from ...util import parse_icecast_url -from ..track import Track -from .base import TrackObserver +from nowplaying.track.observers.base import TrackObserver +from nowplaying.util import parse_icecast_url + +if TYPE_CHECKING: # pragma: no cover + import configargparse # type: ignore[import-untyped] + + from nowplaying.track.observers.base import TTrackObserverOptions + from nowplaying.track.track import Track logger = logging.getLogger(__name__) +_NOWPLAYING_TRACK_EXEPTION = "request failed" + class IcecastTrackObserver(TrackObserver): """Update track metadata on an icecast mountpoint.""" @@ -19,18 +30,26 @@ class Options(TrackObserver.Options): """IcecastTrackObserver options.""" @classmethod - def args(cls, args: configargparse.ArgParser) -> None: - # TODO v3 remove this option + def args( + cls: type[TTrackObserverOptions], + args: configargparse.ArgParser, + ) -> None: + """Args for IcecastTrackObserver.""" + # TODO(hairmare): v3 remove this option + # https://github.com/radiorabe/nowplaying/issues/179 args.add_argument( "-m", "--icecast-base", - dest="icecastBase", + dest="icecast_base", help="Icecast base URL", default="http://icecast.example.org:8000/admin/", ) - # TODO v3 remove this option + # TODO(hairmare): v3 remove this option + # https://github.com/radiorabe/nowplaying/issues/179 args.add_argument( - "--icecast-password", dest="icecastPassword", help="Icecast Password" + "--icecast-password", + dest="icecast_password", + help="Icecast Password", ) args.add_argument( "-i", @@ -44,17 +63,20 @@ def args(cls, args: configargparse.ArgParser) -> None: ) def __init__( - self, + self: Self, url: str, username: str | None = None, password: str | None = None, mount: str | None = None, - ): - # TODO v3 remove optional args and only support parsed URLs + ) -> None: + """Create IcecastTrackObserver.Config.""" + # TODO(hairmare): v3 remove optional args and only support parsed URLs + # https://github.com/radiorabe/nowplaying/issues/179 (self.url, self.username, self.password, self.mount) = parse_icecast_url( - url + url, ) - # TODO v3 remove non URL usage of username, password, ... + # TODO(hairmare): v3 remove non URL usage of username, password, ... + # https://github.com/radiorabe/nowplaying/issues/179 if not self.username and username: # grab from args if not in URL logger.warning("deprecated use username from URL") @@ -72,17 +94,21 @@ def __init__( logger.warning("deprecated use mount from URL") self.mount = mount if not self.password: - raise ValueError("Missing required parameter password for %s" % url) + raise ValueError(f"Missing required parameter password for {url}") # noqa: EM102, TRY003 if not self.mount: - raise ValueError("Missing required parameter mount for %s" % url) + raise ValueError(f"Missing required parameter mount for {url}") # noqa: EM102, TRY003 - def __init__(self, options: Options): + def __init__(self: Self, options: Options) -> None: + """Create IcecastTrackObserver.""" self.options = options - logger.info(f"Icecast URL: {self.options.url} mount: {self.options.mount}") + logger.info("Icecast URL: %s mount: %s", self.options.url, self.options.mount) - def track_started(self, track: Track): + def track_started(self: Self, track: Track) -> None: + """Track started.""" logger.info( - f"Updating Icecast Metadata for track: {track.artist} - {track.title}" + "Updating Icecast Metadata for track: %s - %s", + track.artist, + track.title, ) title = track.title @@ -101,15 +127,19 @@ def track_started(self, track: Track): try: requests.get( self.options.url, - auth=(self.options.username, self.options.password), + auth=(self.options.username, self.options.password), # type: ignore[arg-type] params=params, + timeout=60, ) - except requests.exceptions.RequestException as e: - logger.exception(e) + except requests.exceptions.RequestException: + logger.exception(_NOWPLAYING_TRACK_EXEPTION) logger.info( - f"Icecast Metadata updated on {self.options.url} with data: {params}" + "Icecast Metadata updated on %s with data: %s", + self.options.url, + params, ) - def track_finished(self, track): - return True + def track_finished(self: Self, _: Track) -> None: + """Track finished.""" + return diff --git a/nowplaying/track/observers/scrobbler.py b/nowplaying/track/observers/scrobbler.py index 080712ca..db0aab0f 100644 --- a/nowplaying/track/observers/scrobbler.py +++ b/nowplaying/track/observers/scrobbler.py @@ -1,3 +1,4 @@ +# mypy: ignore-errors # TODO scrobbling is not currently supported # remove the no cover pragma from this file if you support it again import calendar # pragma: no cover @@ -41,7 +42,7 @@ def track_started(self, track): logger.info( "AS now-playing notification for track: %s - %s" - % (track.artist, track.title) + % (track.artist, track.title), ) # scrobble to all networks diff --git a/nowplaying/track/observers/smc_ftp.py b/nowplaying/track/observers/smc_ftp.py index 28fee051..3da6c276 100644 --- a/nowplaying/track/observers/smc_ftp.py +++ b/nowplaying/track/observers/smc_ftp.py @@ -1,15 +1,26 @@ +"""Upload PAD to SMC.""" + +from __future__ import annotations + import logging from datetime import timedelta from ftplib import FTP_TLS from io import BytesIO +from typing import TYPE_CHECKING, Self + +from nowplaying.track.observers.base import TrackObserver + +if TYPE_CHECKING: # pragma: no cover + import configargparse # type: ignore[import-untyped] -import configargparse + from nowplaying.track.observers.base import TTrackObserverOptions + from nowplaying.track.track import Track -from ..track import Track -from .base import TrackObserver logger = logging.getLogger(__name__) +_NOWPLAYING_DAB_MAXLEN = 128 + class SmcFtpTrackObserver(TrackObserver): """Update track metadata for DLS and DL+ to the SMC FTP server.""" @@ -17,8 +28,14 @@ class SmcFtpTrackObserver(TrackObserver): name = "SMC FTP" class Options(TrackObserver.Options): # pragma: no coverage + """Options for SmcFtpTrackObserver.""" + @classmethod - def args(cls, args: configargparse.ArgParser): + def args( + cls: type[TTrackObserverOptions], + args: configargparse.ArgParser, + ) -> None: + """Create args.""" args.add_argument( "--dab-smc", help="Enable SMC FTP delivery", @@ -35,35 +52,44 @@ def args(cls, args: configargparse.ArgParser): help="Username for SMC FTP server", ) args.add_argument( - "--dab-smc-ftp-password", help="Password for SMC FTP server" + "--dab-smc-ftp-password", + help="Password for SMC FTP server", ) - def __init__(self, hostname: str, username: str, password: str) -> None: + def __init__(self: Self, hostname: str, username: str, password: str) -> None: + """Create SmcFtpTrackObserver.Config.""" self.hostname: str = hostname self.username: str = username self.password: str = password - def __init__(self, options: Options): + def __init__(self: Self, options: Options) -> None: + """Create SmcFtpTrackObserver.""" self._options = options - def track_started(self, track: Track): - logger.info(f"Updating DAB+ DLS for track {track.artist=} {track.title=}") + def track_started(self: Self, track: Track) -> None: + """Track started.""" + logger.info( + "Updating DAB+ DLS for track artist=%s title=%s", + track.artist, + track.title, + ) if track.get_duration() < timedelta(seconds=5): logger.info( - "Track is less than 5 seconds, not sending to SMC" - f"{track.artist=} {track.title=}" + "Track is less than 5 seconds, not sending to SMC artist=%s title=%s", + track.artist, + track.title, ) return dls, dlplus = _dls_from_track(track) # check for too long meta and shorten to just artist - if dls.getbuffer().nbytes > 128: # pragma: no cover - logger.warning(f"SMC DLS to long {dls.getvalue().decode('latin1')=}") + if dls.getbuffer().nbytes > _NOWPLAYING_DAB_MAXLEN: # pragma: no cover + logger.warning("SMC DLS to long %s", dls.getvalue().decode("latin1")) dls, dlplus = _dls_from_track(track, title=False) - ftp = FTP_TLS() + ftp = FTP_TLS() # noqa: S321 ftp.connect(self._options.hostname) ftp.sendcmd(f"USER {self._options.username}") ftp.sendcmd(f"PASS {self._options.password}") @@ -75,16 +101,18 @@ def track_started(self, track: Track): ftp.close() logger.info( - f"SMC FTP {self._options.hostname=} " - f"{dls.getvalue().decode('latin1')=} " - f"{dlplus.getvalue().decode('latin1')=}" + "SMC FTP hostname=%s dls=%s dlsplus=%", + self._options.hostname, + dls.getvalue().decode("latin1"), + dlplus.getvalue().decode("latin1"), ) - def track_finished(self, track): - return True + def track_finished(self: Self, _: Track) -> None: + """Track finished.""" + return -def _dls_from_track(track: Track, title=True) -> (BytesIO, BytesIO): +def _dls_from_track(track: Track, *, title: bool = True) -> tuple[BytesIO, BytesIO]: # track.artist contains station name if no artist is set dls = f"{track.artist} - {track.show.name}" if title else track.artist dlplus = "" diff --git a/nowplaying/track/observers/ticker.py b/nowplaying/track/observers/ticker.py index 46619cbf..4ba15fc6 100644 --- a/nowplaying/track/observers/ticker.py +++ b/nowplaying/track/observers/ticker.py @@ -1,15 +1,24 @@ +"""TickerTrackObserver generates the songticker.xml file.""" + +from __future__ import annotations + import datetime import logging import uuid import warnings +from typing import TYPE_CHECKING, Self -import configargparse -import isodate -import lxml.builder +import isodate # type: ignore[import-untyped] +import lxml.builder # type: ignore[import-untyped] import lxml.etree import pytz -from .base import TrackObserver +from nowplaying.track.observers.base import TrackObserver, TTrackObserverOptions + +if TYPE_CHECKING: # pragma: no cover + import configargparse # type: ignore[import-untyped] + + from nowplaying.track.track import Track logger = logging.getLogger(__name__) @@ -28,7 +37,11 @@ class Options(TrackObserver.Options): """TickerTrackObserver options.""" @classmethod - def args(cls, args: configargparse.ArgParser) -> None: + def args( + cls: type[TTrackObserverOptions], + args: configargparse.ArgParser, + ) -> None: + """Build args.""" args.add_argument( "--xml-output", dest="tickerOutputFile", @@ -36,19 +49,25 @@ def args(cls, args: configargparse.ArgParser) -> None: default="/var/www/localhost/htdocs/songticker/0.9.3/current.xml", ) - def __init__(self, file_path: str): + def __init__(self: Self, file_path: str) -> None: + """Create TickerTrackObserver.Config.""" self.file_path = file_path - def __init__(self, options: Options): + def __init__(self: Self, options: Options) -> None: + """Create TickerTrackObserver.""" warnings.warn( "The XML ticker format will be replaced with a JSON variant in the future", PendingDeprecationWarning, + stacklevel=2, ) self.ticker_file_path = options.file_path - def track_started(self, track): + def track_started(self: Self, track: Track) -> None: + """Track started.""" logger.info( - f"Updating Ticker XML file for track: {track.artist} - {track.title}" + "Updating Ticker XML file for track: %s - %s", + track.artist, + track.title, ) try: tz = pytz.timezone("Europe/Zurich") @@ -59,11 +78,11 @@ def track_started(self, track): now = isodate.datetime_isoformat(datetime.datetime.now(tz)) - MAIN_NAMESPACE = "http://rabe.ch/schema/ticker.xsd" - XLINK_NAMESPACE = "http://www.w3.org/1999/xlink" - XLINK = "{%s}" % XLINK_NAMESPACE + MAIN_NAMESPACE = "http://rabe.ch/schema/ticker.xsd" # noqa: N806 + XLINK_NAMESPACE = "http://www.w3.org/1999/xlink" # noqa: N806 + XLINK = "{%s}" % XLINK_NAMESPACE # noqa: N806, UP031 - E = lxml.builder.ElementMaker( + E = lxml.builder.ElementMaker( # noqa: N806 namespace=MAIN_NAMESPACE, nsmap={None: MAIN_NAMESPACE, "xlink": XLINK_NAMESPACE}, ) @@ -73,17 +92,17 @@ def track_started(self, track): show_ref.attrib[XLINK + "show"] = "replace" ticker = E.ticker( - E.identifier("ticker-%s" % uuid.uuid4()), + E.identifier(f"ticker-{uuid.uuid4()}"), E.creator("now-playing daemon v2"), E.date(now), E.show( E.name(track.show.name), show_ref, E.startTime( - isodate.datetime_isoformat(track.show.starttime.astimezone(tz)) + isodate.datetime_isoformat(track.show.starttime.astimezone(tz)), ), E.endTime( - isodate.datetime_isoformat(track.show.endtime.astimezone(tz)) + isodate.datetime_isoformat(track.show.endtime.astimezone(tz)), ), id=track.show.uuid, ), @@ -103,5 +122,5 @@ def track_started(self, track): encoding="utf-8", ) - def track_finished(self, track): - return True + def track_finished(self: Self, _: Track) -> None: + """Track finished.""" diff --git a/nowplaying/track/track.py b/nowplaying/track/track.py index 4bce5c0e..3606bb5f 100644 --- a/nowplaying/track/track.py +++ b/nowplaying/track/track.py @@ -1,31 +1,39 @@ +"""Nowplaying Track model.""" + import datetime import logging import logging.handlers import uuid +from typing import Self import pytz +from nowplaying.show.show import Show + logger = logging.getLogger(__name__) DEFAULT_ARTIST = "Radio Bern" DEFAULT_TITLE = "Livestream" +_EXCEPTION_TRACK_ERROR_NUMBER_NOT_INT = "track number has to be a positive integer" +_EXCEPTION_TRACK_ERROR_STARTTIME_NO_DATETIME = "starttime has to be a datatime object" +_EXCEPTION_TRACK_ERROR_ENDTIME_NO_DATETIME = "endtime has to be a datatime object" + class TrackError(Exception): """Track related exception.""" - pass - class Track: """Track object which has a start and end time and a related show.""" - def __init__(self): - self.artist = None + def __init__(self: Self) -> None: + """Create Track object.""" + self.artist = "" - self.title = None + self.title = "" - self.album = None + self.album = "" self.track = 1 @@ -41,61 +49,63 @@ def __init__(self): # The show's end time, initially set to to now self.endtime = now - def set_artist(self, artist): + def set_artist(self: Self, artist: str) -> None: + """Set Track artist.""" self.artist = artist - def set_title(self, title): + def set_title(self: Self, title: str) -> None: + """Set Track title.""" self.title = title - def set_album(self, album): + def set_album(self: Self, album: str) -> None: + """Set Track album.""" self.album = album - def set_track(self, track): + def set_track(self: Self, track: int) -> None: + """Set Track number.""" if track < 0: - raise TrackError("track number has to be a positive integer") + raise TrackError(_EXCEPTION_TRACK_ERROR_NUMBER_NOT_INT) self.track = track - def set_starttime(self, starttime): + def set_starttime(self: Self, starttime: datetime.datetime) -> None: + """Set Track start time.""" if not isinstance(starttime, datetime.datetime): - raise TrackError("starttime has to be a datatime object") + raise TrackError(_EXCEPTION_TRACK_ERROR_STARTTIME_NO_DATETIME) # The track's start time as a datetime object self.starttime = starttime - def set_endtime(self, endtime): + def set_endtime(self: Self, endtime: datetime.datetime) -> None: + """Set Track end time.""" if not isinstance(endtime, datetime.datetime): - raise TrackError("endtime has to be a datatime object") + raise TrackError(_EXCEPTION_TRACK_ERROR_ENDTIME_NO_DATETIME) # The track's end time as a datetime object self.endtime = endtime - def set_duration(self, seconds): + def set_duration(self: Self, seconds: int) -> None: + """Set Track duration.""" self.endtime = self.starttime + datetime.timedelta(seconds=int(seconds)) - def set_show(self, show): - # if not isinstance(show, show.Show): - # raise TrackError('show has to be a Show object') - - # The show which the track is related to + def set_show(self: Self, show: Show) -> None: + """Set Show for Track.""" self.show = show - def get_duration(self): + def get_duration(self: Self) -> datetime.timedelta: + """Get duration of Track.""" return self.endtime - self.starttime - def has_default_artist(self): - if self.artist == DEFAULT_ARTIST: - return True - - return False - - def has_default_title(self): - if self.title == DEFAULT_TITLE: - return True + def has_default_artist(self: Self) -> bool: + """Return True if Track has default artist.""" + return self.artist == DEFAULT_ARTIST - return False + def has_default_title(self: Self) -> bool: + """Return True if Track has default title.""" + return self.title == DEFAULT_TITLE - def __str__(self): + def __str__(self: Self) -> str: + """Stringify Track.""" return ( f"Track '{self.artist}' - '{self.title}', " f"start: '{self.starttime}', end: '{self.endtime}', uid: {self.uuid}" diff --git a/nowplaying/util.py b/nowplaying/util.py index c15cbf31..3c56e459 100644 --- a/nowplaying/util.py +++ b/nowplaying/util.py @@ -1,3 +1,7 @@ +"""Utils for nowplaying.""" + +from __future__ import annotations + import logging from urllib.parse import parse_qs, urlparse @@ -25,10 +29,14 @@ def parse_icecast_url( ('https://localhost:443/', None, None, None) Args: + ---- url (str): The Icecast URL to parse. + Returns: + ------- Tuple[str, Optional[str], Optional[str], Optional[str]]: The URL, username, password, and mountpoint. + """ parsed = urlparse(url) port = parsed.port or parsed.scheme == "https" and 443 or 80 @@ -39,5 +47,5 @@ def parse_icecast_url( try: mount = parse_qs(parsed.query)["mount"][0] except KeyError: - logger.warning("Missing mount parameter in URL %s" % url) + logger.warning("Missing mount parameter in URL %s", url) return (url, username, password, mount) diff --git a/poetry.lock b/poetry.lock index 483e1833..09a445c9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,25 +1,14 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. - -[[package]] -name = "annotated-types" -version = "0.6.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, -] +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "anyio" -version = "4.2.0" +version = "4.4.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, ] [package.dependencies] @@ -31,6 +20,25 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[package]] +name = "attrs" +version = "24.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-24.1.0-py3-none-any.whl", hash = "sha256:377b47448cb61fea38533f671fba0d0f8a96fd58facd4dc518e3dac9dbea0905"}, + {file = "attrs-24.1.0.tar.gz", hash = "sha256:adbdec84af72d38be7628e353a09b6a6790d15cd71819f6e9d7b0faa8a125745"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + [[package]] name = "autocommand" version = "2.2.2" @@ -44,72 +52,32 @@ files = [ [[package]] name = "babel" -version = "2.14.0" +version = "2.15.0" description = "Internationalization utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, - {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, ] [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] -name = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, -] - -[[package]] -name = "black" -version = "24.8.0" -description = "The uncompromising code formatter." +name = "backports-tarfile" +version = "1.2.0" +description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, + {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, + {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"}, ] -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" - [package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] [[package]] name = "certifi" @@ -223,13 +191,13 @@ files = [ [[package]] name = "cheroot" -version = "10.0.0" +version = "10.0.1" description = "Highly-optimized, pure-python HTTP server" optional = false python-versions = ">=3.6" files = [ - {file = "cheroot-10.0.0-py3-none-any.whl", hash = "sha256:8f65dd38ad3d56419cfe2d1b5e4b4e3282b1d58758ca2a336231641a80cf0717"}, - {file = "cheroot-10.0.0.tar.gz", hash = "sha256:59c4a1877fef9969b3c3c080caaaf377e2780919437853fc0d32a9df40b311f0"}, + {file = "cheroot-10.0.1-py3-none-any.whl", hash = "sha256:6ea332f20bfcede14e66174d112b30e9807492320d737ca628badc924d997595"}, + {file = "cheroot-10.0.1.tar.gz", hash = "sha256:e0b82f797658d26b8613ec8eb563c3b08e6bd6a7921e9d5089bd1175ad1b1740"}, ] [package.dependencies] @@ -325,63 +293,63 @@ yaml = ["PyYAML"] [[package]] name = "coverage" -version = "7.4.1" +version = "7.6.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, - {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, - {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, - {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, - {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, - {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, - {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, - {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, - {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, - {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, - {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, - {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, - {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, - {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, + {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, + {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, + {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, + {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, + {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, + {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, + {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, + {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, + {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, + {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, + {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, + {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, ] [package.extras] @@ -432,6 +400,22 @@ files = [ [package.dependencies] python-dateutil = ">=2.4" +[[package]] +name = "filelock" +version = "3.15.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] +typing = ["typing-extensions (>=4.8)"] + [[package]] name = "ghp-import" version = "2.1.0" @@ -451,17 +435,17 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "googleapis-common-protos" -version = "1.62.0" +version = "1.63.2" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, - {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, + {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, + {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, ] [package.dependencies] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] @@ -482,69 +466,61 @@ colorama = ">=0.4" [[package]] name = "grpcio" -version = "1.60.1" +version = "1.65.4" description = "HTTP/2-based RPC framework" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "grpcio-1.60.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:14e8f2c84c0832773fb3958240c69def72357bc11392571f87b2d7b91e0bb092"}, - {file = "grpcio-1.60.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:33aed0a431f5befeffd9d346b0fa44b2c01aa4aeae5ea5b2c03d3e25e0071216"}, - {file = "grpcio-1.60.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:fead980fbc68512dfd4e0c7b1f5754c2a8e5015a04dea454b9cada54a8423525"}, - {file = "grpcio-1.60.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:082081e6a36b6eb5cf0fd9a897fe777dbb3802176ffd08e3ec6567edd85bc104"}, - {file = "grpcio-1.60.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55ccb7db5a665079d68b5c7c86359ebd5ebf31a19bc1a91c982fd622f1e31ff2"}, - {file = "grpcio-1.60.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9b54577032d4f235452f77a83169b6527bf4b77d73aeada97d45b2aaf1bf5ce0"}, - {file = "grpcio-1.60.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7d142bcd604166417929b071cd396aa13c565749a4c840d6c702727a59d835eb"}, - {file = "grpcio-1.60.1-cp310-cp310-win32.whl", hash = "sha256:2a6087f234cb570008a6041c8ffd1b7d657b397fdd6d26e83d72283dae3527b1"}, - {file = "grpcio-1.60.1-cp310-cp310-win_amd64.whl", hash = "sha256:f2212796593ad1d0235068c79836861f2201fc7137a99aa2fea7beeb3b101177"}, - {file = "grpcio-1.60.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:79ae0dc785504cb1e1788758c588c711f4e4a0195d70dff53db203c95a0bd303"}, - {file = "grpcio-1.60.1-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:4eec8b8c1c2c9b7125508ff7c89d5701bf933c99d3910e446ed531cd16ad5d87"}, - {file = "grpcio-1.60.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:8c9554ca8e26241dabe7951aa1fa03a1ba0856688ecd7e7bdbdd286ebc272e4c"}, - {file = "grpcio-1.60.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91422ba785a8e7a18725b1dc40fbd88f08a5bb4c7f1b3e8739cab24b04fa8a03"}, - {file = "grpcio-1.60.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cba6209c96828711cb7c8fcb45ecef8c8859238baf15119daa1bef0f6c84bfe7"}, - {file = "grpcio-1.60.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c71be3f86d67d8d1311c6076a4ba3b75ba5703c0b856b4e691c9097f9b1e8bd2"}, - {file = "grpcio-1.60.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:af5ef6cfaf0d023c00002ba25d0751e5995fa0e4c9eec6cd263c30352662cbce"}, - {file = "grpcio-1.60.1-cp311-cp311-win32.whl", hash = "sha256:a09506eb48fa5493c58f946c46754ef22f3ec0df64f2b5149373ff31fb67f3dd"}, - {file = "grpcio-1.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:49c9b6a510e3ed8df5f6f4f3c34d7fbf2d2cae048ee90a45cd7415abab72912c"}, - {file = "grpcio-1.60.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:b58b855d0071575ea9c7bc0d84a06d2edfbfccec52e9657864386381a7ce1ae9"}, - {file = "grpcio-1.60.1-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:a731ac5cffc34dac62053e0da90f0c0b8560396a19f69d9703e88240c8f05858"}, - {file = "grpcio-1.60.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:cf77f8cf2a651fbd869fbdcb4a1931464189cd210abc4cfad357f1cacc8642a6"}, - {file = "grpcio-1.60.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c557e94e91a983e5b1e9c60076a8fd79fea1e7e06848eb2e48d0ccfb30f6e073"}, - {file = "grpcio-1.60.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:069fe2aeee02dfd2135d562d0663fe70fbb69d5eed6eb3389042a7e963b54de8"}, - {file = "grpcio-1.60.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb0af13433dbbd1c806e671d81ec75bd324af6ef75171fd7815ca3074fe32bfe"}, - {file = "grpcio-1.60.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2f44c32aef186bbba254129cea1df08a20be414144ac3bdf0e84b24e3f3b2e05"}, - {file = "grpcio-1.60.1-cp312-cp312-win32.whl", hash = "sha256:a212e5dea1a4182e40cd3e4067ee46be9d10418092ce3627475e995cca95de21"}, - {file = "grpcio-1.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:6e490fa5f7f5326222cb9f0b78f207a2b218a14edf39602e083d5f617354306f"}, - {file = "grpcio-1.60.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:4216e67ad9a4769117433814956031cb300f85edc855252a645a9a724b3b6594"}, - {file = "grpcio-1.60.1-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:73e14acd3d4247169955fae8fb103a2b900cfad21d0c35f0dcd0fdd54cd60367"}, - {file = "grpcio-1.60.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:6ecf21d20d02d1733e9c820fb5c114c749d888704a7ec824b545c12e78734d1c"}, - {file = "grpcio-1.60.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33bdea30dcfd4f87b045d404388469eb48a48c33a6195a043d116ed1b9a0196c"}, - {file = "grpcio-1.60.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53b69e79d00f78c81eecfb38f4516080dc7f36a198b6b37b928f1c13b3c063e9"}, - {file = "grpcio-1.60.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:39aa848794b887120b1d35b1b994e445cc028ff602ef267f87c38122c1add50d"}, - {file = "grpcio-1.60.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72153a0d2e425f45b884540a61c6639436ddafa1829a42056aa5764b84108b8e"}, - {file = "grpcio-1.60.1-cp37-cp37m-win_amd64.whl", hash = "sha256:50d56280b482875d1f9128ce596e59031a226a8b84bec88cb2bf76c289f5d0de"}, - {file = "grpcio-1.60.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:6d140bdeb26cad8b93c1455fa00573c05592793c32053d6e0016ce05ba267549"}, - {file = "grpcio-1.60.1-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:bc808924470643b82b14fe121923c30ec211d8c693e747eba8a7414bc4351a23"}, - {file = "grpcio-1.60.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:70c83bb530572917be20c21f3b6be92cd86b9aecb44b0c18b1d3b2cc3ae47df0"}, - {file = "grpcio-1.60.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b106bc52e7f28170e624ba61cc7dc6829566e535a6ec68528f8e1afbed1c41f"}, - {file = "grpcio-1.60.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30e980cd6db1088c144b92fe376747328d5554bc7960ce583ec7b7d81cd47287"}, - {file = "grpcio-1.60.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0c5807e9152eff15f1d48f6b9ad3749196f79a4a050469d99eecb679be592acc"}, - {file = "grpcio-1.60.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1c3dc536b3ee124e8b24feb7533e5c70b9f2ef833e3b2e5513b2897fd46763a"}, - {file = "grpcio-1.60.1-cp38-cp38-win32.whl", hash = "sha256:d7404cebcdb11bb5bd40bf94131faf7e9a7c10a6c60358580fe83913f360f929"}, - {file = "grpcio-1.60.1-cp38-cp38-win_amd64.whl", hash = "sha256:c8754c75f55781515a3005063d9a05878b2cfb3cb7e41d5401ad0cf19de14872"}, - {file = "grpcio-1.60.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:0250a7a70b14000fa311de04b169cc7480be6c1a769b190769d347939d3232a8"}, - {file = "grpcio-1.60.1-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:660fc6b9c2a9ea3bb2a7e64ba878c98339abaf1811edca904ac85e9e662f1d73"}, - {file = "grpcio-1.60.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:76eaaba891083fcbe167aa0f03363311a9f12da975b025d30e94b93ac7a765fc"}, - {file = "grpcio-1.60.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d97c65ea7e097056f3d1ead77040ebc236feaf7f71489383d20f3b4c28412a"}, - {file = "grpcio-1.60.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb2a2911b028f01c8c64d126f6b632fcd8a9ac975aa1b3855766c94e4107180"}, - {file = "grpcio-1.60.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5a1ebbae7e2214f51b1f23b57bf98eeed2cf1ba84e4d523c48c36d5b2f8829ff"}, - {file = "grpcio-1.60.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a66f4d2a005bc78e61d805ed95dedfcb35efa84b7bba0403c6d60d13a3de2d6"}, - {file = "grpcio-1.60.1-cp39-cp39-win32.whl", hash = "sha256:8d488fbdbf04283f0d20742b64968d44825617aa6717b07c006168ed16488804"}, - {file = "grpcio-1.60.1-cp39-cp39-win_amd64.whl", hash = "sha256:61b7199cd2a55e62e45bfb629a35b71fc2c0cb88f686a047f25b1112d3810904"}, - {file = "grpcio-1.60.1.tar.gz", hash = "sha256:dd1d3a8d1d2e50ad9b59e10aa7f07c7d1be2b367f3f2d33c5fade96ed5460962"}, + {file = "grpcio-1.65.4-cp310-cp310-linux_armv7l.whl", hash = "sha256:0e85c8766cf7f004ab01aff6a0393935a30d84388fa3c58d77849fcf27f3e98c"}, + {file = "grpcio-1.65.4-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:e4a795c02405c7dfa8affd98c14d980f4acea16ea3b539e7404c645329460e5a"}, + {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d7b984a8dd975d949c2042b9b5ebcf297d6d5af57dcd47f946849ee15d3c2fb8"}, + {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644a783ce604a7d7c91412bd51cf9418b942cf71896344b6dc8d55713c71ce82"}, + {file = "grpcio-1.65.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5764237d751d3031a36fafd57eb7d36fd2c10c658d2b4057c516ccf114849a3e"}, + {file = "grpcio-1.65.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ee40d058cf20e1dd4cacec9c39e9bce13fedd38ce32f9ba00f639464fcb757de"}, + {file = "grpcio-1.65.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4482a44ce7cf577a1f8082e807a5b909236bce35b3e3897f839f2fbd9ae6982d"}, + {file = "grpcio-1.65.4-cp310-cp310-win32.whl", hash = "sha256:66bb051881c84aa82e4f22d8ebc9d1704b2e35d7867757f0740c6ef7b902f9b1"}, + {file = "grpcio-1.65.4-cp310-cp310-win_amd64.whl", hash = "sha256:870370524eff3144304da4d1bbe901d39bdd24f858ce849b7197e530c8c8f2ec"}, + {file = "grpcio-1.65.4-cp311-cp311-linux_armv7l.whl", hash = "sha256:85e9c69378af02e483bc626fc19a218451b24a402bdf44c7531e4c9253fb49ef"}, + {file = "grpcio-1.65.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2bd672e005afab8bf0d6aad5ad659e72a06dd713020554182a66d7c0c8f47e18"}, + {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:abccc5d73f5988e8f512eb29341ed9ced923b586bb72e785f265131c160231d8"}, + {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:886b45b29f3793b0c2576201947258782d7e54a218fe15d4a0468d9a6e00ce17"}, + {file = "grpcio-1.65.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be952436571dacc93ccc7796db06b7daf37b3b56bb97e3420e6503dccfe2f1b4"}, + {file = "grpcio-1.65.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8dc9ddc4603ec43f6238a5c95400c9a901b6d079feb824e890623da7194ff11e"}, + {file = "grpcio-1.65.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ade1256c98cba5a333ef54636095f2c09e6882c35f76acb04412f3b1aa3c29a5"}, + {file = "grpcio-1.65.4-cp311-cp311-win32.whl", hash = "sha256:280e93356fba6058cbbfc6f91a18e958062ef1bdaf5b1caf46c615ba1ae71b5b"}, + {file = "grpcio-1.65.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2b819f9ee27ed4e3e737a4f3920e337e00bc53f9e254377dd26fc7027c4d558"}, + {file = "grpcio-1.65.4-cp312-cp312-linux_armv7l.whl", hash = "sha256:926a0750a5e6fb002542e80f7fa6cab8b1a2ce5513a1c24641da33e088ca4c56"}, + {file = "grpcio-1.65.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2a1d4c84d9e657f72bfbab8bedf31bdfc6bfc4a1efb10b8f2d28241efabfaaf2"}, + {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:17de4fda50967679677712eec0a5c13e8904b76ec90ac845d83386b65da0ae1e"}, + {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dee50c1b69754a4228e933696408ea87f7e896e8d9797a3ed2aeed8dbd04b74"}, + {file = "grpcio-1.65.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74c34fc7562bdd169b77966068434a93040bfca990e235f7a67cdf26e1bd5c63"}, + {file = "grpcio-1.65.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:24a2246e80a059b9eb981e4c2a6d8111b1b5e03a44421adbf2736cc1d4988a8a"}, + {file = "grpcio-1.65.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18c10f0d054d2dce34dd15855fcca7cc44ec3b811139437543226776730c0f28"}, + {file = "grpcio-1.65.4-cp312-cp312-win32.whl", hash = "sha256:d72962788b6c22ddbcdb70b10c11fbb37d60ae598c51eb47ec019db66ccfdff0"}, + {file = "grpcio-1.65.4-cp312-cp312-win_amd64.whl", hash = "sha256:7656376821fed8c89e68206a522522317787a3d9ed66fb5110b1dff736a5e416"}, + {file = "grpcio-1.65.4-cp38-cp38-linux_armv7l.whl", hash = "sha256:4934077b33aa6fe0b451de8b71dabde96bf2d9b4cb2b3187be86e5adebcba021"}, + {file = "grpcio-1.65.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0cef8c919a3359847c357cb4314e50ed1f0cca070f828ee8f878d362fd744d52"}, + {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:a925446e6aa12ca37114840d8550f308e29026cdc423a73da3043fd1603a6385"}, + {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf53e6247f1e2af93657e62e240e4f12e11ee0b9cef4ddcb37eab03d501ca864"}, + {file = "grpcio-1.65.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdb34278e4ceb224c89704cd23db0d902e5e3c1c9687ec9d7c5bb4c150f86816"}, + {file = "grpcio-1.65.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e6cbdd107e56bde55c565da5fd16f08e1b4e9b0674851d7749e7f32d8645f524"}, + {file = "grpcio-1.65.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:626319a156b1f19513156a3b0dbfe977f5f93db63ca673a0703238ebd40670d7"}, + {file = "grpcio-1.65.4-cp38-cp38-win32.whl", hash = "sha256:3d1bbf7e1dd1096378bd83c83f554d3b93819b91161deaf63e03b7022a85224a"}, + {file = "grpcio-1.65.4-cp38-cp38-win_amd64.whl", hash = "sha256:a99e6dffefd3027b438116f33ed1261c8d360f0dd4f943cb44541a2782eba72f"}, + {file = "grpcio-1.65.4-cp39-cp39-linux_armv7l.whl", hash = "sha256:874acd010e60a2ec1e30d5e505b0651ab12eb968157cd244f852b27c6dbed733"}, + {file = "grpcio-1.65.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b07f36faf01fca5427d4aa23645e2d492157d56c91fab7e06fe5697d7e171ad4"}, + {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:b81711bf4ec08a3710b534e8054c7dcf90f2edc22bebe11c1775a23f145595fe"}, + {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88fcabc332a4aef8bcefadc34a02e9ab9407ab975d2c7d981a8e12c1aed92aa1"}, + {file = "grpcio-1.65.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9ba3e63108a8749994f02c7c0e156afb39ba5bdf755337de8e75eb685be244b"}, + {file = "grpcio-1.65.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8eb485801957a486bf5de15f2c792d9f9c897a86f2f18db8f3f6795a094b4bb2"}, + {file = "grpcio-1.65.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075f3903bc1749ace93f2b0664f72964ee5f2da5c15d4b47e0ab68e4f442c257"}, + {file = "grpcio-1.65.4-cp39-cp39-win32.whl", hash = "sha256:0a0720299bdb2cc7306737295d56e41ce8827d5669d4a3cd870af832e3b17c4d"}, + {file = "grpcio-1.65.4-cp39-cp39-win_amd64.whl", hash = "sha256:a146bc40fa78769f22e1e9ff4f110ef36ad271b79707577bf2a31e3e931141b9"}, + {file = "grpcio-1.65.4.tar.gz", hash = "sha256:2a4f476209acffec056360d3e647ae0e14ae13dcf3dfb130c227ae1c594cbe39"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.60.1)"] +protobuf = ["grpcio-tools (>=1.65.4)"] [[package]] name = "h11" @@ -559,13 +535,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.2" +version = "1.0.5" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, - {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, ] [package.dependencies] @@ -576,17 +552,17 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.23.0)"] +trio = ["trio (>=0.22.0,<0.26.0)"] [[package]] name = "httpx" -version = "0.26.0" +version = "0.27.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.26.0-py3-none-any.whl", hash = "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd"}, - {file = "httpx-0.26.0.tar.gz", hash = "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf"}, + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] @@ -615,41 +591,22 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.11.0" +version = "8.0.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b"}, - {file = "importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443"}, + {file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"}, + {file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - -[[package]] -name = "inflect" -version = "7.0.0" -description = "Correctly generate plurals, singular nouns, ordinals, indefinite articles; convert numbers to words" -optional = false -python-versions = ">=3.8" -files = [ - {file = "inflect-7.0.0-py3-none-any.whl", hash = "sha256:9544afed6182176e43955c44b1acdaed30f9b2b56c16d1fc5b222d98218b546e"}, - {file = "inflect-7.0.0.tar.gz", hash = "sha256:63da9325ad29da81ec23e055b41225795ab793b4ecb483be5dc1fa363fd4717e"}, -] - -[package.dependencies] -pydantic = ">=1.9.1" -typing-extensions = "*" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pygments", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "iniconfig" @@ -676,92 +633,81 @@ files = [ [package.dependencies] six = "*" -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - [[package]] name = "jaraco-collections" -version = "5.0.0" +version = "5.0.1" description = "Collection objects similar to those in stdlib by jaraco" optional = false python-versions = ">=3.8" files = [ - {file = "jaraco.collections-5.0.0-py3-none-any.whl", hash = "sha256:a693d06b12718656921d79ba4f665f905014da09b35aa3deef43be4f14fdaa0d"}, - {file = "jaraco.collections-5.0.0.tar.gz", hash = "sha256:1680e8d09f295f625c7ba926880175a26fdbe7092b4c76d198e30476b21cfe68"}, + {file = "jaraco.collections-5.0.1-py3-none-any.whl", hash = "sha256:b117bacf6b69013741e34b0d75ca9c14e2ab983ad1ab4a2e6188627beffea8ee"}, + {file = "jaraco.collections-5.0.1.tar.gz", hash = "sha256:808631b174b84a4e2a592490d62f62dfc15d8047a0f715726098dc43b81a6cfa"}, ] [package.dependencies] "jaraco.text" = "*" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jaraco-context" -version = "4.3.0" -description = "Context managers by jaraco" +version = "5.3.0" +description = "Useful decorators and context managers" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "jaraco.context-4.3.0-py3-none-any.whl", hash = "sha256:5d9e95ca0faa78943ed66f6bc658dd637430f16125d86988e77844c741ff2f11"}, - {file = "jaraco.context-4.3.0.tar.gz", hash = "sha256:4dad2404540b936a20acedec53355bdaea223acb88fd329fa6de9261c941566e"}, + {file = "jaraco.context-5.3.0-py3-none-any.whl", hash = "sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266"}, + {file = "jaraco.context-5.3.0.tar.gz", hash = "sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2"}, ] +[package.dependencies] +"backports.tarfile" = {version = "*", markers = "python_version < \"3.12\""} + [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["portend", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jaraco-functools" -version = "4.0.0" +version = "4.0.2" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.8" files = [ - {file = "jaraco.functools-4.0.0-py3-none-any.whl", hash = "sha256:daf276ddf234bea897ef14f43c4e1bf9eefeac7b7a82a4dd69228ac20acff68d"}, - {file = "jaraco.functools-4.0.0.tar.gz", hash = "sha256:c279cb24c93d694ef7270f970d499cab4d3813f4e08273f95398651a634f0925"}, + {file = "jaraco.functools-4.0.2-py3-none-any.whl", hash = "sha256:c9d16a3ed4ccb5a889ad8e0b7a343401ee5b2a71cee6ed192d3f68bc351e94e3"}, + {file = "jaraco_functools-4.0.2.tar.gz", hash = "sha256:3460c74cd0d32bf82b9576bbb3527c4364d5b27a21f5158a62aed6c4b42e23f5"}, ] [package.dependencies] more-itertools = "*" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.classes", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["jaraco.classes", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jaraco-text" -version = "3.12.0" +version = "4.0.0" description = "Module for text manipulation" optional = false python-versions = ">=3.8" files = [ - {file = "jaraco.text-3.12.0-py3-none-any.whl", hash = "sha256:1472f5a04a4cf77a0a3867495c24736ed9dc406070634934455323fa3a2c63de"}, - {file = "jaraco.text-3.12.0.tar.gz", hash = "sha256:389e25c8d4b32e9715bf530596fab0f5cd3aa47296e43969392e18a541af592c"}, + {file = "jaraco.text-4.0.0-py3-none-any.whl", hash = "sha256:08de508939b5e681b14cdac2f1f73036cd97f6f8d7b25e96b8911a9a428ca0d1"}, + {file = "jaraco_text-4.0.0.tar.gz", hash = "sha256:5b71fecea69ab6f939d4c906c04fee1eda76500d1641117df6ec45b865f10db0"}, ] [package.dependencies] autocommand = "*" -inflect = "*" "jaraco.context" = ">=4.1" "jaraco.functools" = "*" more-itertools = "*" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pathlib2", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +inflect = ["inflect"] +test = ["pathlib2", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jinja2" @@ -938,15 +884,29 @@ html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] source = ["Cython (>=3.0.10)"] +[[package]] +name = "lxml-stubs" +version = "0.5.1" +description = "Type annotations for the lxml package" +optional = false +python-versions = "*" +files = [ + {file = "lxml-stubs-0.5.1.tar.gz", hash = "sha256:e0ec2aa1ce92d91278b719091ce4515c12adc1d564359dfaf81efa7d4feab79d"}, + {file = "lxml_stubs-0.5.1-py3-none-any.whl", hash = "sha256:1f689e5dbc4b9247cb09ae820c7d34daeb1fdbd1db06123814b856dae7787272"}, +] + +[package.extras] +test = ["coverage[toml] (>=7.2.5)", "mypy (>=1.2.0)", "pytest (>=7.3.0)", "pytest-mypy-plugins (>=1.10.1)"] + [[package]] name = "markdown" -version = "3.5.2" +version = "3.6" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "Markdown-3.5.2-py3-none-any.whl", hash = "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd"}, - {file = "Markdown-3.5.2.tar.gz", hash = "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8"}, + {file = "Markdown-3.6-py3-none-any.whl", hash = "sha256:48f276f4d8cfb8ce6527c8f79e2ee29708508bf4d40aa410fbc3b4ee832c850f"}, + {file = "Markdown-3.6.tar.gz", hash = "sha256:ed4f41f6daecbeeb96e576ce414c41d2d876daa9a16cb35fa8ed8c2ddfad0224"}, ] [package.extras] @@ -1179,13 +1139,13 @@ mkdocs = ">=1.2" [[package]] name = "mkdocstrings" -version = "0.25.1" +version = "0.25.2" description = "Automatic documentation from sources, for MkDocs." optional = false python-versions = ">=3.8" files = [ - {file = "mkdocstrings-0.25.1-py3-none-any.whl", hash = "sha256:da01fcc2670ad61888e8fe5b60afe9fee5781017d67431996832d63e887c2e51"}, - {file = "mkdocstrings-0.25.1.tar.gz", hash = "sha256:c3a2515f31577f311a9ee58d089e4c51fc6046dbd9e9b4c3de4c3194667fe9bf"}, + {file = "mkdocstrings-0.25.2-py3-none-any.whl", hash = "sha256:9e2cda5e2e12db8bb98d21e3410f3f27f8faab685a24b03b06ba7daa5b92abfc"}, + {file = "mkdocstrings-0.25.2.tar.gz", hash = "sha256:5cf57ad7f61e8be3111a2458b4e49c2029c9cb35525393b179f9c916ca8042dc"}, ] [package.dependencies] @@ -1220,13 +1180,13 @@ mkdocstrings = ">=0.25" [[package]] name = "more-itertools" -version = "10.2.0" +version = "10.3.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.8" files = [ - {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, - {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, + {file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"}, + {file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"}, ] [[package]] @@ -1240,6 +1200,52 @@ files = [ {file = "mutagen-1.47.0.tar.gz", hash = "sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99"}, ] +[[package]] +name = "mypy" +version = "1.11.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"}, + {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"}, + {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"}, + {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"}, + {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, + {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, + {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, + {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, + {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, + {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, + {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, + {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"}, + {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"}, + {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"}, + {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"}, + {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"}, + {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"}, + {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"}, + {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"}, + {file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"}, + {file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"}, + {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, + {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -1268,106 +1274,97 @@ pytz = ">=2022.7.1" [[package]] name = "opentelemetry-api" -version = "1.22.0" +version = "1.26.0" description = "OpenTelemetry Python API" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "opentelemetry_api-1.22.0-py3-none-any.whl", hash = "sha256:43621514301a7e9f5d06dd8013a1b450f30c2e9372b8e30aaeb4562abf2ce034"}, - {file = "opentelemetry_api-1.22.0.tar.gz", hash = "sha256:15ae4ca925ecf9cfdfb7a709250846fbb08072260fca08ade78056c502b86bed"}, + {file = "opentelemetry_api-1.26.0-py3-none-any.whl", hash = "sha256:7d7ea33adf2ceda2dd680b18b1677e4152000b37ca76e679da71ff103b943064"}, + {file = "opentelemetry_api-1.26.0.tar.gz", hash = "sha256:2bd639e4bed5b18486fef0b5a520aaffde5a18fc225e808a1ac4df363f43a1ce"}, ] [package.dependencies] deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<7.0" +importlib-metadata = ">=6.0,<=8.0.0" [[package]] name = "opentelemetry-exporter-otlp" -version = "1.22.0" +version = "1.26.0" description = "OpenTelemetry Collector Exporters" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "opentelemetry_exporter_otlp-1.22.0-py3-none-any.whl", hash = "sha256:cb03a1cbf300e12b47690858be13dd26fe2f60b2610204959f3497cd6645e3a1"}, - {file = "opentelemetry_exporter_otlp-1.22.0.tar.gz", hash = "sha256:309a7d4dc67602801f15818e110ce452e78989886aaab5d37e7cf7f55f1d3d27"}, + {file = "opentelemetry_exporter_otlp-1.26.0-py3-none-any.whl", hash = "sha256:f839989f54bda85ee33c5dae033c44dcec9ccbb0dafc6a43d585df44da1d2036"}, + {file = "opentelemetry_exporter_otlp-1.26.0.tar.gz", hash = "sha256:cf0e093f080011951d9f97431a83869761e4d4ebe83a4195ee92d7806223299c"}, ] [package.dependencies] -opentelemetry-exporter-otlp-proto-grpc = "1.22.0" -opentelemetry-exporter-otlp-proto-http = "1.22.0" +opentelemetry-exporter-otlp-proto-grpc = "1.26.0" +opentelemetry-exporter-otlp-proto-http = "1.26.0" [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.22.0" +version = "1.26.0" description = "OpenTelemetry Protobuf encoding" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.22.0-py3-none-any.whl", hash = "sha256:3f2538bec5312587f8676c332b3747f54c89fe6364803a807e217af4603201fa"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.22.0.tar.gz", hash = "sha256:71ae2f81bc6d6fe408d06388826edc8933759b2ca3a97d24054507dc7cfce52d"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.26.0-py3-none-any.whl", hash = "sha256:ee4d8f8891a1b9c372abf8d109409e5b81947cf66423fd998e56880057afbc71"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.26.0.tar.gz", hash = "sha256:bdbe50e2e22a1c71acaa0c8ba6efaadd58882e5a5978737a44a4c4b10d304c92"}, ] [package.dependencies] -backoff = {version = ">=1.10.0,<3.0.0", markers = "python_version >= \"3.7\""} -opentelemetry-proto = "1.22.0" +opentelemetry-proto = "1.26.0" [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.22.0" +version = "1.26.0" description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "opentelemetry_exporter_otlp_proto_grpc-1.22.0-py3-none-any.whl", hash = "sha256:b5bcadc129272004316a455e9081216d3380c1fc2231a928ea6a70aa90e173fb"}, - {file = "opentelemetry_exporter_otlp_proto_grpc-1.22.0.tar.gz", hash = "sha256:1e0e5aa4bbabc74942f06f268deffd94851d12a8dc30b02527472ef1729fe5b1"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.26.0-py3-none-any.whl", hash = "sha256:e2be5eff72ebcb010675b818e8d7c2e7d61ec451755b8de67a140bc49b9b0280"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.26.0.tar.gz", hash = "sha256:a65b67a9a6b06ba1ec406114568e21afe88c1cdb29c464f2507d529eb906d8ae"}, ] [package.dependencies] -backoff = {version = ">=1.10.0,<3.0.0", markers = "python_version >= \"3.7\""} deprecated = ">=1.2.6" googleapis-common-protos = ">=1.52,<2.0" grpcio = ">=1.0.0,<2.0.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.22.0" -opentelemetry-proto = "1.22.0" -opentelemetry-sdk = ">=1.22.0,<1.23.0" - -[package.extras] -test = ["pytest-grpc"] +opentelemetry-exporter-otlp-proto-common = "1.26.0" +opentelemetry-proto = "1.26.0" +opentelemetry-sdk = ">=1.26.0,<1.27.0" [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.22.0" +version = "1.26.0" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "opentelemetry_exporter_otlp_proto_http-1.22.0-py3-none-any.whl", hash = "sha256:e002e842190af45b91dc55a97789d0b98e4308c88d886b16049ee90e17a4d396"}, - {file = "opentelemetry_exporter_otlp_proto_http-1.22.0.tar.gz", hash = "sha256:79ed108981ec68d5f7985355bca32003c2f3a5be1534a96d62d5861b758a82f4"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.26.0-py3-none-any.whl", hash = "sha256:ee72a87c48ec977421b02f16c52ea8d884122470e0be573905237b540f4ee562"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.26.0.tar.gz", hash = "sha256:5801ebbcf7b527377883e6cbbdda35ee712dc55114fff1e93dfee210be56c908"}, ] [package.dependencies] -backoff = {version = ">=1.10.0,<3.0.0", markers = "python_version >= \"3.7\""} deprecated = ">=1.2.6" googleapis-common-protos = ">=1.52,<2.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.22.0" -opentelemetry-proto = "1.22.0" -opentelemetry-sdk = ">=1.22.0,<1.23.0" +opentelemetry-exporter-otlp-proto-common = "1.26.0" +opentelemetry-proto = "1.26.0" +opentelemetry-sdk = ">=1.26.0,<1.27.0" requests = ">=2.7,<3.0" -[package.extras] -test = ["responses (==0.22.0)"] - [[package]] name = "opentelemetry-proto" -version = "1.22.0" +version = "1.26.0" description = "OpenTelemetry Python Proto" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "opentelemetry_proto-1.22.0-py3-none-any.whl", hash = "sha256:ce7188d22c75b6d0fe53e7fb58501613d0feade5139538e79dedd9420610fa0c"}, - {file = "opentelemetry_proto-1.22.0.tar.gz", hash = "sha256:9ec29169286029f17ca34ec1f3455802ffb90131642d2f545ece9a63e8f69003"}, + {file = "opentelemetry_proto-1.26.0-py3-none-any.whl", hash = "sha256:6c4d7b4d4d9c88543bcf8c28ae3f8f0448a753dc291c18c5390444c90b76a725"}, + {file = "opentelemetry_proto-1.26.0.tar.gz", hash = "sha256:c5c18796c0cab3751fc3b98dee53855835e90c0422924b484432ac852d93dc1e"}, ] [package.dependencies] @@ -1375,40 +1372,44 @@ protobuf = ">=3.19,<5.0" [[package]] name = "opentelemetry-sdk" -version = "1.22.0" +version = "1.26.0" description = "OpenTelemetry Python SDK" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "opentelemetry_sdk-1.22.0-py3-none-any.whl", hash = "sha256:a730555713d7c8931657612a88a141e3a4fe6eb5523d9e2d5a8b1e673d76efa6"}, - {file = "opentelemetry_sdk-1.22.0.tar.gz", hash = "sha256:45267ac1f38a431fc2eb5d6e0c0d83afc0b78de57ac345488aa58c28c17991d0"}, + {file = "opentelemetry_sdk-1.26.0-py3-none-any.whl", hash = "sha256:feb5056a84a88670c041ea0ded9921fca559efec03905dddeb3885525e0af897"}, + {file = "opentelemetry_sdk-1.26.0.tar.gz", hash = "sha256:c90d2868f8805619535c05562d699e2f4fb1f00dbd55a86dcefca4da6fa02f85"}, ] [package.dependencies] -opentelemetry-api = "1.22.0" -opentelemetry-semantic-conventions = "0.43b0" +opentelemetry-api = "1.26.0" +opentelemetry-semantic-conventions = "0.47b0" typing-extensions = ">=3.7.4" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.43b0" +version = "0.47b0" description = "OpenTelemetry Semantic Conventions" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "opentelemetry_semantic_conventions-0.43b0-py3-none-any.whl", hash = "sha256:291284d7c1bf15fdaddf309b3bd6d3b7ce12a253cec6d27144439819a15d8445"}, - {file = "opentelemetry_semantic_conventions-0.43b0.tar.gz", hash = "sha256:b9576fb890df479626fa624e88dde42d3d60b8b6c8ae1152ad157a8b97358635"}, + {file = "opentelemetry_semantic_conventions-0.47b0-py3-none-any.whl", hash = "sha256:4ff9d595b85a59c1c1413f02bba320ce7ea6bf9e2ead2b0913c4395c7bbc1063"}, + {file = "opentelemetry_semantic_conventions-0.47b0.tar.gz", hash = "sha256:a8d57999bbe3495ffd4d510de26a97dadc1dace53e0275001b2c1b2f67992a7e"}, ] +[package.dependencies] +deprecated = ">=1.2.6" +opentelemetry-api = "1.26.0" + [[package]] name = "packaging" -version = "23.2" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -1434,18 +1435,19 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" @@ -1482,164 +1484,36 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", [[package]] name = "protobuf" -version = "4.25.2" +version = "4.25.4" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6"}, - {file = "protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9"}, - {file = "protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d"}, - {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62"}, - {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020"}, - {file = "protobuf-4.25.2-cp38-cp38-win32.whl", hash = "sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61"}, - {file = "protobuf-4.25.2-cp38-cp38-win_amd64.whl", hash = "sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62"}, - {file = "protobuf-4.25.2-cp39-cp39-win32.whl", hash = "sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3"}, - {file = "protobuf-4.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0"}, - {file = "protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830"}, - {file = "protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e"}, -] - -[[package]] -name = "pydantic" -version = "2.6.1" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"}, - {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"}, -] - -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.16.2" -typing-extensions = ">=4.6.1" - -[package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.16.2" -description = "" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"}, - {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"}, - {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"}, - {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"}, - {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"}, - {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"}, - {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"}, - {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"}, - {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"}, - {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"}, - {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"}, - {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"}, - {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"}, - {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"}, - {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"}, - {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"}, - {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"}, - {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"}, - {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"}, - {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"}, - {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"}, - {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"}, - {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"}, - {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"}, - {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"}, - {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"}, - {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"}, - {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"}, - {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"}, - {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"}, - {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"}, - {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"}, - {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pydocstyle" -version = "6.3.0" -description = "Python docstring style checker" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, - {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, + {file = "protobuf-4.25.4-cp310-abi3-win32.whl", hash = "sha256:db9fd45183e1a67722cafa5c1da3e85c6492a5383f127c86c4c4aa4845867dc4"}, + {file = "protobuf-4.25.4-cp310-abi3-win_amd64.whl", hash = "sha256:ba3d8504116a921af46499471c63a85260c1a5fc23333154a427a310e015d26d"}, + {file = "protobuf-4.25.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:eecd41bfc0e4b1bd3fa7909ed93dd14dd5567b98c941d6c1ad08fdcab3d6884b"}, + {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4c8a70fdcb995dcf6c8966cfa3a29101916f7225e9afe3ced4395359955d3835"}, + {file = "protobuf-4.25.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:3319e073562e2515c6ddc643eb92ce20809f5d8f10fead3332f71c63be6a7040"}, + {file = "protobuf-4.25.4-cp38-cp38-win32.whl", hash = "sha256:7e372cbbda66a63ebca18f8ffaa6948455dfecc4e9c1029312f6c2edcd86c4e1"}, + {file = "protobuf-4.25.4-cp38-cp38-win_amd64.whl", hash = "sha256:051e97ce9fa6067a4546e75cb14f90cf0232dcb3e3d508c448b8d0e4265b61c1"}, + {file = "protobuf-4.25.4-cp39-cp39-win32.whl", hash = "sha256:90bf6fd378494eb698805bbbe7afe6c5d12c8e17fca817a646cd6a1818c696ca"}, + {file = "protobuf-4.25.4-cp39-cp39-win_amd64.whl", hash = "sha256:ac79a48d6b99dfed2729ccccee547b34a1d3d63289c71cef056653a846a2240f"}, + {file = "protobuf-4.25.4-py3-none-any.whl", hash = "sha256:bfbebc1c8e4793cfd58589acfb8a1026be0003e852b9da7db5a4285bde996978"}, + {file = "protobuf-4.25.4.tar.gz", hash = "sha256:0dc4a62cc4052a036ee2204d26fe4d835c62827c855c8a03f29fe6da146b380d"}, ] -[package.dependencies] -snowballstemmer = ">=2.2.0" - -[package.extras] -toml = ["tomli (>=1.2.3)"] - [[package]] name = "pygments" -version = "2.17.2" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] -plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] [[package]] @@ -1661,17 +1535,17 @@ tests = ["flaky", "pytest", "pytest-cov", "pytest-random-order", "pyyaml"] [[package]] name = "pymdown-extensions" -version = "10.7" +version = "10.9" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.7-py3-none-any.whl", hash = "sha256:6ca215bc57bc12bf32b414887a68b810637d039124ed9b2e5bd3325cbb2c050c"}, - {file = "pymdown_extensions-10.7.tar.gz", hash = "sha256:c0d64d5cf62566f59e6b2b690a4095c931107c250a8c8e1351c1de5f6b036deb"}, + {file = "pymdown_extensions-10.9-py3-none-any.whl", hash = "sha256:d323f7e90d83c86113ee78f3fe62fc9dee5f56b54d912660703ea1816fed5626"}, + {file = "pymdown_extensions-10.9.tar.gz", hash = "sha256:6ff740bcd99ec4172a938970d42b96128bdc9d4b9bcad72494f29921dc69b753"}, ] [package.dependencies] -markdown = ">=3.5" +markdown = ">=3.6" pyyaml = "*" [package.extras] @@ -1715,6 +1589,23 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +[[package]] +name = "pytest-mypy" +version = "0.10.3" +description = "Mypy static type checker plugin for Pytest" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-mypy-0.10.3.tar.gz", hash = "sha256:f8458f642323f13a2ca3e2e61509f7767966b527b4d8adccd5032c3e7b4fd3db"}, + {file = "pytest_mypy-0.10.3-py3-none-any.whl", hash = "sha256:7638d0d3906848fc1810cb2f5cc7fceb4cc5c98524aafcac58f28620e3102053"}, +] + +[package.dependencies] +attrs = ">=19.0" +filelock = ">=3.0" +mypy = {version = ">=0.900", markers = "python_version >= \"3.11\""} +pytest = {version = ">=6.2", markers = "python_version >= \"3.10\""} + [[package]] name = "pytest-random-order" version = "1.1.1" @@ -1746,13 +1637,13 @@ ruff = ">=0.0.242" [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] @@ -1811,7 +1702,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1879,104 +1769,90 @@ uritools = ">=4.0.0,<5.0.0" [[package]] name = "regex" -version = "2023.12.25" +version = "2024.7.24" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, - {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, - {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, - {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, - {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, - {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, - {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, - {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, - {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, - {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, - {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, - {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, - {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, - {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, - {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, - {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, - {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, + {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b0d3f567fafa0633aee87f08b9276c7062da9616931382993c03808bb68ce"}, + {file = "regex-2024.7.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3426de3b91d1bc73249042742f45c2148803c111d1175b283270177fdf669024"}, + {file = "regex-2024.7.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f273674b445bcb6e4409bf8d1be67bc4b58e8b46fd0d560055d515b8830063cd"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23acc72f0f4e1a9e6e9843d6328177ae3074b4182167e34119ec7233dfeccf53"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65fd3d2e228cae024c411c5ccdffae4c315271eee4a8b839291f84f796b34eca"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c414cbda77dbf13c3bc88b073a1a9f375c7b0cb5e115e15d4b73ec3a2fbc6f59"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7a89eef64b5455835f5ed30254ec19bf41f7541cd94f266ab7cbd463f00c41"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19c65b00d42804e3fbea9708f0937d157e53429a39b7c61253ff15670ff62cb5"}, + {file = "regex-2024.7.24-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7a5486ca56c8869070a966321d5ab416ff0f83f30e0e2da1ab48815c8d165d46"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f51f9556785e5a203713f5efd9c085b4a45aecd2a42573e2b5041881b588d1f"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a4997716674d36a82eab3e86f8fa77080a5d8d96a389a61ea1d0e3a94a582cf7"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c0abb5e4e8ce71a61d9446040c1e86d4e6d23f9097275c5bd49ed978755ff0fe"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:18300a1d78cf1290fa583cd8b7cde26ecb73e9f5916690cf9d42de569c89b1ce"}, + {file = "regex-2024.7.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:416c0e4f56308f34cdb18c3f59849479dde5b19febdcd6e6fa4d04b6c31c9faa"}, + {file = "regex-2024.7.24-cp310-cp310-win32.whl", hash = "sha256:fb168b5924bef397b5ba13aabd8cf5df7d3d93f10218d7b925e360d436863f66"}, + {file = "regex-2024.7.24-cp310-cp310-win_amd64.whl", hash = "sha256:6b9fc7e9cc983e75e2518496ba1afc524227c163e43d706688a6bb9eca41617e"}, + {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:382281306e3adaaa7b8b9ebbb3ffb43358a7bbf585fa93821300a418bb975281"}, + {file = "regex-2024.7.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4fdd1384619f406ad9037fe6b6eaa3de2749e2e12084abc80169e8e075377d3b"}, + {file = "regex-2024.7.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d974d24edb231446f708c455fd08f94c41c1ff4f04bcf06e5f36df5ef50b95a"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec4419a3fe6cf8a4795752596dfe0adb4aea40d3683a132bae9c30b81e8d73"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb563dd3aea54c797adf513eeec819c4213d7dbfc311874eb4fd28d10f2ff0f2"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45104baae8b9f67569f0f1dca5e1f1ed77a54ae1cd8b0b07aba89272710db61e"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:994448ee01864501912abf2bad9203bffc34158e80fe8bfb5b031f4f8e16da51"}, + {file = "regex-2024.7.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fac296f99283ac232d8125be932c5cd7644084a30748fda013028c815ba3364"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e37e809b9303ec3a179085415cb5f418ecf65ec98cdfe34f6a078b46ef823ee"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:01b689e887f612610c869421241e075c02f2e3d1ae93a037cb14f88ab6a8934c"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f6442f0f0ff81775eaa5b05af8a0ffa1dda36e9cf6ec1e0d3d245e8564b684ce"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:871e3ab2838fbcb4e0865a6e01233975df3a15e6fce93b6f99d75cacbd9862d1"}, + {file = "regex-2024.7.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c918b7a1e26b4ab40409820ddccc5d49871a82329640f5005f73572d5eaa9b5e"}, + {file = "regex-2024.7.24-cp311-cp311-win32.whl", hash = "sha256:2dfbb8baf8ba2c2b9aa2807f44ed272f0913eeeba002478c4577b8d29cde215c"}, + {file = "regex-2024.7.24-cp311-cp311-win_amd64.whl", hash = "sha256:538d30cd96ed7d1416d3956f94d54e426a8daf7c14527f6e0d6d425fcb4cca52"}, + {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fe4ebef608553aff8deb845c7f4f1d0740ff76fa672c011cc0bacb2a00fbde86"}, + {file = "regex-2024.7.24-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:74007a5b25b7a678459f06559504f1eec2f0f17bca218c9d56f6a0a12bfffdad"}, + {file = "regex-2024.7.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7df9ea48641da022c2a3c9c641650cd09f0cd15e8908bf931ad538f5ca7919c9"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1141a1dcc32904c47f6846b040275c6e5de0bf73f17d7a409035d55b76f289"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80c811cfcb5c331237d9bad3bea2c391114588cf4131707e84d9493064d267f9"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7214477bf9bd195894cf24005b1e7b496f46833337b5dedb7b2a6e33f66d962c"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d55588cba7553f0b6ec33130bc3e114b355570b45785cebdc9daed8c637dd440"}, + {file = "regex-2024.7.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:558a57cfc32adcf19d3f791f62b5ff564922942e389e3cfdb538a23d65a6b610"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a512eed9dfd4117110b1881ba9a59b31433caed0c4101b361f768e7bcbaf93c5"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:86b17ba823ea76256b1885652e3a141a99a5c4422f4a869189db328321b73799"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5eefee9bfe23f6df09ffb6dfb23809f4d74a78acef004aa904dc7c88b9944b05"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:731fcd76bbdbf225e2eb85b7c38da9633ad3073822f5ab32379381e8c3c12e94"}, + {file = "regex-2024.7.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eaef80eac3b4cfbdd6de53c6e108b4c534c21ae055d1dbea2de6b3b8ff3def38"}, + {file = "regex-2024.7.24-cp312-cp312-win32.whl", hash = "sha256:185e029368d6f89f36e526764cf12bf8d6f0e3a2a7737da625a76f594bdfcbfc"}, + {file = "regex-2024.7.24-cp312-cp312-win_amd64.whl", hash = "sha256:2f1baff13cc2521bea83ab2528e7a80cbe0ebb2c6f0bfad15be7da3aed443908"}, + {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:66b4c0731a5c81921e938dcf1a88e978264e26e6ac4ec96a4d21ae0354581ae0"}, + {file = "regex-2024.7.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:88ecc3afd7e776967fa16c80f974cb79399ee8dc6c96423321d6f7d4b881c92b"}, + {file = "regex-2024.7.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64bd50cf16bcc54b274e20235bf8edbb64184a30e1e53873ff8d444e7ac656b2"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb462f0e346fcf41a901a126b50f8781e9a474d3927930f3490f38a6e73b6950"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a82465ebbc9b1c5c50738536fdfa7cab639a261a99b469c9d4c7dcbb2b3f1e57"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68a8f8c046c6466ac61a36b65bb2395c74451df2ffb8458492ef49900efed293"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac8e84fff5d27420f3c1e879ce9929108e873667ec87e0c8eeb413a5311adfe"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba2537ef2163db9e6ccdbeb6f6424282ae4dea43177402152c67ef869cf3978b"}, + {file = "regex-2024.7.24-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:43affe33137fcd679bdae93fb25924979517e011f9dea99163f80b82eadc7e53"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:c9bb87fdf2ab2370f21e4d5636e5317775e5d51ff32ebff2cf389f71b9b13750"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:945352286a541406f99b2655c973852da7911b3f4264e010218bbc1cc73168f2"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:8bc593dcce679206b60a538c302d03c29b18e3d862609317cb560e18b66d10cf"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3f3b6ca8eae6d6c75a6cff525c8530c60e909a71a15e1b731723233331de4169"}, + {file = "regex-2024.7.24-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c51edc3541e11fbe83f0c4d9412ef6c79f664a3745fab261457e84465ec9d5a8"}, + {file = "regex-2024.7.24-cp38-cp38-win32.whl", hash = "sha256:d0a07763776188b4db4c9c7fb1b8c494049f84659bb387b71c73bbc07f189e96"}, + {file = "regex-2024.7.24-cp38-cp38-win_amd64.whl", hash = "sha256:8fd5afd101dcf86a270d254364e0e8dddedebe6bd1ab9d5f732f274fa00499a5"}, + {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0ffe3f9d430cd37d8fa5632ff6fb36d5b24818c5c986893063b4e5bdb84cdf24"}, + {file = "regex-2024.7.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25419b70ba00a16abc90ee5fce061228206173231f004437730b67ac77323f0d"}, + {file = "regex-2024.7.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33e2614a7ce627f0cdf2ad104797d1f68342d967de3695678c0cb84f530709f8"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33a0021893ede5969876052796165bab6006559ab845fd7b515a30abdd990dc"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04ce29e2c5fedf296b1a1b0acc1724ba93a36fb14031f3abfb7abda2806c1535"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b16582783f44fbca6fcf46f61347340c787d7530d88b4d590a397a47583f31dd"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:836d3cc225b3e8a943d0b02633fb2f28a66e281290302a79df0e1eaa984ff7c1"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:438d9f0f4bc64e8dea78274caa5af971ceff0f8771e1a2333620969936ba10be"}, + {file = "regex-2024.7.24-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:973335b1624859cb0e52f96062a28aa18f3a5fc77a96e4a3d6d76e29811a0e6e"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c5e69fd3eb0b409432b537fe3c6f44ac089c458ab6b78dcec14478422879ec5f"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fbf8c2f00904eaf63ff37718eb13acf8e178cb940520e47b2f05027f5bb34ce3"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2757ace61bc4061b69af19e4689fa4416e1a04840f33b441034202b5cd02d4"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:44fc61b99035fd9b3b9453f1713234e5a7c92a04f3577252b45feefe1b327759"}, + {file = "regex-2024.7.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:84c312cdf839e8b579f504afcd7b65f35d60b6285d892b19adea16355e8343c9"}, + {file = "regex-2024.7.24-cp39-cp39-win32.whl", hash = "sha256:ca5b2028c2f7af4e13fb9fc29b28d0ce767c38c7facdf64f6c2cd040413055f1"}, + {file = "regex-2024.7.24-cp39-cp39-win_amd64.whl", hash = "sha256:7c479f5ae937ec9985ecaf42e2e10631551d909f203e31308c12d703922742f9"}, + {file = "regex-2024.7.24.tar.gz", hash = "sha256:9cfd009eed1a46b27c14039ad5bbc5e71b6367c5b2e6d5f5da0ea91600817506"}, ] [[package]] @@ -2002,45 +1878,46 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.5.6" +version = "0.5.5" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.5.6-py3-none-linux_armv6l.whl", hash = "sha256:a0ef5930799a05522985b9cec8290b185952f3fcd86c1772c3bdbd732667fdcd"}, - {file = "ruff-0.5.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b652dc14f6ef5d1552821e006f747802cc32d98d5509349e168f6bf0ee9f8f42"}, - {file = "ruff-0.5.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:80521b88d26a45e871f31e4b88938fd87db7011bb961d8afd2664982dfc3641a"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9bc8f328a9f1309ae80e4d392836e7dbc77303b38ed4a7112699e63d3b066ab"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d394940f61f7720ad371ddedf14722ee1d6250fd8d020f5ea5a86e7be217daf"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111a99cdb02f69ddb2571e2756e017a1496c2c3a2aeefe7b988ddab38b416d36"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e395daba77a79f6dc0d07311f94cc0560375ca20c06f354c7c99af3bf4560c5d"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c476acb43c3c51e3c614a2e878ee1589655fa02dab19fe2db0423a06d6a5b1b6"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2ff8003f5252fd68425fd53d27c1f08b201d7ed714bb31a55c9ac1d4c13e2eb"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c94e084ba3eaa80c2172918c2ca2eb2230c3f15925f4ed8b6297260c6ef179ad"}, - {file = "ruff-0.5.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f77c1c3aa0669fb230b06fb24ffa3e879391a3ba3f15e3d633a752da5a3e670"}, - {file = "ruff-0.5.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f908148c93c02873210a52cad75a6eda856b2cbb72250370ce3afef6fb99b1ed"}, - {file = "ruff-0.5.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:563a7ae61ad284187d3071d9041c08019975693ff655438d8d4be26e492760bd"}, - {file = "ruff-0.5.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:94fe60869bfbf0521e04fd62b74cbca21cbc5beb67cbb75ab33fe8c174f54414"}, - {file = "ruff-0.5.6-py3-none-win32.whl", hash = "sha256:e6a584c1de6f8591c2570e171cc7ce482bb983d49c70ddf014393cd39e9dfaed"}, - {file = "ruff-0.5.6-py3-none-win_amd64.whl", hash = "sha256:d7fe7dccb1a89dc66785d7aa0ac283b2269712d8ed19c63af908fdccca5ccc1a"}, - {file = "ruff-0.5.6-py3-none-win_arm64.whl", hash = "sha256:57c6c0dd997b31b536bff49b9eee5ed3194d60605a4427f735eeb1f9c1b8d264"}, - {file = "ruff-0.5.6.tar.gz", hash = "sha256:07c9e3c2a8e1fe377dd460371c3462671a728c981c3205a5217291422209f642"}, + {file = "ruff-0.5.5-py3-none-linux_armv6l.whl", hash = "sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf"}, + {file = "ruff-0.5.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8"}, + {file = "ruff-0.5.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884"}, + {file = "ruff-0.5.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091"}, + {file = "ruff-0.5.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523"}, + {file = "ruff-0.5.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f"}, + {file = "ruff-0.5.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8"}, + {file = "ruff-0.5.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab"}, + {file = "ruff-0.5.5-py3-none-win32.whl", hash = "sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d"}, + {file = "ruff-0.5.5-py3-none-win_amd64.whl", hash = "sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445"}, + {file = "ruff-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6"}, + {file = "ruff-0.5.5.tar.gz", hash = "sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a"}, ] [[package]] name = "setuptools" -version = "70.0.0" +version = "72.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"}, - {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"}, + {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, + {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -2055,44 +1932,33 @@ files = [ [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, -] - -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -optional = false -python-versions = "*" -files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] name = "tempora" -version = "5.5.0" +version = "5.7.0" description = "Objects and routines pertaining to date and time (tempora)" optional = false python-versions = ">=3.8" files = [ - {file = "tempora-5.5.0-py3-none-any.whl", hash = "sha256:defd976c9144fc9f20432ed0d75926c1c48dd0c7c701b3493e02d5316598e8fe"}, - {file = "tempora-5.5.0.tar.gz", hash = "sha256:13e4fcc997d0509c3306d6841f03e9381b7e5e46b2bebfae9151af90085f0c26"}, + {file = "tempora-5.7.0-py3-none-any.whl", hash = "sha256:93dac0d33825e66c8314d7bd206b9ecb959075c8728bb05b9b050b2726d0442a"}, + {file = "tempora-5.7.0.tar.gz", hash = "sha256:888190a2dbe3255ff26dfa9fcecb25f4d38434c0f1943cd61de98bb41c410c50"}, ] [package.dependencies] "jaraco.functools" = ">=1.20" -pytz = "*" +python-dateutil = "*" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["backports.unittest-mock", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-freezer", "pytest-mypy (>=0.9.1)", "pytest-ruff", "types-pytz"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["backports.zoneinfo", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-freezer", "pytest-mypy", "pytest-ruff (>=0.2.1)", "types-python-dateutil", "tzdata"] [[package]] name = "text-unidecode" @@ -2105,26 +1971,51 @@ files = [ {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, ] +[[package]] +name = "types-pytz" +version = "2024.1.0.20240417" +description = "Typing stubs for pytz" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-pytz-2024.1.0.20240417.tar.gz", hash = "sha256:6810c8a1f68f21fdf0f4f374a432487c77645a0ac0b31de4bf4690cf21ad3981"}, + {file = "types_pytz-2024.1.0.20240417-py3-none-any.whl", hash = "sha256:8335d443310e2db7b74e007414e74c4f53b67452c0cb0d228ca359ccfba59659"}, +] + +[[package]] +name = "types-requests" +version = "2.32.0.20240712" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-requests-2.32.0.20240712.tar.gz", hash = "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358"}, + {file = "types_requests-2.32.0.20240712-py3-none-any.whl", hash = "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3"}, +] + +[package.dependencies] +urllib3 = ">=2" + [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] name = "uritools" -version = "4.0.2" +version = "4.0.3" description = "URI parsing, classification and composition" optional = false python-versions = ">=3.7" files = [ - {file = "uritools-4.0.2-py3-none-any.whl", hash = "sha256:607b15eae1e7b69a120f463a7d98f91a56671e1ab92aae13f8e1f25c017fe60e"}, - {file = "uritools-4.0.2.tar.gz", hash = "sha256:04df2b787d0eb76200e8319382a03562fbfe4741fd66c15506b08d3b8211d573"}, + {file = "uritools-4.0.3-py3-none-any.whl", hash = "sha256:bae297d090e69a0451130ffba6f2f1c9477244aa0a5543d66aed2d9f77d0dd9c"}, + {file = "uritools-4.0.3.tar.gz", hash = "sha256:ee06a182a9c849464ce9d5fa917539aacc8edd2a4924d1b7aabeeecabcae3bc2"}, ] [[package]] @@ -2146,40 +2037,43 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "watchdog" -version = "4.0.0" +version = "4.0.1" description = "Filesystem events monitoring" optional = false python-versions = ">=3.8" files = [ - {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"}, - {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"}, - {file = "watchdog-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8"}, - {file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"}, - {file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"}, - {file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"}, - {file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"}, - {file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"}, - {file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"}, - {file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, + {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, + {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, + {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, + {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, ] [package.extras] @@ -2300,20 +2194,20 @@ test = ["zope.testing"] [[package]] name = "zipp" -version = "3.19.1" +version = "3.19.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"}, - {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"}, + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, ] [package.extras] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "5ae48c689e3201b888913d0a6e50f849859f74f0ddd39e4d7d993f6e8e0e2be2" +content-hash = "73a4900a1b606dd95c21aa430270f5dd1828195c1ed9acf56b5b6bee269b07d8" diff --git a/pyproject.toml b/pyproject.toml index a44529b1..19c8bf2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ classifiers = [ ] readme = "README.md" packages = [ - { include = "nowplaying"}, + { include = "nowplaying" }, ] [tool.poetry.scripts] @@ -35,14 +35,6 @@ opentelemetry-exporter-otlp = "^1.18.0" opentelemetry-sdk = "^1.18.0" [tool.poetry.group.dev.dependencies] -black = ">=23.1,<25.0" -isort = "^5.12.0" -pydocstyle = "^6.1.1" -pytest = ">=7.2.3,<9.0.0" -pytest-cov = ">=4,<6" -pytest-random-order = "^1.1.0" -pytest-ruff = ">=0.4.0,<0.5" -ruff = ">=0.5.1,<0.5.7" faker = ">=26.0.0,<27.0.0" mkdocs = "^1.5.3" mkdocs-material = "^9.4.7" @@ -51,14 +43,19 @@ mkdocs-gen-files = "^0.5.0" mkdocs-literate-nav = "^0.6.1" mkdocs-section-index = "^0.3.8" mkdocstrings-python = "^1.7.3" - -[tool.isort] -line_length = 120 -profile = "black" +pytest = ">=7.2.3,<9.0.0" +pytest-cov = ">=4,<6" +pytest-mypy = "^0.10.3" +pytest-random-order = "^1.1.0" +pytest-ruff = ">=0.4.1,<0.5" +ruff = ">=0.5.1,<0.5.6" +types-requests = "^2.31.0.20240310" +types-pytz = "^2024.1.0.20240203" +lxml-stubs = "^0.5.1" [tool.pytest.ini_options] minversion = "7.2" -addopts = "-ra -q --random-order --doctest-glob='*.md' --doctest-modules --cov=nowplaying --cov-fail-under=100 --ruff --ignore docs/" +addopts = "-ra -q --random-order --doctest-glob='*.md' --doctest-modules --cov=nowplaying --cov-fail-under=100 --ruff --ruff-format --mypy --ignore docs/" filterwarnings = [ "ignore::DeprecationWarning:cairosvg", "ignore::DeprecationWarning:cherrypy", @@ -67,6 +64,13 @@ filterwarnings = [ "ignore::DeprecationWarning:pkg_resources", ] +[tool.ruff] +extend-exclude = [ + "nowplaying/misc/saemubox.py", + "nowplaying/track/observers/scrobbler.py", + "nowplaying/track/observers/dab_audio_companion.py", +] + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..fd15985d --- /dev/null +++ b/ruff.toml @@ -0,0 +1,78 @@ +# [ruff](https://docs.astral.sh/ruff/) config +# +# templated with https://github.com/radiorabe/backstage-software-templates + +extend = "./pyproject.toml" + +[lint] +select = [ + "F", # pyflakes + "E", # pycodestyle errors + "I", # isort + "C90", # mccabe + "N", # pep8-naming + "D", # pydocstyle + "UP", # pyupgrade + "ANN", # flake8-annotations + "ASYNC", # flake8-async + "S", # flake8-bandit + "BLE", # flake8-blind-exception + "FBT", # flake8-boolean-trap + "B", # flake8-bugbear + "A", # flake8-builtins + "COM", # flake8-commas + "C4", # flake8-comprehensions + "DTZ", # flake8-datetimez + "T10", # flake8-debugger + "EM", # flake8-errmsg + "EXE", # flake8-executable + "FA", # flake8-future-annotations + "ISC", # flake8-implicit-str-concat + "ICN", # flake8-import-conventions + "G", # flake8-logging-format + "INP", # flake8-no-pep420 + "PIE", # flake8-pie + "T20", # flake8-print + "PYI", # flake8-pyi + "PT", # flake8-pytest-style + "Q", # flake8-quotes + "RSE", # flake8-raise + "RET", # flake8-return + "SLF", # flake8-self + "SLOT", # flake8-slots + "SIM", # flake8-simplify + "TID", # flake8-tidy-imports + "TCH", # flake8-type-checking + "INT", # flake8-gettext + "ARG", # flake8-unused-arguments + "PTH", # flake8-use-pathlib + "TD", # flake8-todos + "ERA", # eradicate + "PGH", # pygrep-hooks + "PL", # Pylint + "TRY", # tryceratops + "PERF", # Perflint + "RUF", # ruff specific rules +] +ignore = [ + "D203", # we prefer blank-line-before-class (D211) for black compat + "D213", # we prefer multi-line-summary-first-line (D212) + "COM812", # ignore due to conflict with formatter + "ISC001", # ignore due to conflict with formatter +] + +[lint.per-file-ignores] +"tests/**/*.py" = [ + "D", # pydocstyle is optional for tests + "ANN", # flake8-annotations are optional for tests + "S101", # assert is allow in tests + "S108", # /tmp is allowed in tests since it's expected to be mocked + "DTZ001", # tests often run in UTC + "INP001", # tests do not need a dunder init +] +"**/__init__.py" = [ + "D104", # dunder init does not need a docstring because it might be empty +] +"docs/gen_ref_pages.py" = [ + "INP001", # mkdocs does not need a dunder init +] diff --git a/tests/conftest.py b/tests/conftest.py index de10fbb0..72488e8f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,9 @@ from werkzeug.wrappers import Response from nowplaying.api import ApiServer +from nowplaying.show.show import Show +from nowplaying.track.observers.base import TrackObserver +from nowplaying.track.track import Track class AuthenticatedClient(Client): @@ -45,19 +48,64 @@ def fixture_users(user, password): @pytest.fixture(name="options") def fixture_options(users): return SimpleNamespace( - **{ - "apiAuthUsers": users, - } + api_auth_users=users, ) @pytest.fixture(name="unauthenticated_client") def fixture_unauthenticated_client(options): """Create a test client.""" - yield Client(ApiServer(options, event_queue=Queue()), Response) + return Client(ApiServer(options, event_queue=Queue()), Response) @pytest.fixture(name="client") def fixture_client(options, user, password): """Create a test client.""" - yield AuthenticatedClient(ApiServer(options, event_queue=Queue()), user, password) + return AuthenticatedClient(ApiServer(options, event_queue=Queue()), user, password) + + +def new_show(name="Hairmare Traveling Medicine Show"): + s = Show() + s.set_name(name) + return s + + +@pytest.fixture() +def show_factory(): + """Return a method to help creating new show objects for tests.""" + return new_show + + +def new_track( + artist="Hairmare and the Band", + title="An Ode to legacy Python Code", + album="Live at the Refactoring Club", + duration=128, +): + t = Track() + t.set_artist(artist) + t.set_title(title) + t.set_album(album) + t.set_duration(duration) + return t + + +@pytest.fixture() +def track_factory(): + """Return a method to help creating new track objects for tests.""" + return new_track + + +class DummyObserver(TrackObserver): + """Shunt class for testing the abstract TrackObserver.""" + + def track_started(self, track): + pass + + def track_finished(self, track): + pass + + +@pytest.fixture() +def dummy_observer(): + return DummyObserver() diff --git a/tests/test_api.py b/tests/test_api.py index e53805d9..d99e5aec 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -21,18 +21,16 @@ def test_run_server_with_debug(mock_run_simple, users): mock_run_simple.side_effect = None options = SimpleNamespace( - **{ - "apiBindAddress": "0.0.0.0", - "apiPort": 8080, - "apiAuthUsers": users, - "debug": True, - } + api_bind_address="127.0.0.1", + api_port=8080, + api_auth_users=users, + debug=True, ) server = ApiServer(options, event_queue=Queue()) server.run_server() mock_run_simple.assert_called_once_with( - options.apiBindAddress, - options.apiPort, + options.api_bind_address, + options.api_port, mock.ANY, use_debugger=True, use_reloader=True, @@ -45,7 +43,7 @@ def test_stop_server(mock_server, mock_stop, options): """Test the stop_server function.""" api = ApiServer(options, event_queue=Queue()) - api._server = mock_server + api._server = mock_server # noqa: SLF001 api.stop_server() mock_server.stop.assert_called_once_with() @@ -59,7 +57,7 @@ def test_webhook_no_supported_header(client, content_type): if content_type: headers["Content-Type"] = content_type resp = client.post(_WEBHOOK_ENDPOINT, headers=headers, data="{}") - assert resp.status_code == 415 + assert resp.status_code == 415 # noqa: PLR2004 assert ( resp.data.decode("utf-8") == '"The server does not support the media type transmitted in the request."' @@ -67,18 +65,21 @@ def test_webhook_no_supported_header(client, content_type): @pytest.mark.parametrize( - "content_type", [_CONTENT_TYPE_JSON, _CONTENT_TYPE_CLOUDEVENTS] + "content_type", + [_CONTENT_TYPE_JSON, _CONTENT_TYPE_CLOUDEVENTS], ) def test_webhook_invalid_body(client, content_type): """Test the webhook function with invalid JSON.""" body = "invalid-json" resp = client.post( - _WEBHOOK_ENDPOINT, data=body, headers={"Content-Type": content_type} + _WEBHOOK_ENDPOINT, + data=body, + headers={"Content-Type": content_type}, ) - assert resp.status_code == 400 + assert resp.status_code == 400 # noqa: PLR2004 assert resp.data.decode("utf-8") == json.dumps( "Failed to read specversion from both headers and data. " - "The following can not be parsed as json: b'invalid-json'" + "The following can not be parsed as json: b'invalid-json'", ) @@ -91,19 +92,21 @@ def test_webhook_invalid_id_in_payload(client): "source": "https://rabe.ch", # a RaBe CRID must use "rabe.ch" so this is invalid "id": "crid://example.com/v1#t=code=19930301T131200.00Z", - } + }, ) resp = client.post( - _WEBHOOK_ENDPOINT, data=body, headers={"Content-Type": _CONTENT_TYPE_JSON} + _WEBHOOK_ENDPOINT, + data=body, + headers={"Content-Type": _CONTENT_TYPE_JSON}, ) - assert resp.status_code == 400 + assert resp.status_code == 400 # noqa: PLR2004 assert resp.data.decode("utf-8") == json.dumps( - "CRID 'crid://example.com/v1#t=code=19930301T131200.00Z' is not a RaBe CRID" + "CRID 'crid://example.com/v1#t=code=19930301T131200.00Z' is not a RaBe CRID", ) @pytest.mark.parametrize( - "content_type,body,expected_status", + ("content_type", "body", "expected_status"), [ ( _CONTENT_TYPE_JSON, @@ -125,14 +128,17 @@ def test_webhook_invalid_id_in_payload(client): def test_webhook_invalid_event(client, content_type, body, expected_status): """Test the webhook function.""" resp = client.post( - _WEBHOOK_ENDPOINT, data=json.dumps(body), headers={"Content-Type": content_type} + _WEBHOOK_ENDPOINT, + data=json.dumps(body), + headers={"Content-Type": content_type}, ) - assert resp.status_code == 400 + assert resp.status_code == 400 # noqa: PLR2004 assert expected_status in resp.data.decode("utf-8") @pytest.mark.parametrize( - "content_type", [_CONTENT_TYPE_JSON, _CONTENT_TYPE_CLOUDEVENTS] + "content_type", + [_CONTENT_TYPE_JSON, _CONTENT_TYPE_CLOUDEVENTS], ) def test_webhook_valid_event(client, content_type): """Test the webhook function.""" @@ -142,13 +148,15 @@ def test_webhook_valid_event(client, content_type): "type": "ch.rabe.api.events.track.v1.trackStarted", "source": "https://rabe.ch", "id": "crid://rabe.ch/v1#t=clock=19930301T131200.00Z", - } + }, ) assert client.application.event_queue.qsize() == 0 resp = client.post( - _WEBHOOK_ENDPOINT, data=body, headers={"Content-Type": content_type} + _WEBHOOK_ENDPOINT, + data=body, + headers={"Content-Type": content_type}, ) - assert resp.status_code == 200 + assert resp.status_code == 200 # noqa: PLR2004 assert resp.status == "200 Event Received" assert client.application.event_queue.qsize() == 1 event = client.application.event_queue.get() @@ -160,7 +168,9 @@ def test_webhook_valid_event(client, content_type): def test_webhook_auth_fail(unauthenticated_client): """Test the webhook function.""" resp = unauthenticated_client.post( - _WEBHOOK_ENDPOINT, data="{}", headers={"Content-Type": _CONTENT_TYPE_JSON} + _WEBHOOK_ENDPOINT, + data="{}", + headers={"Content-Type": _CONTENT_TYPE_JSON}, ) - assert resp.status_code == 401 + assert resp.status_code == 401 # noqa: PLR2004 assert resp.status == "401 UNAUTHORIZED" diff --git a/tests/test_daemon.py b/tests/test_daemon.py index e2118eb8..35fb7e92 100644 --- a/tests/test_daemon.py +++ b/tests/test_daemon.py @@ -35,10 +35,10 @@ def test_signal_handler(mock_sys_exit, options): with patch.object(SaemuBox, "__init__", lambda *_: None): nowplaying_daemon = NowPlayingDaemon(options) - nowplaying_daemon._api = Mock() + nowplaying_daemon._api = Mock() # noqa: SLF001 nowplaying_daemon.signal_handler(SIGINT, None) - nowplaying_daemon._api.stop_server.assert_called_once() + nowplaying_daemon._api.stop_server.assert_called_once() # noqa: SLF001 mock_sys_exit.assert_called_with(EX_OK) @@ -49,6 +49,6 @@ def test__start_apiserver(mock_run_server, options): with patch.object(SaemuBox, "__init__", lambda *_: None): daemon = NowPlayingDaemon(options) - daemon._start_apiserver() + daemon._start_apiserver() # noqa: SLF001 mock_run_server.assert_called_with() diff --git a/tests/test_input_handler.py b/tests/test_input_handler.py index cfbcdb07..72547ed8 100644 --- a/tests/test_input_handler.py +++ b/tests/test_input_handler.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from queue import Queue from cloudevents.http.event import CloudEvent @@ -12,20 +14,20 @@ def __init__(self): self.event_queue = Queue() self.update_call = None - def update(self, saemubox_id: int, event: CloudEvent = None): + def update(self, saemubox_id: int, event: CloudEvent | None = None): self.update_call = (saemubox_id, event) - def handles(self, event: CloudEvent) -> bool: - return super().handles(event) + def handles(self, event: CloudEvent) -> bool: # noqa: ARG002 + return True def event(self, event: CloudEvent): - return super().event(event) + pass - def handle_id(self, saemubox_id: int, event: CloudEvent = None): - return super().handle_id(saemubox_id, event=event) + def handle_id(self, saemubox_id: int, event: CloudEvent | None = None): + pass - def handle(self, event: CloudEvent = None): - return super().handle(event) + def handle(self, event: CloudEvent | None = None): + pass def test_register_observer(): @@ -33,7 +35,7 @@ def test_register_observer(): handler = InputHandler() observer = ShuntInputObserver() handler.register_observer(observer) - assert observer in handler._observers + assert observer in handler._observers # noqa: SLF001 def test_remove_observer(): @@ -42,7 +44,7 @@ def test_remove_observer(): observer = ShuntInputObserver() handler.register_observer(observer) handler.remove_observer(observer) - assert observer not in handler._observers + assert observer not in handler._observers # noqa: SLF001 def test_update(): diff --git a/tests/test_input_observer.py b/tests/test_input_observer.py index 8b02f417..875df8a4 100644 --- a/tests/test_input_observer.py +++ b/tests/test_input_observer.py @@ -1,13 +1,13 @@ import pytest from cloudevents.http.event import CloudEvent -from isodate import parse_datetime +from isodate import parse_datetime # type: ignore[import-untyped] from nowplaying.input.observer import KlangbeckenInputObserver from nowplaying.track.handler import TrackEventHandler @pytest.mark.parametrize( - "source, expected", + ("source", "expected"), [ ("https://github/radiorabe/klangbecken", True), ("https://github/radiorabe/something-that-is-not-klangbecken", False), @@ -42,7 +42,6 @@ def test_klangbecken_input_observer_event(): data={"item.artist": "artist", "item.title": "title"}, ) observer.event(event) - # TODO assert something def test_klangbecken_input_observer_parse_event(): diff --git a/tests/test_input_observer_base.py b/tests/test_input_observer_base.py index 44899d52..0d450603 100644 --- a/tests/test_input_observer_base.py +++ b/tests/test_input_observer_base.py @@ -1,21 +1,28 @@ -from cloudevents.http.event import CloudEvent +from __future__ import annotations + +from typing import TYPE_CHECKING from nowplaying.input.observer import InputObserver +if TYPE_CHECKING: + from cloudevents.http.event import CloudEvent + class ShuntObserver(InputObserver): - def handles(self, event: CloudEvent): + def handles(self, _: CloudEvent): return True - def event(self, event: CloudEvent): - return super().event(event) + def event(self, event: CloudEvent): ... - def handle_id(self, saemubox_id: int, event: CloudEvent): + def handle_id( + self, + saemubox_id: int, # noqa: ARG002 + event: CloudEvent | None = None, # noqa: ARG002 + ) -> bool: return True - def handle(self, event: CloudEvent): + def handle(self, event: CloudEvent | None = None) -> None: # noqa: ARG002 self.handle_called = True - return super().event(event) def test_init(): diff --git a/tests/test_input_observer_klangbecken.py b/tests/test_input_observer_klangbecken.py index ae420ad8..8134f611 100644 --- a/tests/test_input_observer_klangbecken.py +++ b/tests/test_input_observer_klangbecken.py @@ -31,7 +31,8 @@ def test_init(): @patch("nowplaying.show.client.ShowClient.get_show_info") @pytest.mark.parametrize( - "saemubox_id,expected", [(1, True), (2, False), (0, False), (-1, False)] + ("saemubox_id", "expected"), + [(1, True), (2, False), (0, False), (-1, False)], ) def test_handle_id(mock_get_show_info, saemubox_id, expected, event: CloudEvent): show_url = "http://www.rabe.ch/klangbecken/" @@ -65,11 +66,12 @@ def test_handle(mock_get_show_info): def test_parse_event(event: CloudEvent): expected_track = Track() - expected_track.artist = "Peaches" - expected_track.title = "Fuck the Pain Away" + expected_track.set_artist("Peaches") + expected_track.set_title("Fuck the Pain Away") observer = KlangbeckenInputObserver( - "http://example.org/klangbecken/", "tests/fixtures/now-playing.xml" + "http://example.org/klangbecken/", + "tests/fixtures/now-playing.xml", ) track = observer.parse_event(event) diff --git a/tests/test_main.py b/tests/test_main.py index f3e3672a..cbe27a0c 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -23,7 +23,7 @@ def test_run( now_playing.run() mock_setup_otel.assert_called_once() - mock_setdefaulttimeout.assert_called_once_with(Options.socketDefaultTimeout) + mock_setdefaulttimeout.assert_called_once_with(Options.socket_default_timeout) mock_run_daemon.assert_called_once() @@ -36,7 +36,7 @@ def test_run_daemon(mock_daemon): now_playing = NowPlaying() now_playing.options = options - now_playing._run_daemon() + now_playing._run_daemon() # noqa: SLF001 mock_daemon.assert_called_once_with(options) mock_run.main.assert_called_once() diff --git a/tests/test_show_client.py b/tests/test_show_client.py index c6467189..26f143ae 100644 --- a/tests/test_show_client.py +++ b/tests/test_show_client.py @@ -2,6 +2,7 @@ import json from datetime import datetime, timedelta +from pathlib import Path from unittest.mock import Mock, patch import pytest @@ -16,7 +17,7 @@ def file_get_contents(filename: str) -> str: """Read a file and returns its contents.""" - with open(filename) as file: + with Path(filename).open() as file: return file.read() @@ -73,7 +74,7 @@ def test_lazy_update_with_show_set(mock_logger_debug): show_client.lazy_update() show_client.update.assert_not_called() mock_logger_debug.assert_called_once_with( - "Show still running, won't update show info" + "Show still running, won't update show info", ) @@ -82,17 +83,25 @@ def test_update(mock_requests_get): """Test :class:`ShowClient`'s :meth:`update` method.""" mock_requests_get.return_value.json = Mock( return_value=json.loads( - file_get_contents("tests/fixtures/cast_now_during_show.json") - ) + file_get_contents("tests/fixtures/cast_now_during_show.json"), + ), ) show_client = ShowClient(_BASE_URL) show_client.update() assert show_client.show.name == "Voice of Hindu Kush" assert show_client.show.starttime == datetime( - 2019, 1, 27, 13, tzinfo=pytz.timezone("UTC") + 2019, + 1, + 27, + 13, + tzinfo=pytz.timezone("UTC"), ) assert show_client.show.endtime == datetime( - 2319, 1, 27, 14, tzinfo=pytz.timezone("UTC") + 2319, + 1, + 27, + 14, + tzinfo=pytz.timezone("UTC"), ) assert show_client.show.url == "https://www.rabe.ch/stimme-der-kutuesch/" @@ -115,8 +124,8 @@ def test_update_no_url(mock_requests_get): """Test :class:`ShowClient`'s :meth:`update` method when no url is returned.""" mock_requests_get.return_value.json = Mock( return_value=json.loads( - file_get_contents("tests/fixtures/cast_now_no_url.json") - ) + file_get_contents("tests/fixtures/cast_now_no_url.json"), + ), ) show_client = ShowClient(_BASE_URL) show_client.update() @@ -125,7 +134,7 @@ def test_update_no_url(mock_requests_get): @patch("requests.get") @pytest.mark.parametrize( - "fixture,field", + ("fixture", "field"), [ ("cast_now_no_name", "name"), ("cast_now_no_end", "end time"), @@ -135,7 +144,7 @@ def test_update_no_url(mock_requests_get): def test_update_empty_field(mock_requests_get, fixture, field): """Test :class:`ShowClient`'s :meth:`update` method when a field is empty.""" mock_requests_get.return_value.json = Mock( - return_value=json.loads(file_get_contents(f"tests/fixtures/{fixture}.json")) + return_value=json.loads(file_get_contents(f"tests/fixtures/{fixture}.json")), ) show_client = ShowClient(_BASE_URL) with pytest.raises(ShowClientError) as info: @@ -148,8 +157,8 @@ def test_update_past_show(mock_requests_get): """Test :class:`ShowClient`'s :meth:`update` method when the show is in the past.""" mock_requests_get.return_value.json = Mock( return_value=json.loads( - file_get_contents("tests/fixtures/cast_now_past_show.json") - ) + file_get_contents("tests/fixtures/cast_now_past_show.json"), + ), ) show_client = ShowClient(_BASE_URL) with pytest.raises(ShowClientError) as info: @@ -165,8 +174,8 @@ def test_update_show_empty(mock_requests_get): """ mock_requests_get.return_value.json = Mock( return_value=json.loads( - file_get_contents("tests/fixtures/cast_now_show_empty.json") - ) + file_get_contents("tests/fixtures/cast_now_show_empty.json"), + ), ) show_client = ShowClient(_BASE_URL) show_client.update() @@ -179,8 +188,8 @@ def test_update_show_encoding_fix_in_name(mock_requests_get): """Test :class:`ShowClient`'s :meth:`update` for show name with encoding fix.""" mock_requests_get.return_value.json = Mock( return_value=json.loads( - file_get_contents("tests/fixtures/cast_now_show_encoding_fix.json") - ) + file_get_contents("tests/fixtures/cast_now_show_encoding_fix.json"), + ), ) show_client = ShowClient(_BASE_URL) show_client.update() @@ -192,16 +201,24 @@ def test_update_when_show_is_in_next_array(mock_requests_get): """Test :class:`ShowClient`'s :meth:`update` method.""" mock_requests_get.return_value.json = Mock( return_value=json.loads( - file_get_contents("tests/fixtures/cast_now_show_in_next.json") - ) + file_get_contents("tests/fixtures/cast_now_show_in_next.json"), + ), ) show_client = ShowClient(_BASE_URL) show_client.update() assert show_client.show.name == "Voice of Hindu Kush" assert show_client.show.starttime == datetime( - 2019, 1, 27, 13, tzinfo=pytz.timezone("UTC") + 2019, + 1, + 27, + 13, + tzinfo=pytz.timezone("UTC"), ) assert show_client.show.endtime == datetime( - 2319, 1, 27, 14, tzinfo=pytz.timezone("UTC") + 2319, + 1, + 27, + 14, + tzinfo=pytz.timezone("UTC"), ) assert show_client.show.url == "https://www.rabe.ch/stimme-der-kutuesch/" diff --git a/tests/test_track.py b/tests/test_track.py index 68fc13fe..79a89b7d 100644 --- a/tests/test_track.py +++ b/tests/test_track.py @@ -18,7 +18,7 @@ def test_init(): def test_artist(): """Test :class:`Track`'s :meth:`artist` property.""" track = Track() - assert track.artist is None + assert track.artist == "" assert not track.has_default_artist() track.set_artist("Test") assert track.artist == "Test" @@ -30,7 +30,7 @@ def test_artist(): def test_title(): """Test :class:`Track`'s :meth:`title` property.""" track = Track() - assert track.title is None + assert track.title == "" assert not track.has_default_title() track.set_title("Test Title") assert track.title == "Test Title" @@ -42,7 +42,7 @@ def test_title(): def test_album(): """Test :class:`Track`'s :meth:`album` property.""" track = Track() - assert track.album is None + assert track.album == "" track.set_album("Test Album") assert track.album == "Test Album" @@ -52,7 +52,7 @@ def test_track(): track = Track() assert track.track == 1 track.set_track(2) - assert track.track == 2 + assert track.track == 2 # noqa: PLR2004 with pytest.raises(TypeError): track.set_track("no strings allowed") with pytest.raises(TrackError): @@ -102,4 +102,4 @@ def test_duration(): def test_prettyprinting(): """Test :class:`Track`'s :meth:`__str__` method.""" track = Track() - assert "Track 'None'" in str(track) + assert "Track ''" in str(track) diff --git a/tests/test_track_observer_dabaudiocompanion.py b/tests/test_track_observer_dabaudiocompanion.py index 809a7834..7be429ed 100644 --- a/tests/test_track_observer_dabaudiocompanion.py +++ b/tests/test_track_observer_dabaudiocompanion.py @@ -1,5 +1,8 @@ """Tests for :class:`DabAudioCompanionTrackObserver`.""" +# TODO(hairmare): v3 drop support +# https://github.com/radiorabe/nowplaying/issues/179 + from unittest.mock import MagicMock, Mock, patch from nowplaying.track.observers.dab_audio_companion import ( @@ -15,7 +18,7 @@ def test_init(): dab_audio_companion_track_observer = DabAudioCompanionTrackObserver( options=DabAudioCompanionTrackObserver.Options( url=_BASE_URL, - ) + ), ) assert dab_audio_companion_track_observer.base_url == f"{_BASE_URL}/api/setDLS" @@ -25,8 +28,7 @@ def test_track_started(mock_requests_post, track_factory, show_factory): """Test :class:`DabAudioCompanionTrackObserver`'s :meth:`track_started` method.""" mock_requests_post.return_value.getcode = Mock(return_value=200) mock_requests_post.return_value.read = Mock( - # TODO: mock and test real return value - return_value="contents" + return_value="contents", ) track = track_factory() @@ -35,7 +37,7 @@ def test_track_started(mock_requests_post, track_factory, show_factory): dab_audio_companion_track_observer = DabAudioCompanionTrackObserver( options=DabAudioCompanionTrackObserver.Options( url=_BASE_URL, - ) + ), ) # assume that last frame was DL+ on startup so we always send # delete tags when a show w/o dl+ starts @@ -73,7 +75,7 @@ def test_track_started(mock_requests_post, track_factory, show_factory): ] expected.sort() results.sort() - assert all([a == b for a, b in zip(results, expected)]) + assert all([a == b for a, b in zip(results, expected)]) # noqa: C419 # once ITEM delete have been sent we send regular DLS again dab_audio_companion_track_observer.track_started(track) @@ -92,10 +94,8 @@ def test_track_started(mock_requests_post, track_factory, show_factory): @patch("urllib.request.urlopen") def test_track_started_plain(mock_urlopen, track_factory, show_factory): - # TODO v3 remove when we drop plain support cm = MagicMock() cm.getcode.return_value = 200 - # TODO: mock and test real return value cm.read.return_value = "contents" cm.__enter__.return_value = cm mock_urlopen.return_value = cm @@ -107,7 +107,7 @@ def test_track_started_plain(mock_urlopen, track_factory, show_factory): options=DabAudioCompanionTrackObserver.Options( url=_BASE_URL, dl_plus=False, - ) + ), ) # last frame cannot be dl+ since the feature is inactive assert not o.last_frame_was_dl_plus @@ -115,7 +115,7 @@ def test_track_started_plain(mock_urlopen, track_factory, show_factory): o.track_started(track) assert not o.last_frame_was_dl_plus mock_urlopen.assert_called_with( - "http://localhost:80/api/setDLS?dls=b%27Hairmare+and+the+Band%27+-+b%27An+Ode+to+legacy+Python+Code%27" + "http://localhost:80/api/setDLS?dls=b%27Hairmare+and+the+Band%27+-+b%27An+Ode+to+legacy+Python+Code%27", ) track = track_factory(artist="Radio Bern", title="Livestream") @@ -123,7 +123,7 @@ def test_track_started_plain(mock_urlopen, track_factory, show_factory): o.track_started(track) mock_urlopen.assert_called_with( - "http://localhost:80/api/setDLS?dls=b%27Radio+Bern%27+-+b%27Hairmare+Traveling+Medicine+Show%27" + "http://localhost:80/api/setDLS?dls=b%27Radio+Bern%27+-+b%27Hairmare+Traveling+Medicine+Show%27", ) @@ -132,6 +132,6 @@ def test_track_finished(): dab_audio_companion_track_observer = DabAudioCompanionTrackObserver( options=DabAudioCompanionTrackObserver.Options( url=_BASE_URL, - ) + ), ) assert dab_audio_companion_track_observer.track_finished(Track()) diff --git a/tests/test_track_observer_icecast.py b/tests/test_track_observer_icecast.py index 58adf637..b148730b 100644 --- a/tests/test_track_observer_icecast.py +++ b/tests/test_track_observer_icecast.py @@ -10,7 +10,7 @@ @pytest.mark.parametrize( - "kwargs,url,username,password,mount", + ("kwargs", "url", "username", "password", "mount"), [ ( {"url": "http://user:password@localhost:80/?mount=foo.mp3"}, @@ -57,8 +57,8 @@ def test_init(): options=IcecastTrackObserver.Options( url="http://localhost:80/?mount=foo.mp3", username="foo", - password="bar", - ) + password="bar", # noqa: S106 + ), ) assert icecast_track_observer.options.url == "http://localhost:80/" assert icecast_track_observer.options.mount == "foo.mp3" @@ -67,23 +67,23 @@ def test_init(): options=IcecastTrackObserver.Options( url="http://localhost:80/", username="foo", - password="bar", + password="bar", # noqa: S106 mount="foo.mp3", - ) + ), ) assert icecast_track_observer.options.url == "http://localhost:80/" assert icecast_track_observer.options.mount == "foo.mp3" # test for exception if mount is missing - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Missing required parameter mount"): IcecastTrackObserver.Options( url="http://localhost:80/", username="foo", - password="bar", + password="bar", # noqa: S106 ) # test for exception if password is missing - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Missing required parameter password "): IcecastTrackObserver.Options( url="http://localhost:80/?mount=foo.mp3", username="foo", @@ -95,7 +95,6 @@ def test_track_started(mock_requests_get, track_factory, show_factory): """Test :class:`IcecastTrackObserver`'s :meth:`track_started` method.""" mock_resp = MagicMock() mock_resp.getcode.return_value = 200 - # TODO: mock and test real return value mock_resp.read.return_value = "contents" mock_resp.__enter__.return_value = mock_resp mock_requests_get.return_value = mock_resp @@ -107,8 +106,8 @@ def test_track_started(mock_requests_get, track_factory, show_factory): options=IcecastTrackObserver.Options( url="http://localhost:80/?mount=foo.mp3", username="foo", - password="bar", - ) + password="bar", # noqa: S106 + ), ) icecast_track_observer.track_started(track) @@ -121,6 +120,7 @@ def test_track_started(mock_requests_get, track_factory, show_factory): "charset": "utf-8", "song": "Hairmare and the Band - An Ode to legacy Python Code", }, + timeout=60, ) track = track_factory(artist="Radio Bern", title="Livestream") track.show = show_factory() @@ -135,6 +135,7 @@ def test_track_started(mock_requests_get, track_factory, show_factory): "charset": "utf-8", "song": "Radio Bern - Hairmare Traveling Medicine Show", }, + timeout=60, ) # test for ignoring of failed requests @@ -150,6 +151,7 @@ def test_track_started(mock_requests_get, track_factory, show_factory): "charset": "utf-8", "song": "Radio Bern - Hairmare Traveling Medicine Show", }, + timeout=60, ) @@ -159,7 +161,7 @@ def test_track_finished(): options=IcecastTrackObserver.Options( url="http://localhost:80/?mount=foo.mp3", username="foo", - password="bar", - ) + password="bar", # noqa: S106 + ), ) - assert icecast_track_observer.track_finished(Track()) + icecast_track_observer.track_finished(Track()) diff --git a/tests/test_track_observer_smc_ftp.py b/tests/test_track_observer_smc_ftp.py index 1c172911..432ec144 100644 --- a/tests/test_track_observer_smc_ftp.py +++ b/tests/test_track_observer_smc_ftp.py @@ -12,8 +12,8 @@ def test_init(): options=SmcFtpTrackObserver.Options( hostname="hostname", username="username", - password="password", - ) + password="password", # noqa: S106 + ), ) @@ -30,10 +30,10 @@ def test_track_started(mock_ftp, track_factory, show_factory): options=SmcFtpTrackObserver.Options( hostname="hostname", username="username", - password="password", - ) + password="password", # noqa: S106 + ), ) - smc_ftp_track_observer._ftp_cls = mock_ftp + smc_ftp_track_observer._ftp_cls = mock_ftp # noqa: SLF001 smc_ftp_track_observer.track_started(track) mock_ftp.assert_called_once() mock_ftp_instance.assert_has_calls( @@ -51,7 +51,7 @@ def test_track_started(mock_ftp, track_factory, show_factory): ), call.quit(), call.close(), - ] + ], ) # test skipping short tracks @@ -79,7 +79,7 @@ def test_track_started(mock_ftp, track_factory, show_factory): call.storlines("STOR /dlplus/nowplaying.dls", ANY), call.quit(), call.close(), - ] + ], ) @@ -89,7 +89,7 @@ def test_track_finished(): options=SmcFtpTrackObserver.Options( hostname="hostname", username="username", - password="password", - ) + password="password", # noqa: S106 + ), ) - assert smc_ftp_track_observer.track_finished(Track()) + smc_ftp_track_observer.track_finished(Track()) diff --git a/tests/test_track_observer_tickertrack.py b/tests/test_track_observer_tickertrack.py index 5e11fd47..a9a56f8d 100644 --- a/tests/test_track_observer_tickertrack.py +++ b/tests/test_track_observer_tickertrack.py @@ -1,6 +1,6 @@ """Tests for :class:`observer.TickerTrackObserver`.""" -import os +from pathlib import Path import pytest @@ -10,18 +10,18 @@ @pytest.mark.filterwarnings( - f"ignore:{_FORMAT_WARNING}:PendingDeprecationWarning:nowplaying.track.observer" + f"ignore:{_FORMAT_WARNING}:PendingDeprecationWarning:nowplaying.track.observer", ) def test_init(): """Test class:`TickerTrackObserver`'s :meth:`.__init__` method.""" ticker_track_observer = TickerTrackObserver( - options=TickerTrackObserver.Options(file_path="") + options=TickerTrackObserver.Options(file_path=""), ) assert ticker_track_observer.ticker_file_path == "" @pytest.mark.filterwarnings( - f"ignore:{_FORMAT_WARNING}:PendingDeprecationWarning:nowplaying.track.observer" + f"ignore:{_FORMAT_WARNING}:PendingDeprecationWarning:nowplaying.track.observer", ) def test_track_started(track_factory, show_factory): """Test :class:`TickerTrackObserver`'s :meth:`track_started` method.""" @@ -30,21 +30,21 @@ def test_track_started(track_factory, show_factory): track.show = show_factory() ticker_track_observer = TickerTrackObserver( - options=TickerTrackObserver.Options(file_path="/tmp/track_started.xml") + options=TickerTrackObserver.Options(file_path="/tmp/track_started.xml"), ) ticker_track_observer.track_started(track) - assert os.path.exists("/tmp/track_started.xml") + assert Path("/tmp/track_started.xml").exists() @pytest.mark.filterwarnings( - f"ignore:{_FORMAT_WARNING}:PendingDeprecationWarning:nowplaying.track.observer" + f"ignore:{_FORMAT_WARNING}:PendingDeprecationWarning:nowplaying.track.observer", ) def test_track_finished(track_factory): """Test :class:`TickerTrackObserver`'s :meth:`track_finished` method.""" track = track_factory() ticker_track_observer = TickerTrackObserver( - options=TickerTrackObserver.Options(file_path="/tmp/dummy.xml") + options=TickerTrackObserver.Options(file_path="/tmp/dummy.xml"), ) - assert ticker_track_observer.track_finished(track) + ticker_track_observer.track_finished(track)