From 1eb5ea5c8b026cc976b8ffb8cc6f4444e9b67931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 21:25:00 +0100 Subject: [PATCH 01/13] fix test and handle deferred pool --- blitzortung/cli/webservice.py | 66 +--- blitzortung/dataimport/__init__.py | 1 - blitzortung/db/query_builder.py | 2 +- blitzortung/service/__init__.py | 11 +- blitzortung/service/db.py | 80 ++++ blitzortung/service/histogram.py | 15 +- blitzortung/service/strike_grid.py | 2 +- blitzortung/websocket.py | 4 - poetry.lock | 598 +++++++++++++++++------------ pyproject.toml | 1 + tests/conftest.py | 89 ++++- tests/db/test_db.py | 202 +++------- tests/service/test_histogram.py | 22 +- tests/service/test_service_db.py | 86 +++++ 14 files changed, 699 insertions(+), 480 deletions(-) create mode 100644 blitzortung/service/db.py create mode 100644 tests/service/test_service_db.py diff --git a/blitzortung/cli/webservice.py b/blitzortung/cli/webservice.py index 39fd450..a56147e 100755 --- a/blitzortung/cli/webservice.py +++ b/blitzortung/cli/webservice.py @@ -16,12 +16,9 @@ import platform import time -import psycopg2 -import psycopg2.extras import pyproj import statsd from twisted.application import internet, service -from twisted.internet import defer from twisted.internet.error import ReactorAlreadyInstalledError from twisted.python import log from twisted.python.log import FileLogObserver, ILogObserver, textFromEventDict, _safeFormat @@ -30,8 +27,6 @@ from txjsonrpc_ng.web import jsonrpc from txjsonrpc_ng.web.data import CacheableResult from txjsonrpc_ng.web.jsonrpc import with_request -from txpostgres import reconnection -from txpostgres.txpostgres import Connection, ConnectionPool try: from twisted.internet import epollreactor as reactor @@ -49,6 +44,7 @@ import blitzortung.geom import blitzortung.service from blitzortung.db.query import TimeInterval +from blitzortung.service.db import create_connection_pool from blitzortung.service.general import create_time_interval from blitzortung.service.strike_grid import GridParameters @@ -68,59 +64,6 @@ FORBIDDEN_IPS = {} - -def connection_factory(*args, **kwargs): - """Create a psycopg2 connection with DictConnection factory.""" - kwargs['connection_factory'] = psycopg2.extras.DictConnection - return psycopg2.connect(*args, **kwargs) - - -class LoggingDetector(reconnection.DeadConnectionDetector): - """Database connection detector that logs reconnection events.""" - - def startReconnecting(self, f): - print('[*] database connection is down (error: %r)' % f.value) - return reconnection.DeadConnectionDetector.startReconnecting(self, f) - - def reconnect(self): - print('[*] reconnecting...') - return reconnection.DeadConnectionDetector.reconnect(self) - - def connectionRecovered(self): - print('[*] connection recovered') - return reconnection.DeadConnectionDetector.connectionRecovered(self) - - -class DictConnection(Connection): - """Database connection using DictConnection factory with logging detector.""" - connectionFactory = staticmethod(connection_factory) - - def __init__(self, reactor=None, cooperator=None, detector=None): - if not detector: - detector = LoggingDetector() - super(DictConnection, self).__init__(reactor, cooperator, detector) - - -class DictConnectionPool(ConnectionPool): - """Connection pool using DictConnection instances.""" - connectionFactory = DictConnection - - def __init__(self, _ignored, *connargs, **connkw): - super(DictConnectionPool, self).__init__(_ignored, *connargs, **connkw) - - -def create_connection_pool(): - """Create and start the database connection pool.""" - config = blitzortung.config.config() - db_connection_string = config.get_db_connection_string() - - connection_pool = DictConnectionPool(None, db_connection_string) - - d = connection_pool.start() - d.addErrback(log.err) - return connection_pool - - grid = { 1: blitzortung.geom.GridFactory(-25, 57, 27, 72, UTM_EU), 2: blitzortung.geom.GridFactory(110, 180, -50, 0, UTM_OCEANIA), @@ -588,7 +531,12 @@ def emit(self, event_dict): else: log_directory = None -connection_pool = create_connection_pool() +from twisted.internet import defer, reactor + +# Block until connection pool is ready +connection_pool_deferred = create_connection_pool() +connection_pool = defer.blockingCallFromThread(reactor, lambda: connection_pool_deferred) + root = Blitzortung(connection_pool, log_directory) config = blitzortung.config.config() diff --git a/blitzortung/dataimport/__init__.py b/blitzortung/dataimport/__init__.py index 4b79886..35dee23 100644 --- a/blitzortung/dataimport/__init__.py +++ b/blitzortung/dataimport/__init__.py @@ -6,4 +6,3 @@ def strikes(): from .. import INJECTOR return INJECTOR.get(StrikesBlitzortungDataProvider) - diff --git a/blitzortung/db/query_builder.py b/blitzortung/db/query_builder.py index 7543444..511b52f 100644 --- a/blitzortung/db/query_builder.py +++ b/blitzortung/db/query_builder.py @@ -55,7 +55,7 @@ def global_grid_query(table_name, grid, count_threshold=0, **kwargs): .set_default_conditions(**kwargs) @staticmethod - def histogram_query(table_name: str, time_interval: TimeInterval, binsize:int, region:Optional[int]=None, envelope=None): + def histogram_query(table_name: str, time_interval: TimeInterval, binsize:int, region:Optional[int]=None, envelope=None) -> SelectQuery: query = SelectQuery() \ .set_table_name(table_name) \ diff --git a/blitzortung/service/__init__.py b/blitzortung/service/__init__.py index c09a87c..6074afd 100644 --- a/blitzortung/service/__init__.py +++ b/blitzortung/service/__init__.py @@ -19,27 +19,30 @@ """ from . import histogram, strike, strike_grid +from .histogram import HistogramQuery +from .strike import StrikeQuery +from .strike_grid import GlobalStrikeGridQuery, StrikeGridQuery -def strike_query(): +def strike_query() -> StrikeQuery: from .. import INJECTOR return INJECTOR.get(strike.StrikeQuery) -def strike_grid_query(): +def strike_grid_query() -> StrikeGridQuery: from .. import INJECTOR return INJECTOR.get(strike_grid.StrikeGridQuery) -def global_strike_grid_query(): +def global_strike_grid_query() -> GlobalStrikeGridQuery: from .. import INJECTOR return INJECTOR.get(strike_grid.GlobalStrikeGridQuery) -def histogram_query(): +def histogram_query() -> HistogramQuery: from .. import INJECTOR return INJECTOR.get(histogram.HistogramQuery) diff --git a/blitzortung/service/db.py b/blitzortung/service/db.py new file mode 100644 index 0000000..4f555b6 --- /dev/null +++ b/blitzortung/service/db.py @@ -0,0 +1,80 @@ +# -*- coding: utf8 -*- + +""" + + Copyright 2025 Andreas Würl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +""" + +import psycopg2 +import psycopg2.extras +from pytest_twisted import inlineCallbacks +from twisted.python import log +from txpostgres import reconnection +from txpostgres.txpostgres import Connection, ConnectionPool + +import blitzortung.config + + +def connection_factory(*args, **kwargs): + """Create a psycopg2 connection with DictConnection factory.""" + kwargs['connection_factory'] = psycopg2.extras.DictConnection + return psycopg2.connect(*args, **kwargs) + + +class LoggingDetector(reconnection.DeadConnectionDetector): + """Database connection detector that logs reconnection events.""" + + def startReconnecting(self, f): + print('[*] database connection is down (error: %r)' % f.value) + return reconnection.DeadConnectionDetector.startReconnecting(self, f) + + def reconnect(self): + print('[*] reconnecting...') + return reconnection.DeadConnectionDetector.reconnect(self) + + def connectionRecovered(self): + print('[*] connection recovered') + return reconnection.DeadConnectionDetector.connectionRecovered(self) + + +class DictConnection(Connection): + """Database connection using DictConnection factory with logging detector.""" + connectionFactory = staticmethod(connection_factory) + + def __init__(self, reactor=None, cooperator=None, detector=None): + if not detector: + detector = LoggingDetector() + super(DictConnection, self).__init__(reactor, cooperator, detector) + + +class DictConnectionPool(ConnectionPool): + """Connection pool using DictConnection instances.""" + connectionFactory = DictConnection + + def __init__(self, _ignored, *connargs, **connkw): + super(DictConnectionPool, self).__init__(_ignored, *connargs, **connkw) + +def create_connection_pool(): + """Create and start the database connection pool.""" + config = blitzortung.config.config() + db_connection_string = config.get_db_connection_string() + + connection_pool = DictConnectionPool(None, db_connection_string) + + d = connection_pool.start() + d.addErrback(log.err) + + return d diff --git a/blitzortung/service/histogram.py b/blitzortung/service/histogram.py index 46f85a4..f5561f3 100644 --- a/blitzortung/service/histogram.py +++ b/blitzortung/service/histogram.py @@ -21,9 +21,10 @@ import time from injector import inject +from twisted.internet.defer import DeferredList, Deferred from .. import db -from ..db.query import TimeInterval +from ..db.query import TimeInterval, SelectQuery class HistogramQuery: @@ -31,10 +32,16 @@ class HistogramQuery: def __init__(self, strike_query_builder: db.query_builder.Strike): self.strike_query_builder = strike_query_builder - def create(self, time_interval: TimeInterval, connection, region=None, envelope=None): + def create(self, time_interval: TimeInterval, deferred_pool: Deferred, region=None, envelope=None): reference_time = time.time() - query = self.strike_query_builder.histogram_query(db.table.Strike.table_name, time_interval, 5, region, envelope) - histogram_query = connection.runQuery(str(query), query.get_parameters()) + + histogram_query = self.strike_query_builder.histogram_query(db.table.Strike.table_name, time_interval, 5, region, envelope) + + def execute_query(connection, query: SelectQuery): + return connection.runQuery(str(query), query.get_parameters()) + + histogram_query = deferred_pool.addCallback(execute_query, histogram_query) + histogram_query.addCallback(self.build_result, minutes=time_interval.minutes(), bin_size=5, reference_time=reference_time) return histogram_query diff --git a/blitzortung/service/strike_grid.py b/blitzortung/service/strike_grid.py index 537cbed..e8fa10f 100644 --- a/blitzortung/service/strike_grid.py +++ b/blitzortung/service/strike_grid.py @@ -23,7 +23,7 @@ from typing import Optional from injector import inject -from twisted.internet.defer import gatherResults +from twisted.internet.defer import gatherResults, Deferred from twisted.python import log from .general import TimingState diff --git a/blitzortung/websocket.py b/blitzortung/websocket.py index 7fda369..51a0a09 100644 --- a/blitzortung/websocket.py +++ b/blitzortung/websocket.py @@ -34,7 +34,3 @@ def decode(data): o += 1 f = a return g - - - - diff --git a/poetry.lock b/poetry.lock index 34e7d42..ad6d0e0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -210,14 +210,14 @@ visualize = ["Twisted (>=16.1.1)", "graphviz (>0.5.1)"] [[package]] name = "certifi" -version = "2025.10.5" +version = "2025.11.12" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, - {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, + {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}, + {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, ] [[package]] @@ -467,109 +467,121 @@ files = [ [[package]] name = "coverage" -version = "7.11.0" +version = "7.11.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, - {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, - {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, - {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, - {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, - {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, - {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, - {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, - {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, - {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, - {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, - {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, - {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, - {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, - {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, - {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, - {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, - {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, - {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, - {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, - {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, - {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, - {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, - {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, - {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, - {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, - {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, - {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, - {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, - {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, - {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, - {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, - {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, - {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, - {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, - {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, - {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, - {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, - {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, - {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, - {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, - {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, - {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, - {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5"}, + {file = "coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c"}, + {file = "coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832"}, + {file = "coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e"}, + {file = "coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb"}, + {file = "coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1"}, + {file = "coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297"}, + {file = "coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4"}, + {file = "coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060"}, + {file = "coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7"}, + {file = "coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55"}, + {file = "coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f"}, + {file = "coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd"}, + {file = "coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7"}, + {file = "coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405"}, + {file = "coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e"}, + {file = "coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055"}, + {file = "coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36"}, + {file = "coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3"}, + {file = "coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5"}, + {file = "coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094"}, + {file = "coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c"}, + {file = "coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2"}, + {file = "coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428"}, + {file = "coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d"}, + {file = "coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71"}, + {file = "coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76"}, + {file = "coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c"}, + {file = "coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac"}, + {file = "coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c"}, + {file = "coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902"}, + {file = "coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b"}, + {file = "coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131"}, + {file = "coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a"}, + {file = "coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86"}, + {file = "coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df"}, + {file = "coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd"}, + {file = "coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f"}, + {file = "coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820"}, + {file = "coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237"}, + {file = "coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9"}, + {file = "coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd"}, + {file = "coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe"}, + {file = "coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b"}, ] [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] +[[package]] +name = "decorator" +version = "5.2.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, + {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, +] + [[package]] name = "docker" version = "7.1.0" @@ -762,6 +774,74 @@ aiohttp = ">=3.6.2,<4.0.0" maxminddb = ">=2.7.0,<3.0.0" requests = ">=2.24.0,<3.0.0" +[[package]] +name = "greenlet" +version = "3.2.4" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, + {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, + {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, + {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, + {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, + {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, + {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, + {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, + {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, + {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, + {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, + {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, + {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, + {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil", "setuptools"] + [[package]] name = "hyperlink" version = "21.0.0" @@ -1520,14 +1600,14 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests [[package]] name = "pytest-benchmark" -version = "5.2.0" +version = "5.2.3" description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest_benchmark-5.2.0-py3-none-any.whl", hash = "sha256:0631cdf19f6032fc46d6bf9e8d15931d78473228b579a3fd84ca5e2f0e8ee06c"}, - {file = "pytest_benchmark-5.2.0.tar.gz", hash = "sha256:75731991edf6c807d0699130afbb4ba77d8ce8e3b8314662c340ee8e1db19f43"}, + {file = "pytest_benchmark-5.2.3-py3-none-any.whl", hash = "sha256:bc839726ad20e99aaa0d11a127445457b4219bdb9e80a1afc4b51da7f96b0803"}, + {file = "pytest_benchmark-5.2.3.tar.gz", hash = "sha256:deb7317998a23c650fd4ff76e1230066a76cb45dcece0aca5607143c619e7779"}, ] [package.dependencies] @@ -1559,6 +1639,28 @@ pytest = ">=6.2.5" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +[[package]] +name = "pytest-twisted" +version = "1.14.3" +description = "A twisted plugin for pytest." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["dev"] +files = [ + {file = "pytest_twisted-1.14.3-py2.py3-none-any.whl", hash = "sha256:f2e3f3f6f12f78df17c028fe16d87af09c76b95a7a85bc378b2d3e73a086e81a"}, + {file = "pytest_twisted-1.14.3.tar.gz", hash = "sha256:37e150cbbc0edba6592d36c53f44fc1196f3a9e93e7bef6a25bb10d9963f7f3e"}, +] + +[package.dependencies] +decorator = "*" +greenlet = "*" +pytest = ">=2.3" + +[package.extras] +dev = ["black", "pre-commit"] +pyqt5 = ["qt5reactor[pyqt5] (>=0.6.2)"] +pyside2 = ["qt5reactor[pyside2] (>=0.6.3)"] + [[package]] name = "python-dotenv" version = "1.2.1" @@ -1759,14 +1861,14 @@ files = [ [[package]] name = "testcontainers" -version = "4.13.2" +version = "4.13.3" description = "Python library for throwaway instances of anything that can run in a Docker container" optional = false -python-versions = "<4.0,>=3.9.2" +python-versions = ">=3.9.2" groups = ["dev"] files = [ - {file = "testcontainers-4.13.2-py3-none-any.whl", hash = "sha256:0209baf8f4274b568cde95bef2cadf7b1d33b375321f793790462e235cd684ee"}, - {file = "testcontainers-4.13.2.tar.gz", hash = "sha256:2315f1e21b059427a9d11e8921f85fef322fbe0d50749bcca4eaa11271708ba4"}, + {file = "testcontainers-4.13.3-py3-none-any.whl", hash = "sha256:063278c4805ffa6dd85e56648a9da3036939e6c0ac1001e851c9276b19b05970"}, + {file = "testcontainers-4.13.3.tar.gz", hash = "sha256:9d82a7052c9a53c58b69e1dc31da8e7a715e8b3ec1c4df5027561b47e2efe646"}, ] [package.dependencies] @@ -1781,26 +1883,25 @@ arangodb = ["python-arango (>=7.8,<8.0)"] aws = ["boto3", "httpx"] azurite = ["azure-storage-blob (>=12.19,<13.0)"] chroma = ["chromadb-client (>=1.0.0,<2.0.0)"] -clickhouse = ["clickhouse-driver"] cosmosdb = ["azure-cosmos"] db2 = ["ibm_db_sa ; platform_machine != \"aarch64\" and platform_machine != \"arm64\"", "sqlalchemy"] generic = ["httpx", "redis"] google = ["google-cloud-datastore (>=2)", "google-cloud-pubsub (>=2)"] influxdb = ["influxdb", "influxdb-client"] -k3s = ["kubernetes", "pyyaml"] +k3s = ["kubernetes", "pyyaml (>=6.0.3)"] keycloak = ["python-keycloak"] localstack = ["boto3"] mailpit = ["cryptography"] minio = ["minio"] mongodb = ["pymongo"] -mssql = ["pymssql ; platform_machine != \"arm64\" or python_version >= \"3.10\"", "sqlalchemy"] +mssql = ["pymssql (>=2.3.9) ; platform_machine != \"arm64\" or python_version >= \"3.10\"", "sqlalchemy"] mysql = ["pymysql[rsa]", "sqlalchemy"] nats = ["nats-py"] neo4j = ["neo4j"] openfga = ["openfga-sdk ; python_version >= \"3.10\""] -opensearch = ["opensearch-py"] -oracle = ["oracledb", "sqlalchemy"] -oracle-free = ["oracledb", "sqlalchemy"] +opensearch = ["opensearch-py ; python_version < \"4.0\""] +oracle = ["oracledb (>=3.4.1)", "sqlalchemy"] +oracle-free = ["oracledb (>=3.4.1)", "sqlalchemy"] qdrant = ["qdrant-client"] rabbitmq = ["pika"] redis = ["redis"] @@ -1924,119 +2025,119 @@ test = ["pytest", "websockets"] [[package]] name = "wrapt" -version = "2.0.0" +version = "2.0.1" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "wrapt-2.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a7cebcee61f21b1e46aa32db8d9d93826d0fbf1ad85defc2ccfb93b4adef1435"}, - {file = "wrapt-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:827e6e3a3a560f6ec1f5ee92d4319c21a0549384f896ec692f3201eda31ebd11"}, - {file = "wrapt-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a91075a5383a7cbfe46aed1845ef7c3f027e8e20e7d9a8a75e36ebc9b0dd15e"}, - {file = "wrapt-2.0.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b6a18c813196e18146b8d041e20875bdb0cb09b94ac1d1e1146e0fa87b2deb0d"}, - {file = "wrapt-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec5028d26011a53c76bd91bb6198b30b438c6e0f7adb45f2ad84fe2655b6a104"}, - {file = "wrapt-2.0.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bed9b04900204721a24bcefc652ca267b01c1e8ad8bc8c0cff81558a45a3aadc"}, - {file = "wrapt-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:03442f2b45fa3f2b98a94a1917f52fb34670de8f96c0a009c02dbd512d855a3d"}, - {file = "wrapt-2.0.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:17d0b5c42495ba142a1cee52b76414f9210591c84aae94dffda70240753bfb3c"}, - {file = "wrapt-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ee44215e7d13e112a8fc74e12ed1a1f41cab2bc07b11cc703f2398cd114b261c"}, - {file = "wrapt-2.0.0-cp310-cp310-win32.whl", hash = "sha256:fe6eafac3bc3c957ab6597a0c0654a0a308868458d00d218743e5b5fae51951c"}, - {file = "wrapt-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:9e070c3491397fba0445b8977900271eca9656570cca7c900d9b9352186703a0"}, - {file = "wrapt-2.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:806e2e73186eb5e3546f39fb5d0405040e0088db0fc8b2f667fd1863de2b3c99"}, - {file = "wrapt-2.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b7e221abb6c5387819db9323dac3c875b459695057449634f1111955d753c621"}, - {file = "wrapt-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1147a84c8fc852426580af8b6e33138461ddbc65aa459a25ea539374d32069fa"}, - {file = "wrapt-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d6691d4a711504a0bc10de789842ad6ac627bed22937b10f37a1211a8ab7bb3"}, - {file = "wrapt-2.0.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f460e1eb8e75a17c3918c8e35ba57625721eef2439ef0bcf05304ac278a65e1d"}, - {file = "wrapt-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12c37784b77bf043bf65cc96c7195a5db474b8e54173208af076bdbb61df7b3e"}, - {file = "wrapt-2.0.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75e5c049eb583835f7a0e0e311d9dde9bfbaac723a6dd89d052540f9b2809977"}, - {file = "wrapt-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e50bcbd5b65dac21b82319fcf18486e6ac439947e9305034b00704eb7405f553"}, - {file = "wrapt-2.0.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:06b78cb6b9320f57737a52fede882640d93cface98332d1a3df0c5696ec9ae9f"}, - {file = "wrapt-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c8349ebfc3cd98bc9105e0112dd8c8ac1f3c7cb5601f9d02248cae83a63f748"}, - {file = "wrapt-2.0.0-cp311-cp311-win32.whl", hash = "sha256:028f19ec29e204fe725139d4a8b09f77ecfb64f8f02b7ab5ee822c85e330b68b"}, - {file = "wrapt-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:c6961f05e58d919153ba311b397b7b904b907132b7b8344dde47865d4bb5ec89"}, - {file = "wrapt-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:be7e316c2accd5a31dbcc230de19e2a846a325f8967fdea72704d00e38e6af06"}, - {file = "wrapt-2.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73c6f734aecb1a030d9a265c13a425897e1ea821b73249bb14471445467ca71c"}, - {file = "wrapt-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b4a7f8023b8ce8a36370154733c747f8d65c8697cb977d8b6efeb89291fff23e"}, - {file = "wrapt-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a1cb62f686c50e9dab5983c68f6c8e9cbf14a6007935e683662898a7d892fa69"}, - {file = "wrapt-2.0.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:43dc0550ae15e33e6bb45a82a5e1b5495be2587fbaa996244b509921810ee49f"}, - {file = "wrapt-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39c5b45b056d630545e40674d1f5e1b51864b3546f25ab6a4a331943de96262e"}, - {file = "wrapt-2.0.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:804e88f824b76240a1b670330637ccfd2d18b9efa3bb4f02eb20b2f64880b324"}, - {file = "wrapt-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c2c476aa3fc2b9899c3f7b20963fac4f952e7edb74a31fc92f7745389a2e3618"}, - {file = "wrapt-2.0.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8d851e526891216f89fcb7a1820dad9bd503ba3468fb9635ee28e93c781aa98e"}, - {file = "wrapt-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b95733c2360c4a8656ee93c7af78e84c0bd617da04a236d7a456c8faa34e7a2d"}, - {file = "wrapt-2.0.0-cp312-cp312-win32.whl", hash = "sha256:ea56817176834edf143df1109ae8fdaa087be82fdad3492648de0baa8ae82bf2"}, - {file = "wrapt-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c7d3bee7be7a2665286103f4d1f15405c8074e6e1f89dac5774f9357c9a3809"}, - {file = "wrapt-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:680f707e1d26acbc60926659799b15659f077df5897a6791c7c598a5d4a211c4"}, - {file = "wrapt-2.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2ea096db28d5eb64d381af0e93464621ace38a7003a364b6b5ffb7dd713aabe"}, - {file = "wrapt-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c92b5a82d28491e3f14f037e1aae99a27a5e6e0bb161e65f52c0445a3fa7c940"}, - {file = "wrapt-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:81d234718aabe632d179fac52c7f69f0f99fbaac4d4bcd670e62462bbcbfcad7"}, - {file = "wrapt-2.0.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db2eea83c43f84e4e41dbbb4c1de371a53166e55f900a6b130c3ef51c6345c1a"}, - {file = "wrapt-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65f50e356c425c061e1e17fe687ff30e294fed9bf3441dc1f13ef73859c2a817"}, - {file = "wrapt-2.0.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:887f2a667e3cbfb19e204032d42ad7dedaa43972e4861dc7a3d51ae951d9b578"}, - {file = "wrapt-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9054829da4be461e3ad3192e4b6bbf1fc18af64c9975ce613aec191924e004dc"}, - {file = "wrapt-2.0.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b952ffd77133a5a2798ee3feb18e51b0a299d2f440961e5bb7737dbb02e57289"}, - {file = "wrapt-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e25fde03c480061b8234d8ee4863eb5f40a9be4fb258ce105b364de38fc6bcf9"}, - {file = "wrapt-2.0.0-cp313-cp313-win32.whl", hash = "sha256:49e982b7860d325094978292a49e0418833fc7fc42c0dc7cd0b7524d7d06ee74"}, - {file = "wrapt-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e5c86389d9964050ce50babe247d172a5e3911d59a64023b90db2b4fa00ae7c"}, - {file = "wrapt-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:b96fdaa4611e05c7231937930567d3c16782be9dbcf03eb9f60d83e57dd2f129"}, - {file = "wrapt-2.0.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f2c7b7fead096dbf1dcc455b7f59facb05de3f5bfb04f60a69f98cdfe6049e5f"}, - {file = "wrapt-2.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:04c7c8393f25b11c0faa5d907dd9eb462e87e4e7ba55e308a046d7ed37f4bbe2"}, - {file = "wrapt-2.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a93e0f8b376c0735b2f4daf58018b4823614d2b896cb72b6641c4d3dbdca1d75"}, - {file = "wrapt-2.0.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b42d13603da4416c43c430dbc6313c8d7ff745c40942f146ed4f6dd02c7d2547"}, - {file = "wrapt-2.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8bbd2472abf8c33480ad2314b1f8fac45d592aba6cc093e8839a7b2045660e6"}, - {file = "wrapt-2.0.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e64a3a1fd9a308ab9b815a2ad7a65b679730629dbf85f8fc3f7f970d634ee5df"}, - {file = "wrapt-2.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d61214525eaf88e0d0edf3d1ad5b5889863c6f88e588c6cdc6aa4ee5d1f10a4a"}, - {file = "wrapt-2.0.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:04f7a5f92c5f7324a1735043cc467b1295a1c5b4e0c1395472b7c44706e3dc61"}, - {file = "wrapt-2.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2356f76cb99b3de5b4e5b8210367fbbb81c7309fe39b622f5d199dd88eb7f765"}, - {file = "wrapt-2.0.0-cp313-cp313t-win32.whl", hash = "sha256:0a921b657a224e40e4bc161b5d33934583b34f0c9c5bdda4e6ac66f9d2fcb849"}, - {file = "wrapt-2.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c16f6d4eea98080f6659a8a7fc559d4a0a337ee66960659265cad2c8a40f7c0f"}, - {file = "wrapt-2.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:52878edc13dc151c58a9966621d67163a80654bc6cff4b2e1c79fa62d0352b26"}, - {file = "wrapt-2.0.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:79a53d86c2aff7b32cc77267e3a308365d1fcb881e74bc9cbe26f63ee90e37f0"}, - {file = "wrapt-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d731a4f22ed6ffa4cb551b4d2b0c24ff940c27a88edaf8e3490a5ee3a05aef71"}, - {file = "wrapt-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3e02ab8c0ac766a5a6e81cd3b6cc39200c69051826243182175555872522bd5a"}, - {file = "wrapt-2.0.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:895870602d65d7338edb3b6a717d856632ad9f14f7ff566214e4fb11f0816649"}, - {file = "wrapt-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b9ad4fab76a0086dc364c4f17f39ad289600e73ef5c6e9ab529aff22cac1ac3"}, - {file = "wrapt-2.0.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e7ca0562606d7bad2736b2c18f61295d61f50cd3f4bfc51753df13614dbcce1b"}, - {file = "wrapt-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe089d9f5a4a3dea0108a8ae34bced114d0c4cca417bada1c5e8f42d98af9050"}, - {file = "wrapt-2.0.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e761f2d2f8dbc80384af3d547b522a80e67db3e319c7b02e7fd97aded0a8a678"}, - {file = "wrapt-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:17ba1bdc52d0c783481850996aa26cea5237720769197335abea2ae6b4c23bc0"}, - {file = "wrapt-2.0.0-cp314-cp314-win32.whl", hash = "sha256:f73318741b141223a4674ba96992aa2291b1b3f7a5e85cb3c2c964f86171eb45"}, - {file = "wrapt-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8e08d4edb13cafe7b3260f31d4de033f73d3205774540cf583bffaa4bec97db9"}, - {file = "wrapt-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:af01695c2b7bbd8d67b869d8e3de2b123a7bfbee0185bdd138c2775f75373b83"}, - {file = "wrapt-2.0.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:057f02c13cce7b26c79624c06a3e1c2353e6dc9708525232232f6768118042ca"}, - {file = "wrapt-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:79bdd84570267f3f43d609c892ae2d30b91ee4b8614c2cbfd311a2965f1c9bdb"}, - {file = "wrapt-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:93c8b4f4d54fd401a817abbfc9bf482aa72fd447f8adf19ce81d035b3f5c762c"}, - {file = "wrapt-2.0.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5e09ffd31001dce71c2c2a4fc201bdba9a2f9f62b23700cf24af42266e784741"}, - {file = "wrapt-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d87c285ff04e26083c4b03546e7b74df7ba4f1f32f1dcb92e9ac13c2dbb4c379"}, - {file = "wrapt-2.0.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e52e50ea0a72ea48d1291cf8b8aaedcc99072d9dc5baba6b820486dcf4c67da8"}, - {file = "wrapt-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fd4c95536975895f32571073446e614d5e2810b666b64955586dcddfd438fd3"}, - {file = "wrapt-2.0.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d6ebfe9283209220ed9de80a3e9442aab8fc2be5a9bbf8491b99e02ca9349a89"}, - {file = "wrapt-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5d3ebd784804f146b7ea55359beb138e23cc18e5a5cc2cf26ad438723c00ce3a"}, - {file = "wrapt-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:9b15940ae9debc8b40b15dc57e1ce4433f7fb9d3f8761c7fab1ddd94cb999d99"}, - {file = "wrapt-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7a0efbbc06d3e2077476a04f55859819d23206600b4c33f791359a8e6fa3c362"}, - {file = "wrapt-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7fec8a9455c029c8cf4ff143a53b6e7c463268d42be6c17efa847ebd2f809965"}, - {file = "wrapt-2.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ac3d8beac68e4863c703b844fcc82693f83f933b37d2a54e9d513b2aab9c76aa"}, - {file = "wrapt-2.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4b8f8644602803add6848c81b7d214cfd397b1ebab2130dc8530570d888155c"}, - {file = "wrapt-2.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93cb5bff1fcd89b75f869e4f69566a91ab2c9f13e8edf0241fd5777b2fa6d48e"}, - {file = "wrapt-2.0.0-cp38-cp38-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e0eb6d155d02c7525b7ec09856cda5e611fc6eb9ab40d140e1f35f27ac7d5eae"}, - {file = "wrapt-2.0.0-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:309dd467a94ee38a7aa5752bda64e660aeab5723b26200d0b65a375dad9add09"}, - {file = "wrapt-2.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a55e8edd08e2eece131d90d82cd1521962d9152829b22c56e68539526d605825"}, - {file = "wrapt-2.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:1724dd7b84d419c80ba839da81ad78b02ac30df626e5aefcb18e94632a965f13"}, - {file = "wrapt-2.0.0-cp38-cp38-win32.whl", hash = "sha256:f8255c380a79f6752d0b920e69a5d656d863675d9c433eeb5548518ee2c8d9da"}, - {file = "wrapt-2.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:829c8d46465dbae49dba91516f11200a2b5ea91eae8afaccbc035f0b651eb9c4"}, - {file = "wrapt-2.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:094d348ce7e6ce37bf6ed9a6ecc11886c96f447b3ffebc7539ca197daa9a997e"}, - {file = "wrapt-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98223acaa25b1449d993a3f4ffc8b5a03535e4041b37bf6a25459a0c74ee4cfc"}, - {file = "wrapt-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b79bf04c722035b1c474980dc1a64369feab7b703d6fe67da2d8664ed0bc980"}, - {file = "wrapt-2.0.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:623242959cb0c53f76baeb929be79f5f6a9a1673ef51628072b91bf299af2212"}, - {file = "wrapt-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:59dc94afc4542c7d9b9447fb2ae1168b5a29064eca4061dbbf3b3c26df268334"}, - {file = "wrapt-2.0.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7c532cc9f0a9e6017f8d3c37f478a3e3a5dffa955ebba556274e5e916c058f7"}, - {file = "wrapt-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9d72c725cefbcc8ebab85c8352e5062ae87b6e323858e934e16b54ced580435a"}, - {file = "wrapt-2.0.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:2ca35b83497276c2ca0b072d2c00da2edde4c2a6c8c650eafcd1a006c17ab231"}, - {file = "wrapt-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2fc55d0da29318a5da33c2827aef8946bba046ac609a4784a90faff73c511174"}, - {file = "wrapt-2.0.0-cp39-cp39-win32.whl", hash = "sha256:9c100b0598f3763274f2033bcc0454de7486409f85bc6da58b49e5971747eb36"}, - {file = "wrapt-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:1316972a72c67936a07dbb48e2464356d91dd9674335aaec087b60094d87750b"}, - {file = "wrapt-2.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:5aad54ff45da9784573099696fd84841c7e559ce312f02afa6aa7e89b58e2c2f"}, - {file = "wrapt-2.0.0-py3-none-any.whl", hash = "sha256:02482fb0df89857e35427dfb844319417e14fae05878f295ee43fa3bf3b15502"}, - {file = "wrapt-2.0.0.tar.gz", hash = "sha256:35a542cc7a962331d0279735c30995b024e852cf40481e384fd63caaa391cbb9"}, + {file = "wrapt-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:64b103acdaa53b7caf409e8d45d39a8442fe6dcfec6ba3f3d141e0cc2b5b4dbd"}, + {file = "wrapt-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:91bcc576260a274b169c3098e9a3519fb01f2989f6d3d386ef9cbf8653de1374"}, + {file = "wrapt-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab594f346517010050126fcd822697b25a7031d815bb4fbc238ccbe568216489"}, + {file = "wrapt-2.0.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:36982b26f190f4d737f04a492a68accbfc6fa042c3f42326fdfbb6c5b7a20a31"}, + {file = "wrapt-2.0.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23097ed8bc4c93b7bf36fa2113c6c733c976316ce0ee2c816f64ca06102034ef"}, + {file = "wrapt-2.0.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8bacfe6e001749a3b64db47bcf0341da757c95959f592823a93931a422395013"}, + {file = "wrapt-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8ec3303e8a81932171f455f792f8df500fc1a09f20069e5c16bd7049ab4e8e38"}, + {file = "wrapt-2.0.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:3f373a4ab5dbc528a94334f9fe444395b23c2f5332adab9ff4ea82f5a9e33bc1"}, + {file = "wrapt-2.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f49027b0b9503bf6c8cdc297ca55006b80c2f5dd36cecc72c6835ab6e10e8a25"}, + {file = "wrapt-2.0.1-cp310-cp310-win32.whl", hash = "sha256:8330b42d769965e96e01fa14034b28a2a7600fbf7e8f0cc90ebb36d492c993e4"}, + {file = "wrapt-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:1218573502a8235bb8a7ecaed12736213b22dcde9feab115fa2989d42b5ded45"}, + {file = "wrapt-2.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:eda8e4ecd662d48c28bb86be9e837c13e45c58b8300e43ba3c9b4fa9900302f7"}, + {file = "wrapt-2.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e17283f533a0d24d6e5429a7d11f250a58d28b4ae5186f8f47853e3e70d2590"}, + {file = "wrapt-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85df8d92158cb8f3965aecc27cf821461bb5f40b450b03facc5d9f0d4d6ddec6"}, + {file = "wrapt-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1be685ac7700c966b8610ccc63c3187a72e33cab53526a27b2a285a662cd4f7"}, + {file = "wrapt-2.0.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df0b6d3b95932809c5b3fecc18fda0f1e07452d05e2662a0b35548985f256e28"}, + {file = "wrapt-2.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da7384b0e5d4cae05c97cd6f94faaf78cc8b0f791fc63af43436d98c4ab37bb"}, + {file = "wrapt-2.0.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ec65a78fbd9d6f083a15d7613b2800d5663dbb6bb96003899c834beaa68b242c"}, + {file = "wrapt-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7de3cc939be0e1174969f943f3b44e0d79b6f9a82198133a5b7fc6cc92882f16"}, + {file = "wrapt-2.0.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fb1a5b72cbd751813adc02ef01ada0b0d05d3dcbc32976ce189a1279d80ad4a2"}, + {file = "wrapt-2.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3fa272ca34332581e00bf7773e993d4f632594eb2d1b0b162a9038df0fd971dd"}, + {file = "wrapt-2.0.1-cp311-cp311-win32.whl", hash = "sha256:fc007fdf480c77301ab1afdbb6ab22a5deee8885f3b1ed7afcb7e5e84a0e27be"}, + {file = "wrapt-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:47434236c396d04875180171ee1f3815ca1eada05e24a1ee99546320d54d1d1b"}, + {file = "wrapt-2.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:837e31620e06b16030b1d126ed78e9383815cbac914693f54926d816d35d8edf"}, + {file = "wrapt-2.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1fdbb34da15450f2b1d735a0e969c24bdb8d8924892380126e2a293d9902078c"}, + {file = "wrapt-2.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d32794fe940b7000f0519904e247f902f0149edbe6316c710a8562fb6738841"}, + {file = "wrapt-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:386fb54d9cd903ee0012c09291336469eb7b244f7183d40dc3e86a16a4bace62"}, + {file = "wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7b219cb2182f230676308cdcacd428fa837987b89e4b7c5c9025088b8a6c9faf"}, + {file = "wrapt-2.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:641e94e789b5f6b4822bb8d8ebbdfc10f4e4eae7756d648b717d980f657a9eb9"}, + {file = "wrapt-2.0.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe21b118b9f58859b5ebaa4b130dee18669df4bd111daad082b7beb8799ad16b"}, + {file = "wrapt-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:17fb85fa4abc26a5184d93b3efd2dcc14deb4b09edcdb3535a536ad34f0b4dba"}, + {file = "wrapt-2.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b89ef9223d665ab255ae42cc282d27d69704d94be0deffc8b9d919179a609684"}, + {file = "wrapt-2.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a453257f19c31b31ba593c30d997d6e5be39e3b5ad9148c2af5a7314061c63eb"}, + {file = "wrapt-2.0.1-cp312-cp312-win32.whl", hash = "sha256:3e271346f01e9c8b1130a6a3b0e11908049fe5be2d365a5f402778049147e7e9"}, + {file = "wrapt-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:2da620b31a90cdefa9cd0c2b661882329e2e19d1d7b9b920189956b76c564d75"}, + {file = "wrapt-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:aea9c7224c302bc8bfc892b908537f56c430802560e827b75ecbde81b604598b"}, + {file = "wrapt-2.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:47b0f8bafe90f7736151f61482c583c86b0693d80f075a58701dd1549b0010a9"}, + {file = "wrapt-2.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cbeb0971e13b4bd81d34169ed57a6dda017328d1a22b62fda45e1d21dd06148f"}, + {file = "wrapt-2.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb7cffe572ad0a141a7886a1d2efa5bef0bf7fe021deeea76b3ab334d2c38218"}, + {file = "wrapt-2.0.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8d60527d1ecfc131426b10d93ab5d53e08a09c5fa0175f6b21b3252080c70a9"}, + {file = "wrapt-2.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c654eafb01afac55246053d67a4b9a984a3567c3808bb7df2f8de1c1caba2e1c"}, + {file = "wrapt-2.0.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98d873ed6c8b4ee2418f7afce666751854d6d03e3c0ec2a399bb039cd2ae89db"}, + {file = "wrapt-2.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9e850f5b7fc67af856ff054c71690d54fa940c3ef74209ad9f935b4f66a0233"}, + {file = "wrapt-2.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e505629359cb5f751e16e30cf3f91a1d3ddb4552480c205947da415d597f7ac2"}, + {file = "wrapt-2.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2879af909312d0baf35f08edeea918ee3af7ab57c37fe47cb6a373c9f2749c7b"}, + {file = "wrapt-2.0.1-cp313-cp313-win32.whl", hash = "sha256:d67956c676be5a24102c7407a71f4126d30de2a569a1c7871c9f3cabc94225d7"}, + {file = "wrapt-2.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9ca66b38dd642bf90c59b6738af8070747b610115a39af2498535f62b5cdc1c3"}, + {file = "wrapt-2.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:5a4939eae35db6b6cec8e7aa0e833dcca0acad8231672c26c2a9ab7a0f8ac9c8"}, + {file = "wrapt-2.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a52f93d95c8d38fed0669da2ebdb0b0376e895d84596a976c15a9eb45e3eccb3"}, + {file = "wrapt-2.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e54bbf554ee29fcceee24fa41c4d091398b911da6e7f5d7bffda963c9aed2e1"}, + {file = "wrapt-2.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:908f8c6c71557f4deaa280f55d0728c3bca0960e8c3dd5ceeeafb3c19942719d"}, + {file = "wrapt-2.0.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e2f84e9af2060e3904a32cea9bb6db23ce3f91cfd90c6b426757cf7cc01c45c7"}, + {file = "wrapt-2.0.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3612dc06b436968dfb9142c62e5dfa9eb5924f91120b3c8ff501ad878f90eb3"}, + {file = "wrapt-2.0.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d2d947d266d99a1477cd005b23cbd09465276e302515e122df56bb9511aca1b"}, + {file = "wrapt-2.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7d539241e87b650cbc4c3ac9f32c8d1ac8a54e510f6dca3f6ab60dcfd48c9b10"}, + {file = "wrapt-2.0.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:4811e15d88ee62dbf5c77f2c3ff3932b1e3ac92323ba3912f51fc4016ce81ecf"}, + {file = "wrapt-2.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c1c91405fcf1d501fa5d55df21e58ea49e6b879ae829f1039faaf7e5e509b41e"}, + {file = "wrapt-2.0.1-cp313-cp313t-win32.whl", hash = "sha256:e76e3f91f864e89db8b8d2a8311d57df93f01ad6bb1e9b9976d1f2e83e18315c"}, + {file = "wrapt-2.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:83ce30937f0ba0d28818807b303a412440c4b63e39d3d8fc036a94764b728c92"}, + {file = "wrapt-2.0.1-cp313-cp313t-win_arm64.whl", hash = "sha256:4b55cacc57e1dc2d0991dbe74c6419ffd415fb66474a02335cb10efd1aa3f84f"}, + {file = "wrapt-2.0.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5e53b428f65ece6d9dad23cb87e64506392b720a0b45076c05354d27a13351a1"}, + {file = "wrapt-2.0.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ad3ee9d0f254851c71780966eb417ef8e72117155cff04821ab9b60549694a55"}, + {file = "wrapt-2.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7b822c61ed04ee6ad64bc90d13368ad6eb094db54883b5dde2182f67a7f22c0"}, + {file = "wrapt-2.0.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7164a55f5e83a9a0b031d3ffab4d4e36bbec42e7025db560f225489fa929e509"}, + {file = "wrapt-2.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e60690ba71a57424c8d9ff28f8d006b7ad7772c22a4af432188572cd7fa004a1"}, + {file = "wrapt-2.0.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3cd1a4bd9a7a619922a8557e1318232e7269b5fb69d4ba97b04d20450a6bf970"}, + {file = "wrapt-2.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b4c2e3d777e38e913b8ce3a6257af72fb608f86a1df471cb1d4339755d0a807c"}, + {file = "wrapt-2.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3d366aa598d69416b5afedf1faa539fac40c1d80a42f6b236c88c73a3c8f2d41"}, + {file = "wrapt-2.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c235095d6d090aa903f1db61f892fffb779c1eaeb2a50e566b52001f7a0f66ed"}, + {file = "wrapt-2.0.1-cp314-cp314-win32.whl", hash = "sha256:bfb5539005259f8127ea9c885bdc231978c06b7a980e63a8a61c8c4c979719d0"}, + {file = "wrapt-2.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:4ae879acc449caa9ed43fc36ba08392b9412ee67941748d31d94e3cedb36628c"}, + {file = "wrapt-2.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:8639b843c9efd84675f1e100ed9e99538ebea7297b62c4b45a7042edb84db03e"}, + {file = "wrapt-2.0.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:9219a1d946a9b32bb23ccae66bdb61e35c62773ce7ca6509ceea70f344656b7b"}, + {file = "wrapt-2.0.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fa4184e74197af3adad3c889a1af95b53bb0466bced92ea99a0c014e48323eec"}, + {file = "wrapt-2.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c5ef2f2b8a53b7caee2f797ef166a390fef73979b15778a4a153e4b5fedce8fa"}, + {file = "wrapt-2.0.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e042d653a4745be832d5aa190ff80ee4f02c34b21f4b785745eceacd0907b815"}, + {file = "wrapt-2.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2afa23318136709c4b23d87d543b425c399887b4057936cd20386d5b1422b6fa"}, + {file = "wrapt-2.0.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c72328f668cf4c503ffcf9434c2b71fdd624345ced7941bc6693e61bbe36bef"}, + {file = "wrapt-2.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3793ac154afb0e5b45d1233cb94d354ef7a983708cc3bb12563853b1d8d53747"}, + {file = "wrapt-2.0.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fec0d993ecba3991645b4857837277469c8cc4c554a7e24d064d1ca291cfb81f"}, + {file = "wrapt-2.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:949520bccc1fa227274da7d03bf238be15389cd94e32e4297b92337df9b7a349"}, + {file = "wrapt-2.0.1-cp314-cp314t-win32.whl", hash = "sha256:be9e84e91d6497ba62594158d3d31ec0486c60055c49179edc51ee43d095f79c"}, + {file = "wrapt-2.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:61c4956171c7434634401db448371277d07032a81cc21c599c22953374781395"}, + {file = "wrapt-2.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:35cdbd478607036fee40273be8ed54a451f5f23121bd9d4be515158f9498f7ad"}, + {file = "wrapt-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:90897ea1cf0679763b62e79657958cd54eae5659f6360fc7d2ccc6f906342183"}, + {file = "wrapt-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:50844efc8cdf63b2d90cd3d62d4947a28311e6266ce5235a219d21b195b4ec2c"}, + {file = "wrapt-2.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49989061a9977a8cbd6d20f2efa813f24bf657c6990a42967019ce779a878dbf"}, + {file = "wrapt-2.0.1-cp38-cp38-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:09c7476ab884b74dce081ad9bfd07fe5822d8600abade571cb1f66d5fc915af6"}, + {file = "wrapt-2.0.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1a8a09a004ef100e614beec82862d11fc17d601092c3599afd22b1f36e4137e"}, + {file = "wrapt-2.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:89a82053b193837bf93c0f8a57ded6e4b6d88033a499dadff5067e912c2a41e9"}, + {file = "wrapt-2.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f26f8e2ca19564e2e1fdbb6a0e47f36e0efbab1acc31e15471fad88f828c75f6"}, + {file = "wrapt-2.0.1-cp38-cp38-win32.whl", hash = "sha256:115cae4beed3542e37866469a8a1f2b9ec549b4463572b000611e9946b86e6f6"}, + {file = "wrapt-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:c4012a2bd37059d04f8209916aa771dfb564cccb86079072bdcd48a308b6a5c5"}, + {file = "wrapt-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:68424221a2dc00d634b54f92441914929c5ffb1c30b3b837343978343a3512a3"}, + {file = "wrapt-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6bd1a18f5a797fe740cb3d7a0e853a8ce6461cc62023b630caec80171a6b8097"}, + {file = "wrapt-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fb3a86e703868561c5cad155a15c36c716e1ab513b7065bd2ac8ed353c503333"}, + {file = "wrapt-2.0.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5dc1b852337c6792aa111ca8becff5bacf576bf4a0255b0f05eb749da6a1643e"}, + {file = "wrapt-2.0.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c046781d422f0830de6329fa4b16796096f28a92c8aef3850674442cdcb87b7f"}, + {file = "wrapt-2.0.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f73f9f7a0ebd0db139253d27e5fc8d2866ceaeef19c30ab5d69dcbe35e1a6981"}, + {file = "wrapt-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b667189cf8efe008f55bbda321890bef628a67ab4147ebf90d182f2dadc78790"}, + {file = "wrapt-2.0.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:a9a83618c4f0757557c077ef71d708ddd9847ed66b7cc63416632af70d3e2308"}, + {file = "wrapt-2.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e9b121e9aeb15df416c2c960b8255a49d44b4038016ee17af03975992d03931"}, + {file = "wrapt-2.0.1-cp39-cp39-win32.whl", hash = "sha256:1f186e26ea0a55f809f232e92cc8556a0977e00183c3ebda039a807a42be1494"}, + {file = "wrapt-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:bf4cb76f36be5de950ce13e22e7fdf462b35b04665a12b64f3ac5c1bbbcf3728"}, + {file = "wrapt-2.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:d6cc985b9c8b235bd933990cdbf0f891f8e010b65a3911f7a55179cd7b0fc57b"}, + {file = "wrapt-2.0.1-py3-none-any.whl", hash = "sha256:4d2ce1bf1a48c5277d7969259232b57645aae5686dba1eaeade39442277afbca"}, + {file = "wrapt-2.0.1.tar.gz", hash = "sha256:9c9c635e78497cacb81e84f8b11b23e0aacac7a136e73b8e5b2109a1d9fc468f"}, ] [package.extras] @@ -2189,43 +2290,38 @@ propcache = ">=0.2.1" [[package]] name = "zope-interface" -version = "8.0.1" +version = "8.1" description = "Interfaces for Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "zope_interface-8.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fd7195081b8637eeed8d73e4d183b07199a1dc738fb28b3de6666b1b55662570"}, - {file = "zope_interface-8.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f7c4bc4021108847bce763673ce70d0716b08dfc2ba9889e7bad46ac2b3bb924"}, - {file = "zope_interface-8.0.1-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:758803806b962f32c87b31bb18c298b022965ba34fe532163831cc39118c24ab"}, - {file = "zope_interface-8.0.1-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f8e88f35f86bbe8243cad4b2972deef0fdfca0a0723455abbebdc83bbab96b69"}, - {file = "zope_interface-8.0.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7844765695937d9b0d83211220b72e2cf6ac81a08608ad2b58f2c094af498d83"}, - {file = "zope_interface-8.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:64fa7b206dd9669f29d5c1241a768bebe8ab1e8a4b63ee16491f041e058c09d0"}, - {file = "zope_interface-8.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4bd01022d2e1bce4a4a4ed9549edb25393c92e607d7daa6deff843f1f68b479d"}, - {file = "zope_interface-8.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:29be8db8b712d94f1c05e24ea230a879271d787205ba1c9a6100d1d81f06c69a"}, - {file = "zope_interface-8.0.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:51ae1b856565b30455b7879fdf0a56a88763b401d3f814fa9f9542d7410dbd7e"}, - {file = "zope_interface-8.0.1-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d2e7596149cb1acd1d4d41b9f8fe2ffc0e9e29e2e91d026311814181d0d9efaf"}, - {file = "zope_interface-8.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b2737c11c34fb9128816759864752d007ec4f987b571c934c30723ed881a7a4f"}, - {file = "zope_interface-8.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:cf66e4bf731aa7e0ced855bb3670e8cda772f6515a475c6a107bad5cb6604103"}, - {file = "zope_interface-8.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:115f27c1cc95ce7a517d960ef381beedb0a7ce9489645e80b9ab3cbf8a78799c"}, - {file = "zope_interface-8.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af655c573b84e3cb6a4f6fd3fbe04e4dc91c63c6b6f99019b3713ef964e589bc"}, - {file = "zope_interface-8.0.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:23f82ef9b2d5370750cc1bf883c3b94c33d098ce08557922a3fbc7ff3b63dfe1"}, - {file = "zope_interface-8.0.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35a1565d5244997f2e629c5c68715b3d9d9036e8df23c4068b08d9316dcb2822"}, - {file = "zope_interface-8.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:029ea1db7e855a475bf88d9910baab4e94d007a054810e9007ac037a91c67c6f"}, - {file = "zope_interface-8.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0beb3e7f7dc153944076fcaf717a935f68d39efa9fce96ec97bafcc0c2ea6cab"}, - {file = "zope_interface-8.0.1-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:c7cc027fc5c61c5d69e5080c30b66382f454f43dc379c463a38e78a9c6bab71a"}, - {file = "zope_interface-8.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fcf9097ff3003b7662299f1c25145e15260ec2a27f9a9e69461a585d79ca8552"}, - {file = "zope_interface-8.0.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6d965347dd1fb9e9a53aa852d4ded46b41ca670d517fd54e733a6b6a4d0561c2"}, - {file = "zope_interface-8.0.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9a3b8bb77a4b89427a87d1e9eb969ab05e38e6b4a338a9de10f6df23c33ec3c2"}, - {file = "zope_interface-8.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:87e6b089002c43231fb9afec89268391bcc7a3b66e76e269ffde19a8112fb8d5"}, - {file = "zope_interface-8.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:64a43f5280aa770cbafd0307cb3d1ff430e2a1001774e8ceb40787abe4bb6658"}, - {file = "zope_interface-8.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b84464a9fcf801289fa8b15bfc0829e7855d47fb4a8059555effc6f2d1d9a613"}, - {file = "zope_interface-8.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7b915cf7e747b5356d741be79a153aa9107e8923bc93bcd65fc873caf0fb5c50"}, - {file = "zope_interface-8.0.1-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:110c73ddf974b369ef3c6e7b0d87d44673cf4914eba3fe8a33bfb21c6c606ad8"}, - {file = "zope_interface-8.0.1-cp39-cp39-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9e9bdca901c1bcc34e438001718512c65b3b8924aabcd732b6e7a7f0cd715f17"}, - {file = "zope_interface-8.0.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bbd22d4801ad3e8ec704ba9e3e6a4ac2e875e4d77e363051ccb76153d24c5519"}, - {file = "zope_interface-8.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:a0016ca85f93b938824e2f9a43534446e95134a2945b084944786e1ace2020bc"}, - {file = "zope_interface-8.0.1.tar.gz", hash = "sha256:eba5610d042c3704a48222f7f7c6ab5b243ed26f917e2bc69379456b115e02d1"}, + {file = "zope_interface-8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b6370bc29799223e9a4756e89f358cbfba8e4112fc5046403c849bcc52f1ff32"}, + {file = "zope_interface-8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:638e83a5c15a337e52d36620478d49dbf206a46d34bcc75ff03211e94b12267d"}, + {file = "zope_interface-8.1-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b88e0516bc7f3979839f44adfdbfa8a57d81154a161cbd64642ace22892969af"}, + {file = "zope_interface-8.1-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3c17d683a11cf09eebf332e4771fc2973c15f8428a9a4fc478b39a422f32157b"}, + {file = "zope_interface-8.1-cp310-cp310-win_amd64.whl", hash = "sha256:e826eeda579fcee2624eea6014877103ca7510bd1353bfae1d64e1cabd691d35"}, + {file = "zope_interface-8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db263a60c728c86e6a74945f3f74cfe0ede252e726cf71e05a0c7aca8d9d5432"}, + {file = "zope_interface-8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfa89e5b05b7a79ab34e368293ad008321231e321b3ce4430487407b4fe3450a"}, + {file = "zope_interface-8.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:87eaf011912a06ef86da70aba2ca0ddb68b8ab84a7d1da6b144a586b70a61bca"}, + {file = "zope_interface-8.1-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10f06d128f1c181ded3af08c5004abcb3719c13a976ce9163124e7eeded6899a"}, + {file = "zope_interface-8.1-cp311-cp311-win_amd64.whl", hash = "sha256:17fb5382a4b9bd2ea05648a457c583e5a69f0bfa3076ed1963d48bc42a2da81f"}, + {file = "zope_interface-8.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8aee385282ab2a9813171b15f41317e22ab0a96cf05e9e9e16b29f4af8b6feb"}, + {file = "zope_interface-8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af651a87f950a13e45fd49510111f582717fb106a63d6a0c2d3ba86b29734f07"}, + {file = "zope_interface-8.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:80ed7683cf337f3b295e4b96153e2e87f12595c218323dc237c0147a6cc9da26"}, + {file = "zope_interface-8.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb9a7a45944b28c16d25df7a91bf2b9bdb919fa2b9e11782366a1e737d266ec1"}, + {file = "zope_interface-8.1-cp312-cp312-win_amd64.whl", hash = "sha256:fc5e120e3618741714c474b2427d08d36bd292855208b4397e325bd50d81439d"}, + {file = "zope_interface-8.1-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:afc2bbd1c5d9ecda8c2114a2793bda811ea175742c93dc77a02d5ed49c7a732e"}, + {file = "zope_interface-8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:827a9ad0ebfef467f047d17d83868f66eb44feb00daf2e3dbd404b6f1cb08858"}, + {file = "zope_interface-8.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:3913ad3fced813c4af551aae4511dc000c6d4df648430060593aec851bb5fc19"}, + {file = "zope_interface-8.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c073ba91caeff93aaf9569c9c3ec3fd790290329cefa69eb6dcb43eabb832c2c"}, + {file = "zope_interface-8.1-cp313-cp313-win_amd64.whl", hash = "sha256:eef72e1726fa055510c1442c3bc3614a4fd0cd056e26c824c26a27d02ad9cfdd"}, + {file = "zope_interface-8.1-cp314-cp314-macosx_10_9_x86_64.whl", hash = "sha256:04ee726e6939be1c2924d8513a17eb819217129e25d3217ca009cf9c8832e3fe"}, + {file = "zope_interface-8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b931e7241d089df18e0e34a7bfba019bc89c5bf49af8894b4540a0857cb70b79"}, + {file = "zope_interface-8.1-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c09a4a25007f113c82fcdf4b40ffcbcfaa3f26e428d9cbb9ea322fe27149bc39"}, + {file = "zope_interface-8.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:26c515e2f92da839d531ef86a3bbaafc3f22bd9aea5eb0c1dc397f67abff56d7"}, + {file = "zope_interface-8.1-cp314-cp314-win_amd64.whl", hash = "sha256:76dfe5580cac661b0aad5f0d308c09d25b567c908a1695bd68986507fd61b61d"}, + {file = "zope_interface-8.1.tar.gz", hash = "sha256:a02ee40770c6a2f3d168a8f71f09b62aec3e4fb366da83f8e849dbaa5b38d12f"}, ] [package.extras] @@ -2236,4 +2332,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.1" python-versions = ">=3.11,<4.0" -content-hash = "8112bc1f4c5d808f6a1a46c8070b9262c44f28273b1140fcb337fedab0d9390f" +content-hash = "85f08de74a72c1de316ab7fb82659210a074e75eb6d867b83ae872175ca16086" diff --git a/pyproject.toml b/pyproject.toml index d631993..d3a0430 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,3 +46,4 @@ pytest-benchmark = "^5.1" mock = "^5.1" assertpy = "^1.1" testcontainers = {extras = ["postgres"], version = "^4.10"} +pytest-twisted = "^1.14.3" diff --git a/tests/conftest.py b/tests/conftest.py index 05d5cef..df3ddb9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,11 @@ import datetime -import os -from typing import Generator, Callable +from typing import Callable import blitzortung.db import psycopg2 import pyproj import pytest +from testcontainers.postgres import PostgresContainer @pytest.fixture @@ -23,21 +23,25 @@ def time_interval(now) -> blitzortung.db.query.TimeInterval: def utm_eu() -> pyproj.CRS: return pyproj.CRS('epsg:32633') + @pytest.fixture def grid_factory(utm_eu): return blitzortung.geom.GridFactory(10, 15, 40, 50, utm_eu, 15, 45) + @pytest.fixture def utm_north(): - return pyproj.CRS('epsg:32631') # UTM 31 N / WGS84 + return pyproj.CRS('epsg:32631') # UTM 31 N / WGS84 + @pytest.fixture def utm_south(): return pyproj.CRS('epsg:32731') # UTM 31 S / WGS84 + @pytest.fixture def local_grid_factory(utm_north, utm_south) -> Callable[[int, int, int], blitzortung.geom.GridFactory]: - def _factory(x : int, y: int, data_area=5): + def _factory(x: int, y: int, data_area=5): data_area_size_factor = 3 size = data_area * data_area_size_factor reference_longitude = (x - 1) * data_area @@ -55,8 +59,85 @@ def _factory(x : int, y: int, data_area=5): reference_latitude + size / 2.0 ) return local_grid + return _factory + @pytest.fixture def global_grid_factory(utm_eu): return blitzortung.geom.GridFactory(-180, 180, -90, 90, utm_eu, 11, 48) + + +@pytest.fixture(scope="session") +def postgres_container(request) -> PostgresContainer: + return create_postgres_container(request) + +def create_postgres_container(request) -> PostgresContainer: + image = "postgis/postgis:16-3.5" + postgres = PostgresContainer(image) + + def remove_container(): + postgres.stop() + + if request is not None: + request.addfinalizer(remove_container) + + postgres.start() + + return postgres + + +@pytest.fixture(scope="session") +def connection_string(postgres_container: PostgresContainer): + yield f"host={postgres_container.get_container_host_ip()} dbname={postgres_container.dbname} user={postgres_container.username} password={postgres_container.password} port={postgres_container.get_exposed_port(5432)}" + + +@pytest.fixture +def connection_pool(connection_string): + connection_pool = psycopg2.pool.ThreadedConnectionPool(4, 50, connection_string) + yield connection_pool + connection_pool.closeall() + + +@pytest.fixture +def db_strikes(connection_pool): + conn = connection_pool.getconn() + + with conn.cursor() as cur: + cur.execute(""" + CREATE TABLE strikes + ( + id bigserial, + "timestamp" timestamptz, + nanoseconds SMALLINT, + geog GEOGRAPHY(Point), + PRIMARY KEY (id) + ); + ALTER TABLE strikes + ADD COLUMN altitude SMALLINT; + ALTER TABLE strikes + ADD COLUMN region SMALLINT; + ALTER TABLE strikes + ADD COLUMN amplitude REAL; + ALTER TABLE strikes + ADD COLUMN error2d SMALLINT; + ALTER TABLE strikes + ADD COLUMN stationcount SMALLINT; + CREATE INDEX strikes_geog ON strikes USING gist(geog); + CREATE INDEX strikes_timestamp ON strikes USING btree("timestamp"); + """) + conn.commit() + + query_builder = blitzortung.db.query_builder.Strike() + strike_builder = blitzortung.builder.strike.Strike() + mapper = blitzortung.db.mapper.Strike(strike_builder) + + strike = blitzortung.db.table.Strike(connection_pool, query_builder, mapper) + yield strike + strike.close() + + with conn.cursor() as cur: + cur.execute("""DROP TABLE strikes""") + conn.commit() + + connection_pool.putconn(conn) diff --git a/tests/db/test_db.py b/tests/db/test_db.py index fc75b20..d5087c8 100644 --- a/tests/db/test_db.py +++ b/tests/db/test_db.py @@ -12,48 +12,9 @@ import blitzortung from blitzortung.service.general import create_time_interval -image = "postgres:16-alpine" -image = "postgis/postgis:16-3.5" -postgres = PostgresContainer(image) -@pytest.fixture(scope="module", autouse=True) -def setup(request): - postgres.start() - def remove_container(): - postgres.stop() - - request.addfinalizer(remove_container) - os.environ["DB_CONN"] = postgres.get_connection_url() - os.environ["DB_HOST"] = postgres.get_container_host_ip() - os.environ["DB_PORT"] = str(postgres.get_exposed_port(5432)) - os.environ["DB_USERNAME"] = postgres.username - os.environ["DB_PASSWORD"] = postgres.password - os.environ["DB_NAME"] = postgres.dbname - - -@pytest.fixture -def connection_string() -> str: - host = os.getenv("DB_HOST", "localhost") - port = os.getenv("DB_PORT", "5432") - username = os.getenv("DB_USERNAME", "postgres") - password = os.getenv("DB_PASSWORD", "postgres") - database = os.getenv("DB_NAME", "postgres") - return f"host={host} dbname={database} user={username} password={password} port={port}" - - -@pytest.fixture -def conn(connection_string): - with psycopg2.connect(connection_string) as connection: - yield connection - - -@pytest.fixture -def connection_pool(connection_string): - connection_pool = psycopg2.pool.ThreadedConnectionPool(4, 50, connection_string) - yield connection_pool - connection_pool.closeall() class BaseForTest(blitzortung.db.table.Base): @@ -124,11 +85,14 @@ def test_from_timezone_to_bare_utc(self, base): assert_that(base.from_timezone_to_bare_utc(time)).is_equal_to(utc_time) -def test_db_version(conn): - with conn.cursor() as cur: - cur.execute("""SELECT version()""") - foo = cur.fetchone() - print(foo) +def test_db_version(connection_string): + with psycopg2.connect(connection_string) as connection: + with connection.cursor() as cur: + cur.execute("""SELECT version()""") + result = cur.fetchone() + assert result is not None + assert len(result) == 1 + assert result[0].startswith("PostgreSQL") def test_db_version_pool(connection_pool): @@ -140,48 +104,6 @@ def test_db_version_pool(connection_pool): connection_pool.putconn(conn) -@pytest.fixture -def strikes(connection_pool): - conn = connection_pool.getconn() - - with conn.cursor() as cur: - cur.execute(""" - CREATE TABLE strikes - ( - id bigserial, - "timestamp" timestamptz, - nanoseconds SMALLINT, - geog GEOGRAPHY(Point), - PRIMARY KEY (id) - ); - ALTER TABLE strikes - ADD COLUMN altitude SMALLINT; - ALTER TABLE strikes - ADD COLUMN region SMALLINT; - ALTER TABLE strikes - ADD COLUMN amplitude REAL; - ALTER TABLE strikes - ADD COLUMN error2d SMALLINT; - ALTER TABLE strikes - ADD COLUMN stationcount SMALLINT; - CREATE INDEX strikes_geog ON strikes USING gist(geog); - CREATE INDEX strikes_timestamp ON strikes USING btree("timestamp"); - """) - conn.commit() - - query_builder = blitzortung.db.query_builder.Strike() - strike_builder = blitzortung.builder.strike.Strike() - mapper = blitzortung.db.mapper.Strike(strike_builder) - - strike = blitzortung.db.table.Strike(connection_pool, query_builder, mapper) - yield strike - strike.close() - - with conn.cursor() as cur: - cur.execute("""DROP TABLE strikes""") - conn.commit() - - connection_pool.putconn(conn) @pytest.fixture @@ -199,78 +121,78 @@ def factory(x: float, y: float, offset: Optional[datetime.timedelta] = None) -> return factory -def test_empty_query(strikes, strike_factory, now): - result = strikes.select() +def test_empty_query(db_strikes, strike_factory, now): + result = db_strikes.select() print("result", list(result)) assert len(list(result)) == 0 -def test_insert_with_rollback(strikes, strike_factory, time_interval): +def test_insert_with_rollback(db_strikes, strike_factory, time_interval): strike = strike_factory(11, 49) - strikes.insert(strike) - strikes.rollback() + db_strikes.insert(strike) + db_strikes.rollback() - result = strikes.select(time_interval=time_interval) + result = db_strikes.select(time_interval=time_interval) assert len(list(result)) == 0 -def test_insert_without_commit(strikes, strike_factory, time_interval): +def test_insert_without_commit(db_strikes, strike_factory, time_interval): strike = strike_factory(11, 49) - strikes.insert(strike) + db_strikes.insert(strike) - result = strikes.select(time_interval=time_interval) + result = db_strikes.select(time_interval=time_interval) assert len(list(result)) == 1 -def test_insert_and_select_strike(strikes, strike_factory, time_interval): +def test_insert_and_select_strike(db_strikes, strike_factory, time_interval): strike = strike_factory(11, 49) - strikes.insert(strike) - strikes.commit() + db_strikes.insert(strike) + db_strikes.commit() - result = strikes.select(time_interval=time_interval) + result = db_strikes.select(time_interval=time_interval) assert len(list(result)) == 1 -def test_get_latest_time(strikes, strike_factory, time_interval): +def test_get_latest_time(db_strikes, strike_factory, time_interval): strike = strike_factory(11, 49) - strikes.insert(strike) - strikes.commit() + db_strikes.insert(strike) + db_strikes.commit() - result = strikes.get_latest_time() + result = db_strikes.get_latest_time() assert result == strike.timestamp -def test_get_latest_time_with_region(strikes, strike_factory, time_interval): +def test_get_latest_time_with_region(db_strikes, strike_factory, time_interval): strike = strike_factory(11, 49) - strikes.insert(strike, 5) - strikes.commit() + db_strikes.insert(strike, 5) + db_strikes.commit() - result = strikes.get_latest_time() + result = db_strikes.get_latest_time() assert result == strike.timestamp -def test_get_latest_time_with_region_match(strikes, strike_factory, time_interval): +def test_get_latest_time_with_region_match(db_strikes, strike_factory, time_interval): strike = strike_factory(11, 49) - strikes.insert(strike, 5) - strikes.commit() + db_strikes.insert(strike, 5) + db_strikes.commit() - result = strikes.get_latest_time(5) + result = db_strikes.get_latest_time(5) assert result == strike.timestamp -def test_get_latest_time_with_region_mismatch(strikes, strike_factory, time_interval): +def test_get_latest_time_with_region_mismatch(db_strikes, strike_factory, time_interval): strike = strike_factory(11, 49) - strikes.insert(strike, 5) - strikes.commit() + db_strikes.insert(strike, 5) + db_strikes.commit() - result = strikes.get_latest_time(4) + result = db_strikes.get_latest_time(4) assert result is None @@ -281,43 +203,43 @@ def test_get_latest_time_with_region_mismatch(strikes, strike_factory, time_inte (25000, (4, 2, 1, 0)), (10000, (11, 6, 1, 0)), (5000, (23, 11, 1, 0))]) -def test_grid_query(strikes, strike_factory, grid_factory, time_interval, utm_eu, raster_size, expected): - strikes.insert(strike_factory(11.5, 49.5)) - strikes.commit() +def test_grid_query(db_strikes, strike_factory, grid_factory, time_interval, utm_eu, raster_size, expected): + db_strikes.insert(strike_factory(11.5, 49.5)) + db_strikes.commit() grid = grid_factory.get_for(raster_size) - result = strikes.select_grid(grid, 0, time_interval=time_interval) + result = db_strikes.select_grid(grid, 0, time_interval=time_interval) assert result == (expected,) -def test_issues_with_missing_data_on_grid_query(strikes, strike_factory, local_grid_factory, time_interval, utm_eu): +def test_issues_with_missing_data_on_grid_query(db_strikes, strike_factory, local_grid_factory, time_interval, utm_eu): grid_size = 100000 for i in range(10): - strikes.insert(strike_factory(-90 + i,15)) - strikes.commit() + db_strikes.insert(strike_factory(-90 + i, 15)) + db_strikes.commit() # get_local_strikes_grid(-6, 0, 60, 10000, 0, >= 0, 15) local_grid1 = local_grid_factory(-6,0,15) grid1 = local_grid1.get_for(grid_size) - result1 = strikes.select_grid(grid1, 0, time_interval=time_interval) + result1 = db_strikes.select_grid(grid1, 0, time_interval=time_interval) assert len(result1) == 10 # get_local_strikes_grid(-5, 0, 60, 10000, 0, >=0, 15) local_grid2 = local_grid_factory(-5,0,15) grid2 = local_grid2.get_for(grid_size) - result2 = strikes.select_grid(grid2, 0, time_interval=time_interval) + result2 = db_strikes.select_grid(grid2, 0, time_interval=time_interval) assert len(result2) == 10 -def test_grid_query_with_count_threshold(strikes, strike_factory, grid_factory, time_interval, utm_eu): - strikes.insert(strike_factory(11.5, 49.5)) - strikes.insert(strike_factory(12.5, 49.5)) - strikes.insert(strike_factory(12.5, 49.5)) - strikes.commit() +def test_grid_query_with_count_threshold(db_strikes, strike_factory, grid_factory, time_interval, utm_eu): + db_strikes.insert(strike_factory(11.5, 49.5)) + db_strikes.insert(strike_factory(12.5, 49.5)) + db_strikes.insert(strike_factory(12.5, 49.5)) + db_strikes.commit() grid = grid_factory.get_for(10000) - result = strikes.select_grid(grid, 1, time_interval=time_interval) + result = db_strikes.select_grid(grid, 1, time_interval=time_interval) assert result == ((19, 6, 2, 0),) @@ -327,19 +249,19 @@ def test_grid_query_with_count_threshold(strikes, strike_factory, grid_factory, (25000, (36, 553, 1, 0)), (10000, (90, 1383, 1, 0)), (5000, (181, 2766, 1, 0))]) -def test_global_grid_query(strikes, strike_factory, global_grid_factory, time_interval, utm_eu, raster_size, expected): - strikes.insert(strike_factory(11.5, 49.5)) - strikes.commit() +def test_global_grid_query(db_strikes, strike_factory, global_grid_factory, time_interval, utm_eu, raster_size, expected): + db_strikes.insert(strike_factory(11.5, 49.5)) + db_strikes.commit() grid = global_grid_factory.get_for(raster_size) - result = strikes.select_global_grid(grid, 0, time_interval=time_interval) + result = db_strikes.select_global_grid(grid, 0, time_interval=time_interval) assert result == (expected,) -def test_empty_query2(strikes, strike_factory): - result = strikes.select() +def test_empty_query2(db_strikes, strike_factory): + result = db_strikes.select() assert len(list(result)) == 0 @@ -350,16 +272,16 @@ def test_empty_query2(strikes, strike_factory): (-15, [0, 8, 7, 6, 5, 4]), (-30, [0, 0, 0, 0, 8, 7]) ]) -def test_histogram_query(strikes, strike_factory, minute_offset, expected): +def test_histogram_query(db_strikes, strike_factory, minute_offset, expected): for offset in range(8): timedelta = datetime.timedelta(minutes=offset * 5, seconds=1) print(timedelta) for _ in range(offset + 1): - strikes.insert(strike_factory(11.5, 49.5, timedelta)) - strikes.commit() + db_strikes.insert(strike_factory(11.5, 49.5, timedelta)) + db_strikes.commit() query_time_interval = create_time_interval(30, minute_offset) - histogram = strikes.select_histogram(query_time_interval) + histogram = db_strikes.select_histogram(query_time_interval) assert histogram == expected diff --git a/tests/service/test_histogram.py b/tests/service/test_histogram.py index d008ca9..ce8a6a6 100644 --- a/tests/service/test_histogram.py +++ b/tests/service/test_histogram.py @@ -1,7 +1,9 @@ import time import pytest +import pytest_twisted from mock import Mock, call +from twisted.internet import defer from blitzortung.service.general import create_time_interval from blitzortung.service.histogram import HistogramQuery @@ -20,25 +22,23 @@ def connection(): class TestHistogramQuery: @pytest.fixture - def uut(self, query_builder): + def uut(self, query_builder) -> HistogramQuery: return HistogramQuery(query_builder) + @pytest_twisted.inlineCallbacks def test_create(self, uut, query_builder, connection): + connection.runQuery.return_value = defer.succeed([[-4, 5], [-3, 3], [-2, 1], [-1, 2], [0, 4]]) + query_time_interval = create_time_interval(30, 0) - result = uut.create(query_time_interval, connection) - query_builder.histogram_query.assert_called_once_with("strikes", query_time_interval, 5, None, None) + pool = defer.succeed(connection) + result = yield uut.create(query_time_interval, pool) + assert result == [0,5,3,1,2,4] + query_builder.histogram_query.assert_called_once_with("strikes", query_time_interval, 5, None, None) query = query_builder.histogram_query.return_value + assert connection.runQuery.call_args == call(str(query), query.get_parameters.return_value) - assert result == connection.runQuery.return_value - - assert result.addCallback.call_args.args == (uut.build_result,) - assert result.addCallback.call_args.kwargs["minutes"] == 30 - assert result.addCallback.call_args.kwargs["bin_size"] == 5 - now = time.time() - assert result.addCallback.call_args.kwargs["reference_time"] > now - 0.005 - assert result.addCallback.call_args.kwargs["reference_time"] < now + 0.005 def test_build_result(self, uut): reference_time = time.time() - 0.5 diff --git a/tests/service/test_service_db.py b/tests/service/test_service_db.py new file mode 100644 index 0000000..a2207b8 --- /dev/null +++ b/tests/service/test_service_db.py @@ -0,0 +1,86 @@ +# -*- coding: utf8 -*- + +""" + + Copyright 2025 Andreas Würl + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +""" + +from unittest.mock import Mock, patch, MagicMock + +import psycopg2.extras +import pytest +import pytest_twisted +from assertpy import assert_that +import time + +import blitzortung.service.db + + +class TestLoggingDetector: + + def test_start_reconnecting_logs_error(self): + detector = blitzortung.service.db.LoggingDetector() + mock_failure = Mock() + mock_failure.value = Exception("Connection lost") + + with patch('builtins.print') as mock_print: + with patch.object(detector.__class__.__bases__[0], 'startReconnecting', return_value=None): + detector.startReconnecting(mock_failure) + + mock_print.assert_called_once() + assert_that(str(mock_print.call_args[0][0])).contains("database connection is down") + assert_that(str(mock_print.call_args[0][0])).contains("Connection lost") + + def test_reconnect_logs_message(self): + detector = blitzortung.service.db.LoggingDetector() + + with patch('builtins.print') as mock_print: + with patch.object(detector.__class__.__bases__[0], 'reconnect', return_value=None): + detector.reconnect() + + mock_print.assert_called_once() + assert_that(str(mock_print.call_args[0][0])).contains("reconnecting") + + def test_connection_recovered_logs_message(self): + detector = blitzortung.service.db.LoggingDetector() + + with patch('builtins.print') as mock_print: + with patch.object(detector.__class__.__bases__[0], 'connectionRecovered', return_value=None): + detector.connectionRecovered() + + mock_print.assert_called_once() + assert_that(str(mock_print.call_args[0][0])).contains("connection recovered") + + +@pytest.fixture +def config(connection_string: str): + with patch('blitzortung.config.config') as mock_config: + mock_config.return_value.get_db_connection_string.return_value = connection_string + yield mock_config + + +@pytest_twisted.inlineCallbacks +def test_database(config, db_strikes): + deferred_pool = blitzortung.service.db.create_connection_pool() + async_connection_pool = yield deferred_pool + + query = async_connection_pool.runQuery("select count(*) from strikes;".encode()) + + result = yield query + + print("ready", result) + + assert result[0]['count'] == 0 From ed4cfd688187bffd45385204add3fc4123d6df60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 21:37:00 +0100 Subject: [PATCH 02/13] allow to execute in test mode --- blitzortung/cli/start_webservice.py | 6 ++++-- blitzortung/cli/webservice.py | 13 +++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/blitzortung/cli/start_webservice.py b/blitzortung/cli/start_webservice.py index 35a499f..d51ba8e 100755 --- a/blitzortung/cli/start_webservice.py +++ b/blitzortung/cli/start_webservice.py @@ -7,9 +7,11 @@ def main(): target_dir = os.path.dirname(os.path.abspath(__file__)) - pid_file = "/var/run/bo-webservice.pid" + args = ["twistd"] + if not os.environ.get("BLITZORTUNG_TEST"): + args += ["--pidfile", "/var/run/bo-webservice.pid"] - sys.argv = ["twistd", "--pidfile", pid_file, "-oy", os.path.join(target_dir, "webservice.py")] + sys.argv = args + ["-oy", os.path.join(target_dir, "webservice.py")] sys.exit(run()) diff --git a/blitzortung/cli/webservice.py b/blitzortung/cli/webservice.py index a56147e..a703349 100755 --- a/blitzortung/cli/webservice.py +++ b/blitzortung/cli/webservice.py @@ -524,18 +524,19 @@ def emit(self, event_dict): application = service.Application("Blitzortung.org JSON-RPC Server") -log_directory = "/var/log/blitzortung" +if os.environ.get('BLITZORTUNG_TEST'): + import tempfile + log_directory = tempfile.mkdtemp() + print("LOG_DIR", log_directory) +else: + log_directory = "/var/log/blitzortung" if os.path.exists(log_directory): logfile = DailyLogFile("webservice.log", log_directory) application.setComponent(ILogObserver, LogObserver(logfile).emit) else: log_directory = None -from twisted.internet import defer, reactor - -# Block until connection pool is ready -connection_pool_deferred = create_connection_pool() -connection_pool = defer.blockingCallFromThread(reactor, lambda: connection_pool_deferred) +connection_pool = create_connection_pool() root = Blitzortung(connection_pool, log_directory) From cf029b7b3ea00c03d71a8e948be29e764cec8452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 22:03:56 +0100 Subject: [PATCH 03/13] adapt grid query calls --- blitzortung/service/histogram.py | 11 +++----- blitzortung/service/strike_grid.py | 10 +++---- tests/service/test_strike_grid.py | 45 ++++++++++++++++++++++-------- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/blitzortung/service/histogram.py b/blitzortung/service/histogram.py index f5561f3..2fc8ef5 100644 --- a/blitzortung/service/histogram.py +++ b/blitzortung/service/histogram.py @@ -35,16 +35,13 @@ def __init__(self, strike_query_builder: db.query_builder.Strike): def create(self, time_interval: TimeInterval, deferred_pool: Deferred, region=None, envelope=None): reference_time = time.time() - histogram_query = self.strike_query_builder.histogram_query(db.table.Strike.table_name, time_interval, 5, region, envelope) + query = self.strike_query_builder.histogram_query(db.table.Strike.table_name, time_interval, 5, region, envelope) - def execute_query(connection, query: SelectQuery): - return connection.runQuery(str(query), query.get_parameters()) + result = deferred_pool.addCallback(lambda connection: connection.runQuery(str(query), query.get_parameters())) - histogram_query = deferred_pool.addCallback(execute_query, histogram_query) - - histogram_query.addCallback(self.build_result, minutes=time_interval.minutes(), bin_size=5, + result.addCallback(self.build_result, minutes=time_interval.minutes(), bin_size=5, reference_time=reference_time) - return histogram_query + return result @staticmethod def build_result(query_result, minutes, bin_size, reference_time): diff --git a/blitzortung/service/strike_grid.py b/blitzortung/service/strike_grid.py index e8fa10f..0a63f68 100644 --- a/blitzortung/service/strike_grid.py +++ b/blitzortung/service/strike_grid.py @@ -55,16 +55,16 @@ class StrikeGridQuery: def __init__(self, strike_query_builder: db.query_builder.Strike): self.strike_query_builder = strike_query_builder - def create(self, grid_parameters: GridParameters, time_interval: TimeInterval, connection, statsd_client): + def create(self, grid_parameters: GridParameters, time_interval: TimeInterval, deferred_pool: Deferred, statsd_client): state = StrikeGridState(statsd_client, grid_parameters, time_interval) query = self.strike_query_builder.grid_query(db.table.Strike.table_name, grid_parameters.grid, time_interval=time_interval, count_threshold=grid_parameters.count_threshold) - grid_query = connection.runQuery(str(query), query.get_parameters()) - grid_query.addCallback(self.build_result, state=state) - grid_query.addErrback(log.err) - return grid_query, state + result = deferred_pool.addCallback(lambda connection: connection.runQuery(str(query), query.get_parameters())) + result.addCallback(self.build_result, state=state) + result.addErrback(log.err) + return result, state @staticmethod def build_result(results, state: StrikeGridState): diff --git a/tests/service/test_strike_grid.py b/tests/service/test_strike_grid.py index 27afd9b..361796f 100644 --- a/tests/service/test_strike_grid.py +++ b/tests/service/test_strike_grid.py @@ -2,8 +2,10 @@ from typing import Callable import pytest +import pytest_twisted from assertpy import assert_that from mock import Mock, call +from twisted.internet import defer from blitzortung.service.strike_grid import StrikeGridQuery, GridParameters, StrikeGridState from tests.conftest import time_interval @@ -30,6 +32,10 @@ class TestStrikeGridQuery: def uut(self, query_builder): return StrikeGridQuery(query_builder) + @pytest.fixture + def now(self): + return datetime.datetime.now(tz=datetime.timezone.utc) + @pytest.fixture def grid_parameters_factory(self, grid_factory) -> Callable[[int, int], GridParameters]: def _factory(raster_baselength, region=1): @@ -41,20 +47,28 @@ def _factory(raster_baselength, region=1): return _factory - def test_create(self, uut, grid_parameters_factory, time_interval, query_builder, connection, statsd_client): + @pytest_twisted.inlineCallbacks + def test_create(self, uut, now, grid_parameters_factory, time_interval, query_builder, connection, statsd_client): grid_parameters = grid_parameters_factory(10000) + connection.runQuery.return_value = defer.succeed([{ + "rx": 7, + "ry": 9, + "strike_count": 3, + "timestamp": now - datetime.timedelta(seconds=65) + }]) + pool = defer.succeed(connection) + + deferred_result, state = uut.create(grid_parameters, time_interval, pool, statsd_client) + result = yield deferred_result - result, state = uut.create(grid_parameters, time_interval, connection, statsd_client) + assert result == ((7, 102, 3, -66),) query_builder.grid_query.assert_called_once_with("strikes", grid_parameters.grid, time_interval=time_interval, count_threshold=grid_parameters.count_threshold) query = query_builder.grid_query.return_value assert connection.runQuery.call_args == call(str(query), query.get_parameters.return_value) - assert result == connection.runQuery.return_value - - assert result.addCallback.call_args.args == (uut.build_result,) - assert result.addCallback.call_args.kwargs["state"] == state + assert state.grid_parameters == grid_parameters def test_build_result(self, uut, statsd_client, grid_parameters_factory, time_interval, ): grid_parameters = grid_parameters_factory(10000) @@ -122,20 +136,27 @@ def _factory(raster_baselength): return _factory - def test_create(self, uut, grid_parameters_factory, time_interval, query_builder, connection, statsd_client): + @pytest_twisted.inlineCallbacks + def test_create(self, uut, now, grid_parameters_factory, time_interval, query_builder, connection, statsd_client): grid_parameters = grid_parameters_factory(10000) + connection.runQuery.return_value = defer.succeed([{ + "rx": 7, + "ry": 9, + "strike_count": 3, + "timestamp": now - datetime.timedelta(seconds=65) + }]) + pool = defer.succeed(connection) + + deferred_result, state = uut.create(grid_parameters, time_interval, pool, statsd_client) + result = yield deferred_result - result, state = uut.create(grid_parameters, time_interval, connection, statsd_client) + assert result == ((7, 1898, 3, -66),) query_builder.grid_query.assert_called_once_with("strikes", grid_parameters.grid, time_interval=time_interval, count_threshold=grid_parameters.count_threshold) query = query_builder.grid_query.return_value assert connection.runQuery.call_args == call(str(query), query.get_parameters.return_value) - assert result == connection.runQuery.return_value - - assert result.addCallback.call_args.args == (uut.build_result,) - assert result.addCallback.call_args.kwargs["state"] == state def test_build_result(self, uut, statsd_client, grid_parameters_factory, time_interval, ): grid_parameters = grid_parameters_factory(10000) From 0f9ff8944180ec8dcff6579c035b94cdd7c05b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 22:08:00 +0100 Subject: [PATCH 04/13] wip --- blitzortung/cli/webservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blitzortung/cli/webservice.py b/blitzortung/cli/webservice.py index a703349..03e4a54 100755 --- a/blitzortung/cli/webservice.py +++ b/blitzortung/cli/webservice.py @@ -450,7 +450,7 @@ def fix_bad_accept_header(self, request, user_agent): def get_histogram(self, time_interval: TimeInterval, region=None, envelope=None): return self.histogram_cache.get(self.histogram_query.create, time_interval=time_interval, - connection=self.connection_pool, + deferred_pool=self.connection_pool, region=region, envelope=envelope) From ca9f4337e007f0de9bde6872bbb8af7538831ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 22:15:23 +0100 Subject: [PATCH 05/13] wip --- blitzortung/cli/webservice.py | 2 +- blitzortung/service/db.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/blitzortung/cli/webservice.py b/blitzortung/cli/webservice.py index 03e4a54..0c77714 100755 --- a/blitzortung/cli/webservice.py +++ b/blitzortung/cli/webservice.py @@ -29,7 +29,7 @@ from txjsonrpc_ng.web.jsonrpc import with_request try: - from twisted.internet import epollreactor as reactor + from twisted.internet import epollreactor as reactor, defer except ImportError: from twisted.internet import kqreactor as reactor diff --git a/blitzortung/service/db.py b/blitzortung/service/db.py index 4f555b6..1989975 100644 --- a/blitzortung/service/db.py +++ b/blitzortung/service/db.py @@ -1,5 +1,3 @@ -# -*- coding: utf8 -*- - """ Copyright 2025 Andreas Würl @@ -75,6 +73,10 @@ def create_connection_pool(): connection_pool = DictConnectionPool(None, db_connection_string) d = connection_pool.start() + def log_pool(pool): + print("pool created:", pool) + return pool + d.addCallback(log_pool) d.addErrback(log.err) return d From fd10843c725ffe5d4d640810d34a2cb94c957bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 22:22:57 +0100 Subject: [PATCH 06/13] wip --- blitzortung/service/db.py | 4 ++++ blitzortung/service/histogram.py | 5 +++-- blitzortung/service/strike_grid.py | 14 +++++++------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/blitzortung/service/db.py b/blitzortung/service/db.py index 1989975..e094022 100644 --- a/blitzortung/service/db.py +++ b/blitzortung/service/db.py @@ -80,3 +80,7 @@ def log_pool(pool): d.addErrback(log.err) return d + +def execute(connection, query): + print("execute:", connection, query) + return connection.runQuery(str(query), query.get_parameters()) diff --git a/blitzortung/service/histogram.py b/blitzortung/service/histogram.py index 2fc8ef5..3f133dd 100644 --- a/blitzortung/service/histogram.py +++ b/blitzortung/service/histogram.py @@ -23,6 +23,7 @@ from injector import inject from twisted.internet.defer import DeferredList, Deferred +from .db import execute from .. import db from ..db.query import TimeInterval, SelectQuery @@ -35,9 +36,9 @@ def __init__(self, strike_query_builder: db.query_builder.Strike): def create(self, time_interval: TimeInterval, deferred_pool: Deferred, region=None, envelope=None): reference_time = time.time() - query = self.strike_query_builder.histogram_query(db.table.Strike.table_name, time_interval, 5, region, envelope) + histogram_query = self.strike_query_builder.histogram_query(db.table.Strike.table_name, time_interval, 5, region, envelope) - result = deferred_pool.addCallback(lambda connection: connection.runQuery(str(query), query.get_parameters())) + result = deferred_pool.addCallback(execute, histogram_query) result.addCallback(self.build_result, minutes=time_interval.minutes(), bin_size=5, reference_time=reference_time) diff --git a/blitzortung/service/strike_grid.py b/blitzortung/service/strike_grid.py index 0a63f68..11e4ca6 100644 --- a/blitzortung/service/strike_grid.py +++ b/blitzortung/service/strike_grid.py @@ -26,6 +26,7 @@ from twisted.internet.defer import gatherResults, Deferred from twisted.python import log +from .db import execute from .general import TimingState from .. import db from ..db.grid_result import build_grid_result @@ -61,7 +62,7 @@ def create(self, grid_parameters: GridParameters, time_interval: TimeInterval, d query = self.strike_query_builder.grid_query(db.table.Strike.table_name, grid_parameters.grid, time_interval=time_interval, count_threshold=grid_parameters.count_threshold) - result = deferred_pool.addCallback(lambda connection: connection.runQuery(str(query), query.get_parameters())) + result = deferred_pool.addCallback(execute, query) result.addCallback(self.build_result, state=state) result.addErrback(log.err) return result, state @@ -126,8 +127,7 @@ class GlobalStrikeGridQuery: def __init__(self, strike_query_builder: db.query_builder.Strike): self.strike_query_builder = strike_query_builder - def create(self, grid_parameters: GridParameters, time_interval: TimeInterval, connection, - statsd_client): + def create(self, grid_parameters: GridParameters, time_interval: TimeInterval, deferred_pool: Deferred, statsd_client): state = StrikeGridState(statsd_client, grid_parameters, time_interval) @@ -135,10 +135,10 @@ def create(self, grid_parameters: GridParameters, time_interval: TimeInterval, c time_interval=time_interval, count_threshold=grid_parameters.count_threshold) - grid_query = connection.runQuery(str(query), query.get_parameters()) - grid_query.addCallback(self.build_strikes_grid_result, state=state) - grid_query.addErrback(log.err) - return grid_query, state + result = deferred_pool.addCallback(execute, query) + result.addCallback(self.build_strikes_grid_result, state=state) + result.addErrback(log.err) + return result, state @staticmethod def build_strikes_grid_result(results, state): From 7d2c19725d59531c40eade1089f586b6e050cb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 22:49:33 +0100 Subject: [PATCH 07/13] wip --- blitzortung/cli/webservice.py | 22 ++++++++++++++-------- blitzortung/service/db.py | 3 ++- blitzortung/service/histogram.py | 13 ++++++------- blitzortung/service/strike_grid.py | 14 +++++++------- tests/service/test_histogram.py | 3 +-- tests/service/test_strike_grid.py | 6 ++---- 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/blitzortung/cli/webservice.py b/blitzortung/cli/webservice.py index 0c77714..a312155 100755 --- a/blitzortung/cli/webservice.py +++ b/blitzortung/cli/webservice.py @@ -536,12 +536,18 @@ def emit(self, event_dict): else: log_directory = None -connection_pool = create_connection_pool() - -root = Blitzortung(connection_pool, log_directory) +def start_server(result): + root = Blitzortung(connection_pool, log_directory) + config = blitzortung.config.config() + site = server.Site(root) + site.displayTracebacks = False + jsonrpc_server = internet.TCPServer(config.get_webservice_port(), site, interface='127.0.0.1') + jsonrpc_server.setServiceParent(application) + return jsonrpc_server + +def on_error(failure): + log.err(failure, "Failed to create connection pool") + raise failure.value -config = blitzortung.config.config() -site = server.Site(root) -site.displayTracebacks = False -jsonrpc_server = internet.TCPServer(config.get_webservice_port(), site, interface='127.0.0.1') -jsonrpc_server.setServiceParent(application) +connection_pool = create_connection_pool() +connection_pool.addCallback(start_server).addErrback(on_error) diff --git a/blitzortung/service/db.py b/blitzortung/service/db.py index e094022..6ffbded 100644 --- a/blitzortung/service/db.py +++ b/blitzortung/service/db.py @@ -19,6 +19,7 @@ import psycopg2 import psycopg2.extras from pytest_twisted import inlineCallbacks +from twisted.internet.defer import Deferred from twisted.python import log from txpostgres import reconnection from txpostgres.txpostgres import Connection, ConnectionPool @@ -65,7 +66,7 @@ class DictConnectionPool(ConnectionPool): def __init__(self, _ignored, *connargs, **connkw): super(DictConnectionPool, self).__init__(_ignored, *connargs, **connkw) -def create_connection_pool(): +def create_connection_pool() -> Deferred: """Create and start the database connection pool.""" config = blitzortung.config.config() db_connection_string = config.get_db_connection_string() diff --git a/blitzortung/service/histogram.py b/blitzortung/service/histogram.py index 3f133dd..80e86f6 100644 --- a/blitzortung/service/histogram.py +++ b/blitzortung/service/histogram.py @@ -21,11 +21,10 @@ import time from injector import inject -from twisted.internet.defer import DeferredList, Deferred from .db import execute from .. import db -from ..db.query import TimeInterval, SelectQuery +from ..db.query import TimeInterval class HistogramQuery: @@ -33,15 +32,15 @@ class HistogramQuery: def __init__(self, strike_query_builder: db.query_builder.Strike): self.strike_query_builder = strike_query_builder - def create(self, time_interval: TimeInterval, deferred_pool: Deferred, region=None, envelope=None): + def create(self, time_interval: TimeInterval, connection_pool, region=None, envelope=None): reference_time = time.time() - histogram_query = self.strike_query_builder.histogram_query(db.table.Strike.table_name, time_interval, 5, region, envelope) - - result = deferred_pool.addCallback(execute, histogram_query) + query = self.strike_query_builder.histogram_query(db.table.Strike.table_name, time_interval, 5, region, + envelope) + result = execute(connection_pool, query) result.addCallback(self.build_result, minutes=time_interval.minutes(), bin_size=5, - reference_time=reference_time) + reference_time=reference_time) return result @staticmethod diff --git a/blitzortung/service/strike_grid.py b/blitzortung/service/strike_grid.py index 11e4ca6..c109104 100644 --- a/blitzortung/service/strike_grid.py +++ b/blitzortung/service/strike_grid.py @@ -23,7 +23,7 @@ from typing import Optional from injector import inject -from twisted.internet.defer import gatherResults, Deferred +from twisted.internet.defer import gatherResults from twisted.python import log from .db import execute @@ -56,13 +56,14 @@ class StrikeGridQuery: def __init__(self, strike_query_builder: db.query_builder.Strike): self.strike_query_builder = strike_query_builder - def create(self, grid_parameters: GridParameters, time_interval: TimeInterval, deferred_pool: Deferred, statsd_client): + def create(self, grid_parameters: GridParameters, time_interval: TimeInterval, connection_pool, statsd_client): state = StrikeGridState(statsd_client, grid_parameters, time_interval) query = self.strike_query_builder.grid_query(db.table.Strike.table_name, grid_parameters.grid, - time_interval=time_interval, count_threshold=grid_parameters.count_threshold) + time_interval=time_interval, + count_threshold=grid_parameters.count_threshold) - result = deferred_pool.addCallback(execute, query) + result = execute(connection_pool, query) result.addCallback(self.build_result, state=state) result.addErrback(log.err) return result, state @@ -127,15 +128,14 @@ class GlobalStrikeGridQuery: def __init__(self, strike_query_builder: db.query_builder.Strike): self.strike_query_builder = strike_query_builder - def create(self, grid_parameters: GridParameters, time_interval: TimeInterval, deferred_pool: Deferred, statsd_client): - + def create(self, grid_parameters: GridParameters, time_interval: TimeInterval, connection_pool, statsd_client): state = StrikeGridState(statsd_client, grid_parameters, time_interval) query = self.strike_query_builder.global_grid_query(db.table.Strike.table_name, grid_parameters.grid, time_interval=time_interval, count_threshold=grid_parameters.count_threshold) - result = deferred_pool.addCallback(execute, query) + result = execute(connection_pool, query) result.addCallback(self.build_strikes_grid_result, state=state) result.addErrback(log.err) return result, state diff --git a/tests/service/test_histogram.py b/tests/service/test_histogram.py index ce8a6a6..c6d947f 100644 --- a/tests/service/test_histogram.py +++ b/tests/service/test_histogram.py @@ -31,8 +31,7 @@ def test_create(self, uut, query_builder, connection): query_time_interval = create_time_interval(30, 0) - pool = defer.succeed(connection) - result = yield uut.create(query_time_interval, pool) + result = yield uut.create(query_time_interval, connection) assert result == [0,5,3,1,2,4] query_builder.histogram_query.assert_called_once_with("strikes", query_time_interval, 5, None, None) diff --git a/tests/service/test_strike_grid.py b/tests/service/test_strike_grid.py index 361796f..58c4080 100644 --- a/tests/service/test_strike_grid.py +++ b/tests/service/test_strike_grid.py @@ -56,9 +56,8 @@ def test_create(self, uut, now, grid_parameters_factory, time_interval, query_bu "strike_count": 3, "timestamp": now - datetime.timedelta(seconds=65) }]) - pool = defer.succeed(connection) - deferred_result, state = uut.create(grid_parameters, time_interval, pool, statsd_client) + deferred_result, state = uut.create(grid_parameters, time_interval, connection, statsd_client) result = yield deferred_result assert result == ((7, 102, 3, -66),) @@ -145,9 +144,8 @@ def test_create(self, uut, now, grid_parameters_factory, time_interval, query_bu "strike_count": 3, "timestamp": now - datetime.timedelta(seconds=65) }]) - pool = defer.succeed(connection) - deferred_result, state = uut.create(grid_parameters, time_interval, pool, statsd_client) + deferred_result, state = uut.create(grid_parameters, time_interval, connection, statsd_client) result = yield deferred_result assert result == ((7, 1898, 3, -66),) From 18e16fdf4b2e1a03269649d89f6e25bafe143ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 22:51:59 +0100 Subject: [PATCH 08/13] wip --- blitzortung/cli/webservice.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blitzortung/cli/webservice.py b/blitzortung/cli/webservice.py index a312155..0576f40 100755 --- a/blitzortung/cli/webservice.py +++ b/blitzortung/cli/webservice.py @@ -536,7 +536,7 @@ def emit(self, event_dict): else: log_directory = None -def start_server(result): +def start_server(connection_pool): root = Blitzortung(connection_pool, log_directory) config = blitzortung.config.config() site = server.Site(root) @@ -549,5 +549,5 @@ def on_error(failure): log.err(failure, "Failed to create connection pool") raise failure.value -connection_pool = create_connection_pool() -connection_pool.addCallback(start_server).addErrback(on_error) +deferred_connection_pool = create_connection_pool() +deferred_connection_pool.addCallback(start_server).addErrback(on_error) From afae42756ee51e4164f2589b9483a70e9cd5f564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 22:53:32 +0100 Subject: [PATCH 09/13] wip --- blitzortung/cli/webservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blitzortung/cli/webservice.py b/blitzortung/cli/webservice.py index 0576f40..197d4e2 100755 --- a/blitzortung/cli/webservice.py +++ b/blitzortung/cli/webservice.py @@ -450,7 +450,7 @@ def fix_bad_accept_header(self, request, user_agent): def get_histogram(self, time_interval: TimeInterval, region=None, envelope=None): return self.histogram_cache.get(self.histogram_query.create, time_interval=time_interval, - deferred_pool=self.connection_pool, + connection_pool=self.connection_pool, region=region, envelope=envelope) From 526cf4069820a3842ab16b742f4b8185acf84dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 22:57:04 +0100 Subject: [PATCH 10/13] cleanup --- blitzortung/service/db.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blitzortung/service/db.py b/blitzortung/service/db.py index 6ffbded..cff2462 100644 --- a/blitzortung/service/db.py +++ b/blitzortung/service/db.py @@ -25,6 +25,7 @@ from txpostgres.txpostgres import Connection, ConnectionPool import blitzortung.config +from blitzortung.db.query import SelectQuery def connection_factory(*args, **kwargs): @@ -82,6 +83,5 @@ def log_pool(pool): return d -def execute(connection, query): - print("execute:", connection, query) +def execute(connection, query: SelectQuery): return connection.runQuery(str(query), query.get_parameters()) From f945c47b85f91634c4c802abd7527ab0d75be793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 23:00:24 +0100 Subject: [PATCH 11/13] cleanup --- blitzortung/cli/webservice.py | 1 + blitzortung/service/db.py | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/blitzortung/cli/webservice.py b/blitzortung/cli/webservice.py index 197d4e2..fff1bc7 100755 --- a/blitzortung/cli/webservice.py +++ b/blitzortung/cli/webservice.py @@ -537,6 +537,7 @@ def emit(self, event_dict): log_directory = None def start_server(connection_pool): + print("Connection pool is ready") root = Blitzortung(connection_pool, log_directory) config = blitzortung.config.config() site = server.Site(root) diff --git a/blitzortung/service/db.py b/blitzortung/service/db.py index cff2462..2623587 100644 --- a/blitzortung/service/db.py +++ b/blitzortung/service/db.py @@ -75,10 +75,6 @@ def create_connection_pool() -> Deferred: connection_pool = DictConnectionPool(None, db_connection_string) d = connection_pool.start() - def log_pool(pool): - print("pool created:", pool) - return pool - d.addCallback(log_pool) d.addErrback(log.err) return d From 7e37c163e3bd2f79003fe9a25ad26235b97d87d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 23:19:44 +0100 Subject: [PATCH 12/13] seems that we did not use global builder --- blitzortung/service/strike_grid.py | 10 +++++----- tests/service/test_strike_grid.py | 20 ++++++++------------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/blitzortung/service/strike_grid.py b/blitzortung/service/strike_grid.py index c109104..6b67d18 100644 --- a/blitzortung/service/strike_grid.py +++ b/blitzortung/service/strike_grid.py @@ -136,12 +136,12 @@ def create(self, grid_parameters: GridParameters, time_interval: TimeInterval, c count_threshold=grid_parameters.count_threshold) result = execute(connection_pool, query) - result.addCallback(self.build_strikes_grid_result, state=state) + result.addCallback(self.build_result, state=state) result.addErrback(log.err) return result, state @staticmethod - def build_strikes_grid_result(results, state): + def build_result(results, state): state.add_info_text( "global grid query %.03fs #%d %s" % (state.get_seconds(), len(results), state.grid_parameters)) state.log_timing('global_strikes_grid.query') @@ -176,15 +176,15 @@ def build_grid_response(results, state): histogram_data = results[1] state.log_gauge('global_strikes_grid.size', len(grid_data)) - state.log_gauge(f'global_strikes_grid.size.{state.grid_metadata.base_length}', len(grid_data)) + state.log_gauge(f'global_strikes_grid.size.{state.grid_parameters.base_length}', len(grid_data)) state.log_incr('global_strikes_grid') grid_parameters = state.grid_parameters end_time = state.time_interval.end duration = state.time_interval.duration response = {'r': grid_data, - 'xd': round(grid_parameters.x_div, 6), - 'yd': round(grid_parameters.y_div, 6), + 'xd': round(grid_parameters.grid.x_div, 6), + 'yd': round(grid_parameters.grid.y_div, 6), 't': end_time.strftime("%Y%m%dT%H:%M:%S"), 'dt': duration.seconds, 'h': histogram_data} diff --git a/tests/service/test_strike_grid.py b/tests/service/test_strike_grid.py index 58c4080..f80e91e 100644 --- a/tests/service/test_strike_grid.py +++ b/tests/service/test_strike_grid.py @@ -7,7 +7,7 @@ from mock import Mock, call from twisted.internet import defer -from blitzortung.service.strike_grid import StrikeGridQuery, GridParameters, StrikeGridState +from blitzortung.service.strike_grid import StrikeGridQuery, GridParameters, StrikeGridState, GlobalStrikeGridQuery from tests.conftest import time_interval @@ -123,7 +123,7 @@ class TestGlobalStrikeGridQuery: @pytest.fixture def uut(self, query_builder): - return StrikeGridQuery(query_builder) + return GlobalStrikeGridQuery(query_builder) @pytest.fixture def grid_parameters_factory(self, global_grid_factory) -> Callable[[int], GridParameters]: @@ -148,12 +148,12 @@ def test_create(self, uut, now, grid_parameters_factory, time_interval, query_bu deferred_result, state = uut.create(grid_parameters, time_interval, connection, statsd_client) result = yield deferred_result - assert result == ((7, 1898, 3, -66),) + assert result == ((7, -10, 3, -66),) - query_builder.grid_query.assert_called_once_with("strikes", grid_parameters.grid, time_interval=time_interval, + query_builder.global_grid_query.assert_called_once_with("strikes", grid_parameters.grid, time_interval=time_interval, count_threshold=grid_parameters.count_threshold) - query = query_builder.grid_query.return_value + query = query_builder.global_grid_query.return_value assert connection.runQuery.call_args == call(str(query), query.get_parameters.return_value) def test_build_result(self, uut, statsd_client, grid_parameters_factory, time_interval, ): @@ -173,7 +173,7 @@ def test_build_result(self, uut, statsd_client, grid_parameters_factory, time_in result = uut.build_result(query_result, state=state) - assert result == ((rx, grid_parameters.grid.y_bin_count - ry, 3, -seconds_offset),) + assert result == ((rx, -1 - ry, 3, -seconds_offset),) def test_build_grid_response(self, uut, statsd_client, grid_parameters_factory, time_interval, ): grid_parameters = grid_parameters_factory(10000) @@ -194,13 +194,9 @@ def test_build_grid_response(self, uut, statsd_client, grid_parameters_factory, 'h': [0, 0, 0, 0, 0, 1], 'r': ((7, 102, 3, -65),), 't': time_interval.end.strftime("%Y%m%dT%H:%M:%S"), - 'x0': -180.0, - 'xc': 2834, 'xd': 0.127011, - 'y1': 90.0238, - 'yc': 1907, 'yd': 0.094352 } - assert_that(response["x0"] + response["xd"] * response["xc"]).is_close_to(grid_parameters.grid.x_max, 0.1) - assert_that(response["y1"] - response["yd"] * response["yc"]).is_close_to(grid_parameters.grid.y_min, 0.1) + # assert_that(response["x0"] + response["xd"] * response["xc"]).is_close_to(grid_parameters.grid.x_max, 0.1) + # assert_that(response["y1"] - response["yd"] * response["yc"]).is_close_to(grid_parameters.grid.y_min, 0.1) From 5e133d30c59551ce13deb385dbf77007317a089b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20W=C3=BCrl?= Date: Fri, 14 Nov 2025 23:37:06 +0100 Subject: [PATCH 13/13] cleanup --- tests/dataimport/test_dataimport_base.py | 2 - tests/db/test_db_init.py | 16 +++- tests/service/test_general.py | 2 +- tests/service/test_service_db.py | 5 +- tests/test_data.py | 112 +++++++++++++++++++++++ tests/test_lock.py | 2 +- 6 files changed, 130 insertions(+), 9 deletions(-) diff --git a/tests/dataimport/test_dataimport_base.py b/tests/dataimport/test_dataimport_base.py index 527d507..55ef6b6 100644 --- a/tests/dataimport/test_dataimport_base.py +++ b/tests/dataimport/test_dataimport_base.py @@ -20,10 +20,8 @@ import os import tempfile -from unittest.mock import Mock, patch, MagicMock from assertpy import assert_that # pylint: disable=import-error -import pytest # pylint: disable=import-error import blitzortung.dataimport.base diff --git a/tests/db/test_db_init.py b/tests/db/test_db_init.py index 8c9ba73..51bd9c2 100644 --- a/tests/db/test_db_init.py +++ b/tests/db/test_db_init.py @@ -18,9 +18,10 @@ """ -from mock import Mock +from mock import Mock, patch import blitzortung.db +import blitzortung.db.table class TestDbModule: @@ -29,3 +30,16 @@ def test_cleanup_closes_connection_pool(self): connection_pool = Mock() blitzortung.db.DbModule.cleanup(connection_pool) connection_pool.closeall.assert_called_once() + + +class TestHelperFunctions: + + @patch('blitzortung.INJECTOR') + def test_strike(self, mock_injector): + mock_strike_table = Mock(spec=blitzortung.db.table.Strike) + mock_injector.get.return_value = mock_strike_table + + result = blitzortung.db.strike() + + mock_injector.get.assert_called_once_with(blitzortung.db.table.Strike) + assert result == mock_strike_table diff --git a/tests/service/test_general.py b/tests/service/test_general.py index abb7e72..aa394ac 100644 --- a/tests/service/test_general.py +++ b/tests/service/test_general.py @@ -20,7 +20,7 @@ import datetime import time -from unittest.mock import Mock +from mock import Mock from assertpy import assert_that # pylint: disable=import-error import pytest # pylint: disable=import-error diff --git a/tests/service/test_service_db.py b/tests/service/test_service_db.py index a2207b8..6e164b9 100644 --- a/tests/service/test_service_db.py +++ b/tests/service/test_service_db.py @@ -18,13 +18,10 @@ """ -from unittest.mock import Mock, patch, MagicMock - -import psycopg2.extras import pytest import pytest_twisted from assertpy import assert_that -import time +from mock import Mock, patch import blitzortung.service.db diff --git a/tests/test_data.py b/tests/test_data.py index 7b3ec93..1b0f854 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -51,6 +51,64 @@ def test_from_nanoseconds(self): datetime.datetime(2018, 10, 30, 21, 43, 53, 552753, datetime.timezone.utc)) assert_that(timestamp.nanosecond).is_equal_to(700) + def test_comparison_with_datetime(self): + ts = Timestamp(datetime.datetime(2020, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)) + dt_before = datetime.datetime(2019, 12, 31, 12, 0, 0, tzinfo=datetime.timezone.utc) + dt_after = datetime.datetime(2020, 1, 2, 12, 0, 0, tzinfo=datetime.timezone.utc) + + # Test __lt__ with datetime + assert_that(ts < dt_after).is_true() + assert_that(ts < dt_before).is_false() + + # Test __le__ with datetime + assert_that(ts <= dt_after).is_true() + assert_that(ts <= dt_before).is_false() + + # Test __gt__ with datetime + assert_that(ts > dt_before).is_true() + assert_that(ts > dt_after).is_false() + + # Test __ge__ with datetime + assert_that(ts >= dt_before).is_true() + assert_that(ts >= dt_after).is_false() + + def test_addition_operations(self): + ts = Timestamp(datetime.datetime(2020, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)) + + # Test adding Timedelta + td = Timedelta(datetime.timedelta(hours=1), 500) + result = ts + td + assert_that(result.datetime).is_equal_to(datetime.datetime(2020, 1, 1, 13, 0, 0, tzinfo=datetime.timezone.utc)) + assert_that(result.nanosecond).is_equal_to(500) + + # Test adding datetime.timedelta + delta = datetime.timedelta(hours=2) + result = ts + delta + assert_that(result.datetime).is_equal_to(datetime.datetime(2020, 1, 1, 14, 0, 0, tzinfo=datetime.timezone.utc)) + + # Test adding int (nanoseconds) + result = ts + 500 + assert_that(result.nanosecond).is_equal_to(500) + + def test_subtraction_operations(self): + ts = Timestamp(datetime.datetime(2020, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)) + + # Test subtracting datetime.timedelta + delta = datetime.timedelta(hours=2) + result = ts - delta + assert_that(result.datetime).is_equal_to(datetime.datetime(2020, 1, 1, 10, 0, 0, tzinfo=datetime.timezone.utc)) + + # Test subtracting int (nanoseconds) + ts_with_ns = Timestamp(datetime.datetime(2020, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc), 500) + result = ts_with_ns - 200 + assert_that(result.nanosecond).is_equal_to(300) + + def test_replace_with_nanosecond(self): + ts = Timestamp(datetime.datetime(2020, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc), 100) + result = ts.replace(nanosecond=500) + assert_that(result.nanosecond).is_equal_to(500) + assert_that(result.datetime).is_equal_to(ts.datetime) + class TestTimedelta: def test_normalizing(self): @@ -63,6 +121,17 @@ def test_normalizing(self): assert_that(Timedelta(nanodelta=-1500)).is_equal_to( Timedelta(datetime.timedelta(microseconds=-2), 500)) + def test_properties(self): + td = Timedelta(datetime.timedelta(days=5, seconds=3661), 500) + assert_that(td.days).is_equal_to(5) + assert_that(td.seconds).is_equal_to(3661) + + def test_repr(self): + td = Timedelta(datetime.timedelta(hours=2), 500) + result = repr(td) + assert_that(result).contains("Timedelta") + assert_that(result).contains("500") + class EventBaseTest: def setup_method(self): @@ -148,6 +217,19 @@ def test_is_valid_returning_false_if_event_is_not_valid(self): event = blitzortung.data.Event(self.not_a_time, 0.0, 0.0) self.assertFalse(event.is_valid) + def test_has_same_location(self): + event1 = blitzortung.data.Event(self.now_time, 11.0, 49.0) + event2 = blitzortung.data.Event(self.later_time, 11.0, 49.0) + event3 = blitzortung.data.Event(self.now_time, 12.0, 49.0) + + self.assertTrue(event1.has_same_location(event2)) + self.assertFalse(event1.has_same_location(event3)) + + def test_string_with_nat(self): + event = blitzortung.data.Event(self.not_a_time, 11.0, 49.0) + result = str(event) + assert_that(result).contains("NaT") + class TestStrike: def setup_method(self): @@ -296,3 +378,33 @@ def test_raster_to_reduced_array(self): def test_raster_set_outside_valid_index_value_does_not_throw_exception(self): self.grid_data.set(1000, 0, blitzortung.geom.GridElement(20, self.reference_time - datetime.timedelta(hours=1))) assert_that(self.grid_data.to_reduced_array(self.reference_time)).is_equal_to(()) + + +class TestChannelWaveform: + def test_init(self): + waveform_data = [1, 2, 3, 4, 5] + waveform = blitzortung.data.ChannelWaveform( + channel_number=1, + amplifier_version=2, + antenna=3, + gain=4.5, + values=100, + start=0, + bits=12, + shift=0, + conversion_gap=10, + conversion_time=5, + waveform=waveform_data + ) + + assert_that(waveform.channel_number).is_equal_to(1) + assert_that(waveform.amplifier_version).is_equal_to(2) + assert_that(waveform.antenna).is_equal_to(3) + assert_that(waveform.gain).is_equal_to(4.5) + assert_that(waveform.values).is_equal_to(100) + assert_that(waveform.start).is_equal_to(0) + assert_that(waveform.bits).is_equal_to(12) + assert_that(waveform.shift).is_equal_to(0) + assert_that(waveform.conversion_gap).is_equal_to(10) + assert_that(waveform.conversion_time).is_equal_to(5) + assert_that(waveform.waveform).is_equal_to(waveform_data) diff --git a/tests/test_lock.py b/tests/test_lock.py index 33a09be..3833de6 100644 --- a/tests/test_lock.py +++ b/tests/test_lock.py @@ -19,7 +19,7 @@ """ import tempfile -from unittest.mock import patch +from mock import patch from assertpy import assert_that # pylint: disable=import-error import pytest # pylint: disable=import-error