diff --git a/05-architecture/05-02-packages/05-02-01-core/.pylintrc b/05-architecture/05-02-packages/05-02-01-core/.pylintrc new file mode 100644 index 0000000..c101b7b --- /dev/null +++ b/05-architecture/05-02-packages/05-02-01-core/.pylintrc @@ -0,0 +1,8 @@ +[MASTER] +load-plugins=pylint_forbidden_imports +[MESSAGES CONTROL] +disable=all +enable=this-package-is-forbidden-to-be-imported-here +[CONVENTIONS] +allowed-modules-dependencies=smarttesting_api->smarttesting, # api może importować smarttesting + tests->* # testy moga importować wszystko diff --git a/05-architecture/05-02-packages/05-02-01-core/smarttesting/__init__.py b/05-architecture/05-02-packages/05-02-01-core/smarttesting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-02-packages/05-02-01-core/smarttesting/models/__init__.py b/05-architecture/05-02-packages/05-02-01-core/smarttesting/models/__init__.py new file mode 100644 index 0000000..8b90288 --- /dev/null +++ b/05-architecture/05-02-packages/05-02-01-core/smarttesting/models/__init__.py @@ -0,0 +1,3 @@ +from smarttesting.models.person import Person + +__all__ = ["Person"] diff --git a/05-architecture/05-02-packages/05-02-01-core/smarttesting/models/basket.py b/05-architecture/05-02-packages/05-02-01-core/smarttesting/models/basket.py new file mode 100644 index 0000000..0cca478 --- /dev/null +++ b/05-architecture/05-02-packages/05-02-01-core/smarttesting/models/basket.py @@ -0,0 +1,24 @@ +"""Jako ilustracja do _02_threadlocals_tests. + +Antyprzykład wykorzystania threadlocals - klasy Basket nawet nie da się przetestować +bez podniesienia całego kontekstu aplikacji Flaska lub zamockowania tejże. +""" +from dataclasses import dataclass, field +from typing import Dict + +from flask import request + + +@dataclass +class Basket: + user_id: int + items: Dict[int, int] = field(default_factory=dict) + + @classmethod + def get_from_request(cls) -> "Basket": + basket_id = request.cookies.get("basket-id") + if basket_id is None: + return Basket(0, {}) + else: + # pobierz z bazy po id, cokolwiek innego + return Basket(1, {}) diff --git a/05-architecture/05-02-packages/05-02-01-core/smarttesting/models/person.py b/05-architecture/05-02-packages/05-02-01-core/smarttesting/models/person.py new file mode 100644 index 0000000..0504820 --- /dev/null +++ b/05-architecture/05-02-packages/05-02-01-core/smarttesting/models/person.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass +from typing import Optional + +from smarttesting.tasks import send_email +from smarttesting.taxes import TaxService + + +@dataclass +class Person: + """Klasa udająca, że jest modelem Active Record (np. Django ORM).""" + + person_id: int + name: str + surname: str + dues: Optional[int] = 0 + + def calculate(self) -> None: + tax = TaxService() + self.dues = tax.calculate() + send_email.delay(person_id=self.person_id, dues=self.dues) + self.save() + + def save(self) -> None: + """Active Record ORM by to miał!""" diff --git a/05-architecture/05-02-packages/05-02-01-core/smarttesting/tasks.py b/05-architecture/05-02-packages/05-02-01-core/smarttesting/tasks.py new file mode 100644 index 0000000..05aead8 --- /dev/null +++ b/05-architecture/05-02-packages/05-02-01-core/smarttesting/tasks.py @@ -0,0 +1,6 @@ +class CeleryWannabeTask: + def delay(self, *_args, **_kwargs) -> None: + raise NotImplementedError + + +send_email = CeleryWannabeTask() diff --git a/05-architecture/05-02-packages/05-02-01-core/smarttesting/taxes.py b/05-architecture/05-02-packages/05-02-01-core/smarttesting/taxes.py new file mode 100644 index 0000000..09cb18e --- /dev/null +++ b/05-architecture/05-02-packages/05-02-01-core/smarttesting/taxes.py @@ -0,0 +1,9 @@ +import random + + +class TaxService: + def calculate(self) -> int: + if random.random() > 0.5: + raise NotImplementedError + + return 42 diff --git a/05-architecture/05-02-packages/05-02-01-core/smarttesting_api/__init__.py b/05-architecture/05-02-packages/05-02-01-core/smarttesting_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-02-packages/05-02-01-core/smarttesting_api/app.py b/05-architecture/05-02-packages/05-02-01-core/smarttesting_api/app.py new file mode 100644 index 0000000..c6f9d20 --- /dev/null +++ b/05-architecture/05-02-packages/05-02-01-core/smarttesting_api/app.py @@ -0,0 +1,10 @@ +from flask import Flask, jsonify +from smarttesting.models.basket import Basket + +app = Flask(__name__) + + +@app.route("/baskets") +def current_basket(): + basket = Basket.get_from_request() + return jsonify({"user_id": basket.user_id, "items": basket.items}) diff --git a/05-architecture/05-02-packages/05-02-01-core/tests/__init__.py b/05-architecture/05-02-packages/05-02-01-core/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-02-packages/05-02-01-core/tests/lesson1/_01_bad_class_tests.py b/05-architecture/05-02-packages/05-02-01-core/tests/lesson1/_01_bad_class_tests.py new file mode 100644 index 0000000..3dd8ef8 --- /dev/null +++ b/05-architecture/05-02-packages/05-02-01-core/tests/lesson1/_01_bad_class_tests.py @@ -0,0 +1,47 @@ +from unittest.mock import patch + +from smarttesting import tasks +from smarttesting.models import Person +from smarttesting.taxes import TaxService + + +class Test01BadClass: + """Klasa z przykładami źle zaprojektowanego kodu. + + Sam kod w sobie niewiele robi, chodzi jedynie o zaprezentowanie koncepcji + i często powielanych w Pythonie zachowań utrudniających potem testowanie. + + Czy zdarzyło Ci się, że dodawanie kolejnych testów było dla Ciebie drogą przez + mękę? + + Czy znasz przypadki, gdzie potrzebne były setki linijek kodu przygotowującego pod + uruchomienie testu? Oznacza to, że najprawdopodobniej albo nasz sposób testowania + jest niepoprawny albo architektura aplikacji jest zła. + """ + + def test_heavy_monkey_patching(self) -> None: + """Patchowanie jest zaskakująco chętnie wykorzystywaną techniką w testowaniu + w Pythonie, jednak z punktu widzenia inżynierii oprogramowania jest to praktyka + mocno wątpliwa. + + Tak naprawdę bardzo silnie cementuje nasz kod, będąc bardziej obejściem na + problemy z ukrytymi wejściami i wyjściami do testowanego kodu. + Ukryte wejścia/wyjścia to wszystko, czego testowana funkcja/metoda nie dostaje + w argumencie lub przez self., ale co jest potrzebne do jej działania. + + Pokazywane w poprzednich lekcjach podejście wykorzystujące Dependency Injection + z kontenerami IoC to sposób na pełną kontrolę tych kłopotliwych zależności. + """ + person = Person(person_id=1, name="Jacek", surname="Kowalski") + + # Najpierw trzeba się grubo napracować patchując wszystkie lekkomyślnie + # zaimplementowane zależności z różnych zakątków aplikacji... + with patch.object(TaxService, "calculate") as tax_calculate_mock: + tax_calculate_mock.return_value = 10 + # ...i modlić się, żeby nikt niczego nie przeniósł, bo patche od razu + # przestaną działać + with patch.object(tasks.send_email, "delay") as delay_mock: + person.calculate() # wołamy metodę której nazwa niewiele mówi + + tax_calculate_mock.assert_called_once() + delay_mock.assert_called_once_with(person_id=person.person_id, dues=10) diff --git a/05-architecture/05-02-packages/05-02-01-core/tests/lesson1/__init__.py b/05-architecture/05-02-packages/05-02-01-core/tests/lesson1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-02-packages/05-02-01-core/tests/lesson2/_02_threadlocals_tests.py b/05-architecture/05-02-packages/05-02-01-core/tests/lesson2/_02_threadlocals_tests.py new file mode 100644 index 0000000..e9cf515 --- /dev/null +++ b/05-architecture/05-02-packages/05-02-01-core/tests/lesson2/_02_threadlocals_tests.py @@ -0,0 +1,54 @@ +import pytest +from flask import request as request_threadlocal_proxy +from smarttesting.models import basket + + +class Test02Threadlocals: + """ + Wiele popularnych pythonowych frameworków wykorzystuje mechanizm threadlocal + aby rozwiązac problem przekazywania zależności. Z zewnątrz wygląda to jak zmienna + globalna, którą importujemy i z niej korzystamy w kodzie. threadlocal gwarantuje, + że w jednym wątku (najczęściej - tym samym requeście) będziemy dostawać dokładnie + ten sam obiekt. Dobry przykład to flask.request, będący proxy-objectem na bieżący + obiekt żądania. + + Utrzymywalność takich rozwiązań jest jednak mocno wątpliwa a i przetestowanie nie + należy do najłatwiejszych - szczególnie, jeśli dana biblioteka nie udostępnia + narzędzi do zarządzania stanem obiektów wykorzystywanych za pośrednictwem + threadlocali (np. test clienta). + + Testy mogą potencjalnie zabezpieczyć nas przed złym wykorzystaniem threadlocali. + Pomijając trudność wykonania (trzeba obsłużyć całkiem sporo przypadków), to chodzi + tu bardziej o koncepcję i celowość takich testów - próbujemy za ich pomocą wymusić + konwencje i dobre praktyki. + """ + + @pytest.mark.xfail() # Ten test nie przechodzi bo ma demaskować źle napisany kod + def test_request_proxy_not_in_module(self) -> None: + """Próba wykrycia, czy ktoś nie próbuje importować threadlocala w kodzie. + + W Pythonie wszystko jest obiektem, także moduł. Jeżeli ktoś coś importuje, + to ten obiekt jest dodawanay do __dict__ modułu pod zaimportowaną nazwą. + + W tym teście spróbujemy wykryć tylko jeden prosty przypadek: + - czy ktoś nie importuje bezpośrednio `flask.request` + """ + # Pobieramy atrybut o nazwie `request` modułu basket lub None + # jeżeli w module basket zrobimy `from flask import request` to dokładnie + # taki atrybut zostanie dodany do wspomnianego modułu + request_in_basket = getattr(basket, "request", None) + + assert request_in_basket is not request_threadlocal_proxy + + @pytest.mark.xfail() # Ten test nie przechodzi bo ma demaskować źle napisany kod + def test_request_proxy_not_aliased_in_module(self) -> None: + """Próba wykrycia, czy ktoś nie próbuje importować threadlocala w kodzie. + + Nieco sprytniejsze podejście niż poprzednio - tym razem wykryjemy także + aliasowanie przechodząc po wszystkich rzeczach zaimportowanych i sprawdzając, + czy któraś z nich przypadkiem nie znajduje się na liście "zakazanych" obiektów. + """ + forbidden_objects = [request_threadlocal_proxy] + for _attr_name, attr_value in vars(basket).items(): + for obj in forbidden_objects: + assert attr_value is not obj diff --git a/05-architecture/05-02-packages/05-02-01-core/tests/lesson2/_03_architecture_tests.py b/05-architecture/05-02-packages/05-02-01-core/tests/lesson2/_03_architecture_tests.py new file mode 100644 index 0000000..12cc2c7 --- /dev/null +++ b/05-architecture/05-02-packages/05-02-01-core/tests/lesson2/_03_architecture_tests.py @@ -0,0 +1,36 @@ +import sys +from pathlib import Path + +import pytest +from pylint import lint + + +class Test03Architecture: + """Brak modyfikatorów dostępu nie pozwala na proste ograniczanie dostępu, + ale zawsze możemy użyć statycznej analizy kodu w postaci linterów i wtyczek + do nich aby wymusić pewne zależności pomiędzy naszymi modułami (a raczej ich brak). + """ + + @pytest.fixture(autouse=True) + def setup(self): + root_dir = Path(__file__).parents[2] + self.paths_to_check = [ + str(root_dir / module_name) + for module_name in ("smarttesting", "smarttesting_api", "tests") + ] + + def test_run_pylint_checks_on_modules(self) -> None: + """Uruchomienie pylinta programatycznie. + + Jest to odpowiednik uruchomienia go ręcznie w katalogu + 05-architecture/05-02-packages/05-02-01-core komendą + `pylint smarttesting smarttesting_api/ tests/`. + + Zajrzyj koniecznie do pliku .pylintrc z konfiguracją i sprobuj zaimportować + coś z smarttesting_api w module smarttesting. + """ + lint.Run(self.paths_to_check, do_exit=False) + + sys.stdout.seek(0) + output = sys.stdout.read() + assert "Your code has been rated at 10.00/10" in output, output diff --git a/05-architecture/05-02-packages/05-02-01-core/tests/lesson2/__init__.py b/05-architecture/05-02-packages/05-02-01-core/tests/lesson2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-02-packages/README.adoc b/05-architecture/05-02-packages/README.adoc new file mode 100644 index 0000000..c1870b4 --- /dev/null +++ b/05-architecture/05-02-packages/README.adoc @@ -0,0 +1,30 @@ += Testowanie architektury + +W Pythonie w ogóle nie istnieje koncept modyfikatorów dostępu tak do pojedynczych modułów jak i klas. +Oznacza to, że nic nie stoi na przeszkodzie byśmy z dowolnego miejsca kodu importowali i używali klas i funkcji z dowolnego innego miejsca, także teoretycznie nieprzeznaczonych do bezpośredniego użycia i nieudokumentowanych części bibliotek 3rd party lub wbudowanych modułów. + +Pewną wskazówką dla użytkownika naszego modułu może byc umieszczenie zmiennej `\_\_all\_\_` w `\_\_init\_\_.py` najwyższego poziomu, jednak fizycznie nie powstrzymamy nikogo przed importowaniem rzeczy z bebechów. + +Stanowi to spore wyzwanie dla większych projektów w Pythonie. + +Aplikacja podzielona jest na dwa moduły, `smarttesting` i `smarttesting_api`. Pierwsza reprezentuje domenę biznesową naszej aplikacji a druga kod z podpiętym frameworkiem. + +W tej lekcji zobaczymy jak możemy uchronić się przed popsuciem naszej domeny +i przed tym, że klasy z jednych pakietów są widoczne w innych. + +== Testy a architektura [01] + +=== Kod + +Tylko moduł `05-02-01-core` i klasa `tests.lesson1._01_bad_class_tests.Test01BadClass` + + +== Testowanie pakietowania [02] + +=== Kod + +* `05-02-01-core` +** `_02_threadlocals_tests.py`, `_03_architecture_tests.py` + +=== Inne +https://import-linter.readthedocs.io/en/stable/[Import Linter] \ No newline at end of file diff --git a/05-architecture/05-03-cdc/01_generate_stubs_with_scc.sh b/05-architecture/05-03-cdc/01_generate_stubs_with_scc.sh new file mode 100755 index 0000000..f931652 --- /dev/null +++ b/05-architecture/05-03-cdc/01_generate_stubs_with_scc.sh @@ -0,0 +1,29 @@ +#!/bin/bash +SC_CONTRACT_DOCKER_VERSION="3.0.3" + +PROJECT_NAME="smarttesting" +PROJECT_VERSION="0.0.1.RELEASE" + +NETWORKING="" +SYSTEM_NAME=$(uname -s) +if [ "$SYSTEM_NAME" == "Linux" ] +then + APPLICATION_BASE_URL="http://localhost:5050" + # to nie działa na Macu, a Sprint Cloud Contracts musi dobić się do aplikacji + NETWORKING="--network host" +else + # Windows & MacOS + APPLICATION_BASE_URL="http://host.docker.internal:5050" +fi + +CURRENT_DIR=$(pwd) + +# Wygenerowane pliki stubów znajdziemy w folderze producenta/spring-cloud-contract-output/ +docker run --rm $NETWORKING \ + -e "APPLICATION_BASE_URL=${APPLICATION_BASE_URL}" \ + -e "PUBLISH_ARTIFACTS=false" \ + -e "PROJECT_NAME=${PROJECT_NAME}" \ + -e "PROJECT_VERSION=${PROJECT_VERSION}" \ + -v "${CURRENT_DIR}/05-03-01-producer/contracts/:/contracts:ro" \ + -v "${CURRENT_DIR}/05-03-01-producer/spring-cloud-contract-output:/spring-cloud-contract-output/" \ + springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}" diff --git a/05-architecture/05-03-cdc/02_run_stubrunner.sh b/05-architecture/05-03-cdc/02_run_stubrunner.sh new file mode 100755 index 0000000..cd54f84 --- /dev/null +++ b/05-architecture/05-03-cdc/02_run_stubrunner.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -o errexit + +CURRENT_DIR=$(pwd) + +# Port, na którym będzie uruchomiony stub naszych kontraktów +STUB_PORT="5051" + +SC_CONTRACT_DOCKER_VERSION="3.0.3" +# Port na którym bedzie działać Stubrunner (w tym przykładzie nie ma szczególnego znaczenia) +STUBRUNNER_PORT="8083" +# Wskazujemy którego stuba chcemy użyć +STUBRUNNER_IDS="com.example:smarttesting:+:$STUB_PORT" + +# Miejsce wskazujące output z Spring Cloud Contract (01_generate_stubs_with_scc.sh) +STUBS_LOCALATION="${CURRENT_DIR}/05-03-01-producer/spring-cloud-contract-output/" +# Ustawienie Stubrunnera, które powoduje że będzie korzystał ze stubów w plikach +STUBRUNNER_REPOSITORY_ROOT="stubs://file:///scc_output" + +docker run --rm -e "STUBRUNNER_IDS=${STUBRUNNER_IDS}" \ + -e STUBRUNNER_REPOSITORY_ROOT=$STUBRUNNER_REPOSITORY_ROOT \ + -e STUBRUNNER_STUBS_MODE=LOCAL \ + -p "${STUBRUNNER_PORT}:${STUBRUNNER_PORT}" \ + -p "$STUB_PORT:$STUB_PORT" \ + -v "$STUBS_LOCALATION:/scc_output:ro" \ +springcloud/spring-cloud-contract-stub-runner:"${SC_CONTRACT_DOCKER_VERSION}" diff --git a/05-architecture/05-03-cdc/05-03-01-producer/.pylintrc b/05-architecture/05-03-cdc/05-03-01-producer/.pylintrc new file mode 100644 index 0000000..853531e --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/.pylintrc @@ -0,0 +1,13 @@ +[MASTER] +load-plugins=pylint_forbidden_imports +[MESSAGES CONTROL] +disable=all +enable=this-package-is-forbidden-to-be-imported-here +[CONVENTIONS] +allowed-modules-dependencies=smarttesting_api->smarttesting, + smarttesting_api->smarttesting_main, + smarttesting_main->*, + smartesting_main->celery, + smarttesting_main->sqlalchemy, + smarttesting_api->sqlalchemy, + tests->* # testy moga importować wszystko diff --git a/05-architecture/05-03-cdc/05-03-01-producer/contracts/should_return_fraud.yml b/05-architecture/05-03-cdc/05-03-01-producer/contracts/should_return_fraud.yml new file mode 100644 index 0000000..a9a8afa --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/contracts/should_return_fraud.yml @@ -0,0 +1,15 @@ +request: + method: POST + url: /fraudCheck + headers: + Content-Type: application/json + body: + uuid: 89c878e3-38f7-4831-af6c-c3b4a0669022 + person: + name: Stefania + surname: Stefanowska + date_of_birth: "2020-01-01" + gender: FEMALE + national_id_number: "1234567890" +response: + status: 401 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/contracts/should_return_non_fraud.yml b/05-architecture/05-03-cdc/05-03-01-producer/contracts/should_return_non_fraud.yml new file mode 100644 index 0000000..d2b1417 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/contracts/should_return_non_fraud.yml @@ -0,0 +1,15 @@ +request: + method: POST + url: /fraudCheck + headers: + Content-Type: application/json + body: + uuid: 6cb4521f-49da-48e5-9ea2-4a1d3899581d + person: + name: Jacek + surname: Dubilas + date_of_birth: "1980-03-08" + gender: MALE + national_id_number: "80030818293" +response: + status: 200 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/__init__.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/customer/__init__.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/customer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/customer/customer.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/customer/customer.py new file mode 100644 index 0000000..e3abc18 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/customer/customer.py @@ -0,0 +1,28 @@ +from dataclasses import dataclass +from uuid import UUID + +from smarttesting.customer.person import Person + + +@dataclass +class Customer: + """Klient. Klasa opakowująca osobę do zweryfikowania.""" + + _uuid: UUID + _person: Person + + @property + def uuid(self) -> UUID: + return self._uuid + + @property + def person(self) -> Person: + return self._person + + @property + def is_student(self) -> bool: + return self._person.is_student + + @property + def student(self): + return self._person.student diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/customer/person.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/customer/person.py new file mode 100644 index 0000000..7825d55 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/customer/person.py @@ -0,0 +1,64 @@ +import enum +from dataclasses import dataclass +from datetime import date + + +class Gender(enum.Enum): + MALE = enum.auto() + FEMALE = enum.auto() + + +class Status(enum.Enum): + STUDENT = enum.auto() + NOT_STUDENT = enum.auto() + + +@dataclass +class Person: + """Reprezentuje osobę do zweryfikowania.""" + + _name: str + _surname: str + _date_of_birth: date + _gender: Gender + _national_id_number: str + _status: Status = Status.NOT_STUDENT + + @property + def name(self) -> str: + return self._name + + @property + def surname(self) -> str: + return self._surname + + @property + def date_of_birth(self) -> date: + return self._date_of_birth + + @property + def gender(self) -> Gender: + return self._gender + + @property + def national_id_number(self) -> str: + return self._national_id_number + + @property + def is_student(self) -> bool: + return self._status == Status.STUDENT + + def student(self) -> None: + self._status = Status.STUDENT + + @property + def age(self): + today = date.today() + years_diff = today.year - self._date_of_birth.year + had_birthday_this_year = ( + today.replace(year=self._date_of_birth.year) < self._date_of_birth + ) + if had_birthday_this_year: + years_diff -= 1 + + return years_diff diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/message.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/message.py new file mode 100644 index 0000000..1a51c39 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/message.py @@ -0,0 +1,17 @@ +from typing import ClassVar, Dict, Type + + +class Message: + """Klasa bazowa dla wszystkich wiadomości. + + Potrzebna jest nam 'jedynie' do implementacji własnego kodeka do tasków Celery + by można było przekazywać instancje dataclass jako argumenty wywołania tasków.""" + + __messages_by_name: ClassVar[Dict[str, Type]] = {} + + def __init_subclass__(cls) -> None: + cls.__messages_by_name[cls.__name__] = cls + + @classmethod + def subclass_for_name(cls, name: str) -> Type: + return cls.__messages_by_name[name] diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/serialization.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/serialization.py new file mode 100644 index 0000000..c69545c --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/serialization.py @@ -0,0 +1,53 @@ +import functools +import json +from typing import Any, Hashable, Type, cast + +from marshmallow import Schema +from marshmallow_dataclass import class_schema +from smarttesting.message import Message + +__all__ = [ + "dataclass_dump", + "dataclass_load", +] + + +def dataclass_dump(data: Any) -> str: + return json.dumps(data, cls=Encoder) + + +def dataclass_load(data: Any) -> Any: + return json.loads(data, object_hook=decoder) + + +class Encoder(json.JSONEncoder): + def default(self, o: Any) -> Any: + try: + schema = _get_schema_for_dataclass(cast(Hashable, type(o))) + except TypeError: + return json.JSONEncoder.default(self, o) + else: + dict_repr = schema.dump(o) + dict_repr["__dataclass_name__"] = type(o).__name__ + return dict_repr + + +def decoder(obj: Any) -> Any: + if "__dataclass_name__" in obj: + dataclass_name = obj.pop("__dataclass_name__") + dataclass = Message.subclass_for_name(dataclass_name) + schema = _get_schema_for_dataclass(cast(Hashable, dataclass)) + return schema.load(obj) + else: + return obj + + +@functools.lru_cache(maxsize=None) +def _get_schema_for_dataclass(dataclass_obj: Type) -> Schema: + """ + Funkcja budująca instancję schemę dla podanej klasy udekorowanej @dataclass. + + Schema jest bezstanowa, więc bezpieczne jest reużywanie obiektów. + """ + schema_cls = class_schema(dataclass_obj) + return schema_cls() diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/__init__.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/__init__.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/bik_verification_service.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/bik_verification_service.py new file mode 100644 index 0000000..f38d835 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/bik_verification_service.py @@ -0,0 +1,35 @@ +import logging +from dataclasses import dataclass + +import requests +from requests.exceptions import RequestException +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, + Status, +) + +logger = logging.getLogger(__name__) + + +@dataclass +class BIKVerificationService: + """Klient do komunikacji z Biurem Informacji Kredytowej.""" + + _bik_service_url: str + + def verify(self, customer: Customer) -> CustomerVerificationResult: + """ + Weryfikuje czy dana osoba jest oszustem poprzez wysłanie zapytania po HTTP + do BIK. Do wykonania zapytania po HTTP wykorzystujemy bibliotekę `requests`. + """ + try: + id_number = customer.person.national_id_number + response = requests.get(self._bik_service_url + id_number) + + if response.text == Status.VERIFICATION_PASSED.name: + return CustomerVerificationResult.create_passed(customer.uuid) + except RequestException: + logger.exception("HTTP request failed") + + return CustomerVerificationResult.create_failed(customer.uuid) diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/customer_verification.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/customer_verification.py new file mode 100644 index 0000000..af6ed67 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/customer_verification.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass + +from smarttesting.customer.person import Person +from smarttesting.message import Message +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, +) + + +@dataclass(frozen=True) +class CustomerVerification(Message): + """Klasa wiadomości, którą wysyłamy poprzez brokera. + + Reprezentuje osobę i rezultat weryfikacji. + """ + + person: Person + result: CustomerVerificationResult diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/customer_verification_result.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/customer_verification_result.py new file mode 100644 index 0000000..3f3006f --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/customer_verification_result.py @@ -0,0 +1,36 @@ +import enum +from dataclasses import dataclass +from uuid import UUID + + +class Status(enum.Enum): + VERIFICATION_PASSED = "VERIFICATION_PASSED" + VERIFICATION_FAILED = "VERIFICATION_FAILED" + + +@dataclass(frozen=True) +class CustomerVerificationResult: + """Rezultat weryfikacji klienta.""" + + _user_id: UUID + _status: Status + + @property + def user_id(self) -> UUID: + return self._user_id + + @property + def status(self) -> Status: + return self._status + + @property + def passed(self) -> bool: + return self._status == Status.VERIFICATION_PASSED + + @staticmethod + def create_passed(user_id: UUID) -> "CustomerVerificationResult": + return CustomerVerificationResult(user_id, Status.VERIFICATION_PASSED) + + @staticmethod + def create_failed(user_id: UUID) -> "CustomerVerificationResult": + return CustomerVerificationResult(user_id, Status.VERIFICATION_FAILED) diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/customer_verifier.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/customer_verifier.py new file mode 100644 index 0000000..9863d4a --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/customer_verifier.py @@ -0,0 +1,80 @@ +from dataclasses import dataclass +from typing import Set + +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting.verifier.customer.customer_verification import CustomerVerification +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, + Status, +) +from smarttesting.verifier.customer.fraud_alert_task import FraudAlertTask +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto +from smarttesting.verifier.verification import Verification + + +@dataclass +class CustomerVerifier: + """Weryfikacja czy klient jest oszustem czy nie. + + Przechodzi po różnych implementacjach weryfikacji i jeśli, przy którejś okaże się, + że użytkownik jest oszustem, wówczas wysyłamy wiadomość do brokera, z informacją + o oszuście. + """ + + _bik_verification_service: BIKVerificationService + _verifications: Set[Verification] + _repository: VerificationRepository + _fraud_alert_task: FraudAlertTask + + def verify(self, customer: Customer) -> CustomerVerificationResult: + """ + Główna metoda biznesowa. Sprawdza, czy już nie doszło do weryfikacji klienta + i jeśli rezultat zostanie odnaleziony w bazie danych to go zwraca. W innym + przypadku zapisuje wynik weryfikacji w bazie danych. Weryfikacja wówczas + zachodzi poprzez odpytanie BIKu o stan naszego klienta. + """ + prior_result = self._repository.find_by_user_id(customer.uuid) + if prior_result: + return CustomerVerificationResult( + prior_result.uuid, Status(prior_result.status) + ) + else: + return self._verify_customer(customer) + + def _verify_customer(self, customer: Customer) -> CustomerVerificationResult: + result = self._perform_checks(customer) + self._save_verification_result(customer, result) + if not result.passed: + customer_verification = CustomerVerification(customer.person, result) + self._fraud_alert_task.delay(customer_verification=customer_verification) + return result + + def _perform_checks(self, customer: Customer) -> CustomerVerificationResult: + external_result = self._bik_verification_service.verify(customer) + + person = customer.person + verifications_passed = all( + verification.passes(person) for verification in self._verifications + ) + + if external_result.passed and verifications_passed: + return CustomerVerificationResult.create_passed(customer.uuid) + else: + return CustomerVerificationResult.create_failed(customer.uuid) + + def _save_verification_result( + self, customer: Customer, result: CustomerVerificationResult + ) -> None: + self._repository.save( + VerifiedPersonDto( + uuid=customer.uuid, + national_identification_number=customer.person.national_id_number, + status=result.status, + ) + ) diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/fraud_alert_task.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/fraud_alert_task.py new file mode 100644 index 0000000..010ab5c --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/fraud_alert_task.py @@ -0,0 +1,17 @@ +from typing import Protocol + +from smarttesting.verifier.customer.customer_verification import CustomerVerification + + +class TaskResult(Protocol): + """Prosty protokół opokowujący AsyncResult z Celery.""" + + def get(self) -> None: + ... + + +class FraudAlertTask(Protocol): + """Prosty protokół opakowaujący taska celery z danym argumentem.""" + + def delay(self, *, customer_verification: CustomerVerification) -> TaskResult: + ... diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/fraud_detected_handler.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/fraud_detected_handler.py new file mode 100644 index 0000000..91396f3 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/fraud_detected_handler.py @@ -0,0 +1,26 @@ +import logging + +from injector import Inject +from smarttesting.verifier.customer.customer_verification import CustomerVerification +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto + +logger = logging.getLogger(__name__) + + +def fraud_detected_handler( + repo: Inject[VerificationRepository], *, customer_verification: CustomerVerification +) -> None: + """Implementacja zadania przechodzącego przez brokera RabbitMQ.""" + logger.info("Got customer verification: %s", customer_verification) + person = customer_verification.person + result = customer_verification.result + repo.save( + VerifiedPersonDto( + uuid=result.user_id, + national_identification_number=person.national_id_number, + status=result.status, + ) + ) diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/module.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/module.py new file mode 100644 index 0000000..4005ee3 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/module.py @@ -0,0 +1,34 @@ +from typing import Set + +import injector +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting.verifier.customer.customer_verifier import ( + CustomerVerifier, + FraudAlertTask, +) +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.verification import Verification + + +class CustomerModule(injector.Module): + """Moduł injectora dla modułu klienta.""" + + @injector.provider + def bik_verification_service(self) -> BIKVerificationService: + return BIKVerificationService("http://localhost") + + @injector.provider + def customer_verifier( + self, + bik_verification_service: BIKVerificationService, + verifications: Set[Verification], + repo: VerificationRepository, + fraud_alert_task: FraudAlertTask, + ) -> CustomerVerifier: + return CustomerVerifier( + bik_verification_service, verifications, repo, fraud_alert_task + ) diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/__init__.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/age.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/age.py new file mode 100644 index 0000000..2bebb13 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/age.py @@ -0,0 +1,11 @@ +from smarttesting.customer.person import Person +from smarttesting.verifier.verification import Verification + + +class AgeVerification(Verification): + """Weryfikacja wieku osoby wnioskującej o udzielenie pożyczki.""" + + def passes(self, person: Person) -> bool: + if person.age < 0: + raise ValueError("Age cannot be negative!") + return 18 <= person.age <= 99 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/identification_number.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/identification_number.py new file mode 100644 index 0000000..194cdc8 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/identification_number.py @@ -0,0 +1,45 @@ +from smarttesting.customer.person import Gender, Person +from smarttesting.verifier.verification import Verification + + +class IdentificationNumberVerification(Verification): + """Weryfikacja poprawności numeru PESEL. + + Zobacz: https://pl.wikipedia.org/wiki/PESEL#Cyfra_kontrolna_i_sprawdzanie_poprawno.C5.9Bci_numeru + """ + + def passes(self, person: Person) -> bool: + return ( + self._gender_matches_id_number(person) + and self._starts_with_date_of_birth(person) + and self._weight_is_correct(person) + ) + + def _gender_matches_id_number(self, person: Person) -> bool: + tenth_character = person.national_id_number[9:10] + if int(tenth_character) % 2 == 0: + return person.gender == Gender.FEMALE + else: + return person.gender == Gender.MALE + + def _starts_with_date_of_birth(self, person: Person) -> bool: + dob_formatted = person.date_of_birth.strftime("%y%m%d") + if dob_formatted[0] == "0": + month = person.date_of_birth.month + 20 + dob_formatted = dob_formatted[:2] + str(month) + dob_formatted[4:] + + return dob_formatted == person.national_id_number[:6] + + def _weight_is_correct(self, person: Person) -> bool: + if len(person.national_id_number) != 11: + return False + + weights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3] + weight_sum = sum( + int(person.national_id_number[index]) * weights[index] + for index in range(10) + ) + actual_sum = (10 - weight_sum % 10) % 10 + + check_sum = int(person.national_id_number[10]) + return actual_sum == check_sum diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/module.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/module.py new file mode 100644 index 0000000..1775bcf --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/module.py @@ -0,0 +1,24 @@ +from typing import Set + +import injector +from smarttesting.verifier.customer.verification.age import AgeVerification +from smarttesting.verifier.customer.verification.identification_number import ( + IdentificationNumberVerification, +) +from smarttesting.verifier.verification import Verification + + +class VerificationModule(injector.Module): + @injector.provider + def age(self) -> AgeVerification: + return AgeVerification() + + @injector.provider + def id_number(self) -> IdentificationNumberVerification: + return IdentificationNumberVerification() + + @injector.provider + def verifications( + self, age: AgeVerification, id_number: IdentificationNumberVerification + ) -> Set[Verification]: + return {age, id_number} diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification_repository.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification_repository.py new file mode 100644 index 0000000..17060de --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification_repository.py @@ -0,0 +1,15 @@ +import abc +from typing import Optional +from uuid import UUID + +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto + + +class VerificationRepository(abc.ABC): + @abc.abstractmethod + def find_by_user_id(self, user_id: UUID) -> Optional[VerifiedPersonDto]: + pass + + @abc.abstractmethod + def save(self, verified_person: VerifiedPersonDto) -> None: + pass diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verified_person.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verified_person.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verified_person_dto.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verified_person_dto.py new file mode 100644 index 0000000..936d466 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verified_person_dto.py @@ -0,0 +1,11 @@ +from dataclasses import dataclass +from uuid import UUID + +from smarttesting.verifier.customer.customer_verification_result import Status + + +@dataclass +class VerifiedPersonDto: + uuid: UUID + national_identification_number: str + status: Status diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/verification.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/verification.py new file mode 100644 index 0000000..fb44e92 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/verification.py @@ -0,0 +1,9 @@ +import abc + +from smarttesting.customer.person import Person + + +class Verification(abc.ABC): + @abc.abstractmethod + def passes(self, person: Person) -> bool: + pass diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_api/__init__.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_api/web_app.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_api/web_app.py new file mode 100644 index 0000000..44c4355 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_api/web_app.py @@ -0,0 +1,63 @@ +"""Prosta, demonstracyjna aplikacja flaskowa.""" +import os +from http import HTTPStatus + +import marshmallow +import marshmallow_dataclass +from flask import Flask, Response, jsonify, request +from flask_expects_json import expects_json +from flask_injector import FlaskInjector +from marshmallow import ValidationError +from marshmallow.fields import Field +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.customer_verifier import CustomerVerifier +from smarttesting_main.smart_testing_application import assemble +from sqlalchemy.orm import Session + +DEV_MODE = False +if os.environ.get("APP_ENV") == "DEV": + os.environ["FLASK_ENV"] = "development" + DEV_MODE = True + + +app = Flask(__name__) + + +@app.after_request # type: ignore +def close_tx(response: Response, session: Session) -> Response: + session.commit() + session.close() + return response + + +class PrivateFieldsCapableSchema(marshmallow.Schema): + def on_bind_field(self, field_name: str, field_obj: Field) -> None: + # Dataclasses (w przeciwieństwie do attrs) nie aliasują prywatnych pól + # w __init__, więc żeby API nie wymagało podawania pól w formacie "_uuid", + # aliasujemy je usuwając podkreślnik + field_obj.data_key = field_name.lstrip("_") + + +CustomerSchema = marshmallow_dataclass.class_schema( + Customer, base_schema=PrivateFieldsCapableSchema +) + + +@app.route("/fraudCheck", methods=["POST"]) +@expects_json() +def fraud_check(verifier: CustomerVerifier): + try: + customer = CustomerSchema().load(request.json) # type: ignore + except ValidationError as validation_error: + return jsonify(validation_error.messages), HTTPStatus.BAD_REQUEST + + result = verifier.verify(customer=customer) + if result.passed: + return jsonify({"message": "Weryfikacja udana"}) + else: + return jsonify({"message": "Bagiety już jadą"}), HTTPStatus.UNAUTHORIZED + + +APP_ENV = "DEV" if DEV_MODE else "PROD" +app_injector = assemble(env=APP_ENV) # type: ignore +FlaskInjector(app=app, injector=app_injector) diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/__init__.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/celery_app.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/celery_app.py new file mode 100644 index 0000000..55b6ca9 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/celery_app.py @@ -0,0 +1,9 @@ +import os + +from celery import Celery +from smarttesting_main.smart_testing_application import assemble + +env = os.environ.get("APP_ENV", "DEV") +app_injector = assemble(env=env) # type: ignore + +app = app_injector.get(Celery) diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/celery_module.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/celery_module.py new file mode 100644 index 0000000..8df53d9 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/celery_module.py @@ -0,0 +1,42 @@ +from typing import List, NewType, cast + +import injector +from celery import Celery, Task +from kombu.serialization import register +from smarttesting import serialization +from smarttesting.verifier.customer.fraud_alert_task import FraudAlertTask +from smarttesting.verifier.customer.fraud_detected_handler import fraud_detected_handler +from smarttesting_main.task import task_with_injectables + +CeleryConfig = NewType("CeleryConfig", object) + + +class CeleryModule(injector.Module): + def __init__(self) -> None: + register( + "dataclasses_serialization", + serialization.dataclass_dump, + serialization.dataclass_load, + content_type="application/json", + content_encoding="utf-8", + ) + + @injector.singleton + @injector.provider + def celery(self, container: injector.Injector, config: CeleryConfig) -> Celery: + app = Celery(config_source=config) + app.__injector__ = container + return app + + @injector.singleton + @injector.provider + def fraud_alert_task(self, celery: Celery) -> FraudAlertTask: + # To robimy zamiast dekorowania funkcji zadania @app.task + registered_celery_task = celery.task(typing=False)(fraud_detected_handler) + task_with_injected_dependencies = task_with_injectables(registered_celery_task) + return cast(FraudAlertTask, task_with_injected_dependencies) + + @injector.multiprovider + def tasks(self, fraud_alert_task: FraudAlertTask) -> List[Task]: + # Potrzebne do rejestracji zadań przez Celery + return [fraud_alert_task] # type: ignore diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/dev_modules.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/dev_modules.py new file mode 100644 index 0000000..112fd68 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/dev_modules.py @@ -0,0 +1,23 @@ +import injector +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, +) + + +class DevModule(injector.Module): + """Moduł injectora nadpisujący niektóre klasy na potrzeby lokalnego środowiska.""" + + @injector.provider + def stubbed_bik_verification_service(self) -> BIKVerificationService: + class BIKVerificationServiceStub(BIKVerificationService): + def verify(self, customer: Customer) -> CustomerVerificationResult: + if customer.person.surname == "Fraudeusz": + return CustomerVerificationResult.create_failed(customer.uuid) + else: + return CustomerVerificationResult.create_passed(customer.uuid) + + return BIKVerificationServiceStub(_bik_service_url="") diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/__init__.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/module.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/module.py new file mode 100644 index 0000000..2c1dc4c --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/module.py @@ -0,0 +1,14 @@ +import injector +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting_main.infrastructure.verification_repo import ( + SqlAlchemyVerificationRepository, +) +from sqlalchemy.orm import Session + + +class InfrastructureModule(injector.Module): + @injector.provider + def repo(self, session: Session) -> VerificationRepository: + return SqlAlchemyVerificationRepository(session) diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/verification_repo.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/verification_repo.py new file mode 100644 index 0000000..23011c2 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/verification_repo.py @@ -0,0 +1,39 @@ +from dataclasses import dataclass +from typing import Optional +from uuid import UUID + +from smarttesting.verifier.customer.customer_verification_result import Status +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto +from smarttesting_main.infrastructure.verified_person import VerifiedPerson +from sqlalchemy.orm import Session + + +@dataclass +class SqlAlchemyVerificationRepository(VerificationRepository): + _session: Session + + def find_by_user_id(self, user_id: UUID) -> Optional[VerifiedPersonDto]: + model = ( + self._session.query(VerifiedPerson) + .filter(VerifiedPerson.uuid == str(user_id)) + .first() + ) + if model: + return VerifiedPersonDto( + uuid=UUID(model.uuid), + national_identification_number=model.national_identification_number, + status=Status(model.status), + ) + return None + + def save(self, verified_person: VerifiedPersonDto) -> None: + model = VerifiedPerson( + uuid=str(verified_person.uuid), + national_identification_number=verified_person.national_identification_number, + status=verified_person.status.value, + ) + self._session.add(model) + self._session.flush() diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/verified_person.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/verified_person.py new file mode 100644 index 0000000..961cc06 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/verified_person.py @@ -0,0 +1,21 @@ +from typing import Any + +from sqlalchemy import Column, Integer, String +from sqlalchemy.ext.declarative import declarative_base + +Base: Any = declarative_base() + + +class VerifiedPerson(Base): + """ + Model bazodanowy. Wykorzystujemy ORM (mapowanie obiektowo relacyjne) + i obiekt tej klasy mapuje się na tabelę "verified". Każde pole klasy to osobna + kolumna w bazie danych. + """ + + __tablename__ = "verified" + + id = Column(Integer(), primary_key=True) + uuid: str = Column(String(36)) + national_identification_number: str = Column(String(255)) + status: str = Column(String(255)) diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/smart_testing_application.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/smart_testing_application.py new file mode 100644 index 0000000..9d01fd3 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/smart_testing_application.py @@ -0,0 +1,91 @@ +import os +from dataclasses import dataclass +from typing import List, Literal, cast + +import injector +from celery import Task +from smarttesting.verifier.customer.module import CustomerModule +from smarttesting.verifier.customer.verification.module import VerificationModule +from smarttesting_main.celery_module import CeleryConfig, CeleryModule +from smarttesting_main.dev_modules import DevModule +from smarttesting_main.infrastructure.module import InfrastructureModule +from smarttesting_main.infrastructure.verified_person import Base +from sqlalchemy import create_engine +from sqlalchemy.orm import Session, scoped_session, sessionmaker + +Env = Literal["PROD", "DEV"] + + +def assemble(env: Env = "PROD") -> injector.Injector: + """Zainicjowanie kontenera IoC.""" + extra_modules: List[injector.Module] = [] + + db_dsn = os.environ.get("DB_URL", "sqlite:///dev_database.db") + broker_url = os.environ.get("BROKER_URL", "memory://") + if env == "PROD": + extra_modules += [ProdConfigModule(broker_url)] + elif env == "DEV": + extra_modules += [DevConfigModule(broker_url), DevModule()] + + modules = [ + VerificationModule(), + CustomerModule(), + CeleryModule(), + DbModule(db_dsn), + InfrastructureModule(), + ] + extra_modules + + container = injector.Injector(modules=modules, auto_bind=False) + + # Zarejestruj taski w Celery wywołując ich wstrzyknięcie + container.get(List[Task]) + + return container + + +@dataclass +class ProdConfigModule(injector.Module): + _broker_url: str + + @injector.singleton + @injector.provider + def celery_config(self) -> CeleryConfig: + CeleryConfigProd.broker_url = self._broker_url + return cast(CeleryConfig, CeleryConfigProd) + + +@dataclass +class DevConfigModule(injector.Module): + _broker_url: str + + @injector.singleton + @injector.provider + def celery_config(self) -> CeleryConfig: + CeleryConfigDev.broker_url = self._broker_url + return cast(CeleryConfig, CeleryConfigDev) + + +class CeleryConfigProd: + accept_content = {"json", "dataclasses_serialization"} + task_serializer = "dataclasses_serialization" + result_backend = "rpc://" + result_persistent = False + broker_url = "" # będzie nadpisane + + +class CeleryConfigDev(CeleryConfigProd): + worker_concurrency = 1 + task_always_eager = True + + +class DbModule(injector.Module): + def __init__(self, db_dsn: str) -> None: + self._db_dsn = db_dsn + self._engine = create_engine(self._db_dsn) + self._scoped_session_factory = scoped_session(sessionmaker(bind=self._engine)) + # Stwórz schemat bazy danych. Normalnie odbywa się to przez migracje + Base.metadata.create_all(self._engine) + + @injector.provider + def session(self) -> Session: + return self._scoped_session_factory() diff --git a/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/task.py b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/task.py new file mode 100644 index 0000000..48fa2e6 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/task.py @@ -0,0 +1,57 @@ +import functools +import inspect + +from celery import Task +from injector import Injector, get_bindings +from sqlalchemy.orm import Session + + +def task_with_injectables(task: Task) -> Task: + """Dekorator na taski, który zapewni wstrzykiwanie zależności i transakcję. + + Od funkcji-taska wymagane jest, by wszystkie zależności do wstrzyknięte były + argumentami pozycyjnymi zaś wszystkie argumenty niewstrzykiwane były zadeklarowane + jako keyword-only. + + Jest to podyktowane uproszczeniami w tej integracji Celery z Injectorem. Przykład: + ``` + @task_with_injectables + @app.task(typing=False) + def add(x: Inject[int], y: Inject[float], *, z: int) -> None: + print(x + y + z) + ``` + + """ + # Safety-checks zanim przejdziemy dalej + + # Sprawdzamy, czy flaga typing jest ustawiona na False. Inaczej Celery protestuje, + # że wyzwalamy taska bez przekazania wszystkich argumentów (także tych, które potem + # będą wstrzyknięte) + assert ( + task.typing is False + ), "Wymagane jest wyłączenie sprawdzania argumentów przy schedulowaniu taska" + # Upewnijmy się, że niewstrzykiwane argumenty są opisane jako keyword-only + args_spec = inspect.getfullargspec(task.run) + bindings = get_bindings(task.run) + assert set(bindings) == set( + args_spec.args + ), "Wstrzykiwane argumenty muszą być pozycyjne" + assert args_spec.varargs is None, "*args nie jest wspierane" + assert args_spec.varkw is None, "**kwargs nie jest wspierane" + + actual_run = task.run + + @functools.wraps(actual_run) + def wrapped_run(*args, **kwargs): + injector: Injector = task.app.__injector__ + session = injector.get(Session) + try: + result = injector.call_with_injection(actual_run, args=args, kwargs=kwargs) + session.commit() + return result + finally: + session.close() + + task.run = wrapped_run + + return task diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/bootJarMainClassName b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/bootJarMainClassName new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/contractTest/contracts/ContractVerifierTest.class b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/contractTest/contracts/ContractVerifierTest.class new file mode 100644 index 0000000..ab4fc20 Binary files /dev/null and b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/contractTest/contracts/ContractVerifierTest.class differ diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/ContractTestsBase$Config.class b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/ContractTestsBase$Config.class new file mode 100644 index 0000000..513fe0c Binary files /dev/null and b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/ContractTestsBase$Config.class differ diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/ContractTestsBase.class b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/ContractTestsBase.class new file mode 100644 index 0000000..6b1cfcc Binary files /dev/null and b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/ContractTestsBase.class differ diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/ContractVerifierCamelHelper.class b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/ContractVerifierCamelHelper.class new file mode 100644 index 0000000..c7781b3 Binary files /dev/null and b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/ContractVerifierCamelHelper.class differ diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/MessagingAutoConfig$1.class b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/MessagingAutoConfig$1.class new file mode 100644 index 0000000..7eacd26 Binary files /dev/null and b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/MessagingAutoConfig$1.class differ diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/MessagingAutoConfig.class b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/MessagingAutoConfig.class new file mode 100644 index 0000000..0799234 Binary files /dev/null and b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/MessagingAutoConfig.class differ diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/generated-test-sources/contractTest/java/contracts/ContractVerifierTest.java b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/generated-test-sources/contractTest/java/contracts/ContractVerifierTest.java new file mode 100644 index 0000000..504be87 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/generated-test-sources/contractTest/java/contracts/ContractVerifierTest.java @@ -0,0 +1,53 @@ +package contracts; + +import contracts.ContractTestsBase; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import io.restassured.specification.RequestSpecification; +import io.restassured.response.Response; + +import static org.springframework.cloud.contract.verifier.assertion.SpringCloudContractAssertions.assertThat; +import static org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.*; +import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson; +import static io.restassured.RestAssured.*; + +@SuppressWarnings("rawtypes") +public class ContractVerifierTest extends ContractTestsBase { + + @Test + public void validate_should_return_fraud() throws Exception { + // given: + RequestSpecification request = given() + .header("Content-Type", "application/json") + .body("{\"uuid\":\"89c878e3-38f7-4831-af6c-c3b4a0669022\",\"person\":{\"name\":\"Stefania\",\"surname\":\"Stefanowska\",\"date_of_birth\":\"2020-01-01\",\"gender\":\"FEMALE\",\"national_id_number\":\"1234567890\"}}"); + + // when: + Response response = given().spec(request) + + .post("/fraudCheck"); + + // then: + assertThat(response.statusCode()).isEqualTo(401); + + } + + @Test + public void validate_should_return_non_fraud() throws Exception { + // given: + RequestSpecification request = given() + .header("Content-Type", "application/json") + .body("{\"uuid\":\"6cb4521f-49da-48e5-9ea2-4a1d3899581d\",\"person\":{\"name\":\"Jacek\",\"surname\":\"Dubilas\",\"date_of_birth\":\"1980-03-08\",\"gender\":\"MALE\",\"national_id_number\":\"80030818293\"}}"); + + // when: + Response response = given().spec(request) + + .post("/fraudCheck"); + + // then: + assertThat(response.statusCode()).isEqualTo(200); + + } + +} diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/libs/smarttesting-0.0.1.RELEASE-stubs.jar b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/libs/smarttesting-0.0.1.RELEASE-stubs.jar new file mode 100644 index 0000000..8810569 Binary files /dev/null and b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/libs/smarttesting-0.0.1.RELEASE-stubs.jar differ diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/classes/contracts.ContractVerifierTest.html b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/classes/contracts.ContractVerifierTest.html new file mode 100644 index 0000000..e64d4fa --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/classes/contracts.ContractVerifierTest.html @@ -0,0 +1,352 @@ + + + + + +Test results - ContractVerifierTest + + + + + +
+

ContractVerifierTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
2
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.891s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + + + + + + +
TestDurationResult
validate_should_return_fraud()0.813spassed
validate_should_return_non_fraud()0.078spassed
+
+
+

Standard output

+ +
18:29:43.695 [Test worker] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
+18:29:43.704 [Test worker] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
+18:29:43.734 [Test worker] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [contracts.ContractVerifierTest] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
+18:29:43.743 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [contracts.ContractVerifierTest], using SpringBootContextLoader
+18:29:43.746 [Test worker] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [contracts.ContractVerifierTest]: class path resource [contracts/ContractVerifierTest-context.xml] does not exist
+18:29:43.746 [Test worker] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [contracts.ContractVerifierTest]: class path resource [contracts/ContractVerifierTestContext.groovy] does not exist
+18:29:43.747 [Test worker] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [contracts.ContractVerifierTest]: no resource found for suffixes {-context.xml, Context.groovy}.
+18:29:43.789 [Test worker] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [contracts.ContractVerifierTest]
+18:29:43.867 [Test worker] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - @TestExecutionListeners is not present for class [contracts.ContractVerifierTest]: using defaults.
+18:29:43.868 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.event.ApplicationEventsTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
+18:29:43.881 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@61441381, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@12c3fcec, org.springframework.test.context.event.ApplicationEventsTestExecutionListener@5e1f9e1d, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@4d17241e, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@c0efe26, org.springframework.test.context.support.DirtiesContextTestExecutionListener@1a747d7, org.springframework.test.context.transaction.TransactionalTestExecutionListener@236ace88, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@5729392f, org.springframework.test.context.event.EventPublishingTestExecutionListener@2cad1222, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@178f619f, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@6264f368, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@7c1a4e4a, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@38e27fe9, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener@540d72f3, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@113724a9]
+18:29:43.884 [Test worker] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@2bb03c7 testClass = ContractVerifierTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [MergedContextConfiguration@649a898b testClass = ContractVerifierTest, locations = '{}', classes = '{class contracts.ContractTestsBase$Config}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@59ddba0c, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@2197dc88, [ImportsContextCustomizer@6e461332 key = [org.springframework.cloud.contract.verifier.messaging.stream.ContractVerifierStreamAutoConfiguration, org.springframework.cloud.contract.verifier.messaging.integration.ContractVerifierIntegrationConfiguration, org.springframework.cloud.contract.verifier.messaging.amqp.ContractVerifierAmqpAutoConfiguration, org.springframework.cloud.contract.verifier.messaging.amqp.RabbitMockConnectionFactoryAutoConfiguration, org.springframework.cloud.contract.verifier.messaging.camel.ContractVerifierCamelConfiguration, org.springframework.cloud.contract.verifier.messaging.jms.ContractVerifierJmsConfiguration, org.springframework.cloud.contract.verifier.messaging.kafka.ContractVerifierKafkaConfiguration, org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVerifierAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@6ea38c76, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@57bd4084, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@150c9662, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@430cce80], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]], class annotated with @DirtiesContext [false] with mode [null].
+18:29:43.895 [Test worker] DEBUG org.springframework.test.context.support.DependencyInjectionTestExecutionListener - Performing dependency injection for test context [[DefaultTestContext@2bb03c7 testClass = ContractVerifierTest, testInstance = contracts.ContractVerifierTest@38159c7, testMethod = [null], testException = [null], mergedContextConfiguration = [MergedContextConfiguration@649a898b testClass = ContractVerifierTest, locations = '{}', classes = '{class contracts.ContractTestsBase$Config}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@59ddba0c, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@2197dc88, [ImportsContextCustomizer@6e461332 key = [org.springframework.cloud.contract.verifier.messaging.stream.ContractVerifierStreamAutoConfiguration, org.springframework.cloud.contract.verifier.messaging.integration.ContractVerifierIntegrationConfiguration, org.springframework.cloud.contract.verifier.messaging.amqp.ContractVerifierAmqpAutoConfiguration, org.springframework.cloud.contract.verifier.messaging.amqp.RabbitMockConnectionFactoryAutoConfiguration, org.springframework.cloud.contract.verifier.messaging.camel.ContractVerifierCamelConfiguration, org.springframework.cloud.contract.verifier.messaging.jms.ContractVerifierJmsConfiguration, org.springframework.cloud.contract.verifier.messaging.kafka.ContractVerifierKafkaConfiguration, org.springframework.cloud.contract.verifier.messaging.noop.NoOpContractVerifierAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@6ea38c76, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@57bd4084, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@150c9662, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@430cce80], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]].
+18:29:43.920 [Test worker] DEBUG org.springframework.test.context.support.TestPropertySourceUtils - Adding inlined properties to environment: {spring.jmx.enabled=false, org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}
+
+  .   ____          _            __ _ _
+ /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
+( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
+ \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
+  '  |____| .__|_| |_|_| |_\__, | / / / /
+ =========|_|==============|___/=/_/_/_/
+ :: Spring Boot ::                (v2.4.6)
+
+2023-09-15 18:29:44.212  INFO 122 --- [    Test worker] contracts.ContractVerifierTest           : Starting ContractVerifierTest using Java 1.8.0_275 on fad7d23085aa with PID 122 (started by root in /spring-cloud-contract)
+2023-09-15 18:29:44.222  INFO 122 --- [    Test worker] contracts.ContractVerifierTest           : No active profile set, falling back to default profiles: default
+2023-09-15 18:29:45.103  INFO 122 --- [    Test worker] trationDelegate$BeanPostProcessorChecker : Bean 'org.apache.camel.spring.boot.CamelAutoConfiguration' of type [org.apache.camel.spring.boot.CamelAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
+2023-09-15 18:29:45.325  INFO 122 --- [    Test worker] o.apache.camel.support.LRUCacheFactory   : Detected and using LRUCacheFactory: camel-caffeine-lrucache
+2023-09-15 18:29:46.912  INFO 122 --- [    Test worker] o.s.c.c.v.m.i.ContractVerifierMessaging  : The message verifier implementation is of type [class org.springframework.cloud.contract.verifier.messaging.camel.CamelStubMessages]
+2023-09-15 18:29:46.946  INFO 122 --- [    Test worker] o.a.c.s.boot.SpringBootRoutesCollector   : Loading additional Camel XML routes from: classpath:camel/*.xml
+2023-09-15 18:29:46.947  INFO 122 --- [    Test worker] o.a.c.s.boot.SpringBootRoutesCollector   : Loading additional Camel XML rests from: classpath:camel-rest/*.xml
+2023-09-15 18:29:46.976  INFO 122 --- [    Test worker] o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 3.4.3 (camel-1) is starting
+2023-09-15 18:29:46.982  INFO 122 --- [    Test worker] o.a.c.impl.engine.AbstractCamelContext   : StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
+2023-09-15 18:29:46.982  INFO 122 --- [    Test worker] o.a.c.impl.engine.AbstractCamelContext   : Using HealthCheck: camel-health
+2023-09-15 18:29:46.983  INFO 122 --- [    Test worker] o.a.c.impl.engine.AbstractCamelContext   : Total 0 routes, of which 0 are started
+2023-09-15 18:29:46.983  INFO 122 --- [    Test worker] o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 3.4.3 (camel-1) started in 0.007 seconds
+2023-09-15 18:29:46.986  INFO 122 --- [    Test worker] contracts.ContractVerifierTest           : Started ContractVerifierTest in 3.065 seconds (JVM running for 3.899)
+2023-09-15 18:29:47.180  WARN 122 --- [    Test worker] contracts.ContractTestsBase              : An exception occurred while trying to setup messaging from contract
+
+java.lang.IllegalStateException: java.io.FileNotFoundException: should_return_fraud.yml
+	at org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.fileToBytes(ContractVerifierUtil.java:83) ~[spring-cloud-contract-verifier-3.0.3.jar:3.0.3]
+	at org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.contract(ContractVerifierUtil.java:136) ~[spring-cloud-contract-verifier-3.0.3.jar:3.0.3]
+	at contracts.ContractTestsBase.setupMessagingFromContract(ContractTestsBase.java:104) [test/:na]
+	at contracts.ContractTestsBase.setup(ContractTestsBase.java:99) [test/:na]
+	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_275]
+	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_275]
+	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_275]
+	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_275]
+	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688) [junit-platform-commons-1.7.2.jar:1.7.2]
+	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) [junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) [junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149) [junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptLifecycleMethod(TimeoutExtension.java:126) [junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptBeforeEachMethod(TimeoutExtension.java:76) [junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeMethodInExtensionContext(ClassBasedTestDescriptor.java:490) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$synthesizeBeforeEachMethodAdapter$19(ClassBasedTestDescriptor.java:475) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeEachMethods$2(TestMethodTestDescriptor.java:167) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeMethodsOrCallbacksUntilExceptionOccurs$5(TestMethodTestDescriptor.java:195) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(TestMethodTestDescriptor.java:195) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeEachMethods(TestMethodTestDescriptor.java:164) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:127) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at java.util.ArrayList.forEach(ArrayList.java:1259) ~[na:1.8.0_275]
+	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at java.util.ArrayList.forEach(ArrayList.java:1259) ~[na:1.8.0_275]
+	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248) ~[na:na]
+	at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211) ~[na:na]
+	at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226) ~[na:na]
+	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199) ~[na:na]
+	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132) ~[na:na]
+	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99) ~[na:na]
+	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79) ~[na:na]
+	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75) ~[na:na]
+	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61) ~[na:na]
+	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_275]
+	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_275]
+	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_275]
+	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_275]
+	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na]
+	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na]
+	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) ~[na:na]
+	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) ~[na:na]
+	at com.sun.proxy.$Proxy2.stop(Unknown Source) ~[na:na]
+	at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:132) ~[na:na]
+	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_275]
+	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_275]
+	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_275]
+	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_275]
+	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na]
+	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na]
+	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182) ~[na:na]
+	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164) ~[na:na]
+	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:413) ~[na:na]
+	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) ~[na:na]
+	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48) ~[na:na]
+	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_275]
+	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_275]
+	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) ~[na:na]
+	at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_275]
+Caused by: java.io.FileNotFoundException: should_return_fraud.yml
+	at org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.fileToBytes(ContractVerifierUtil.java:78) ~[spring-cloud-contract-verifier-3.0.3.jar:3.0.3]
+	... 95 common frames omitted
+
+2023-09-15 18:29:47.824  WARN 122 --- [    Test worker] contracts.ContractTestsBase              : An exception occurred while trying to setup messaging from contract
+
+java.lang.IllegalStateException: java.io.FileNotFoundException: should_return_non_fraud.yml
+	at org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.fileToBytes(ContractVerifierUtil.java:83) ~[spring-cloud-contract-verifier-3.0.3.jar:3.0.3]
+	at org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.contract(ContractVerifierUtil.java:136) ~[spring-cloud-contract-verifier-3.0.3.jar:3.0.3]
+	at contracts.ContractTestsBase.setupMessagingFromContract(ContractTestsBase.java:104) [test/:na]
+	at contracts.ContractTestsBase.setup(ContractTestsBase.java:99) [test/:na]
+	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_275]
+	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_275]
+	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_275]
+	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_275]
+	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688) [junit-platform-commons-1.7.2.jar:1.7.2]
+	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) [junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) [junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149) [junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptLifecycleMethod(TimeoutExtension.java:126) [junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptBeforeEachMethod(TimeoutExtension.java:76) [junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeMethodInExtensionContext(ClassBasedTestDescriptor.java:490) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$synthesizeBeforeEachMethodAdapter$19(ClassBasedTestDescriptor.java:475) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeEachMethods$2(TestMethodTestDescriptor.java:167) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeMethodsOrCallbacksUntilExceptionOccurs$5(TestMethodTestDescriptor.java:195) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(TestMethodTestDescriptor.java:195) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeEachMethods(TestMethodTestDescriptor.java:164) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:127) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65) ~[junit-jupiter-engine-5.7.2.jar:5.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at java.util.ArrayList.forEach(ArrayList.java:1259) ~[na:1.8.0_275]
+	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at java.util.ArrayList.forEach(ArrayList.java:1259) ~[na:1.8.0_275]
+	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51) ~[junit-platform-engine-1.7.2.jar:1.7.2]
+	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248) ~[na:na]
+	at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211) ~[na:na]
+	at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226) ~[na:na]
+	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199) ~[na:na]
+	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132) ~[na:na]
+	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99) ~[na:na]
+	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79) ~[na:na]
+	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75) ~[na:na]
+	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61) ~[na:na]
+	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_275]
+	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_275]
+	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_275]
+	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_275]
+	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na]
+	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na]
+	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) ~[na:na]
+	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) ~[na:na]
+	at com.sun.proxy.$Proxy2.stop(Unknown Source) ~[na:na]
+	at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:132) ~[na:na]
+	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_275]
+	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_275]
+	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_275]
+	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_275]
+	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na]
+	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na]
+	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182) ~[na:na]
+	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164) ~[na:na]
+	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:413) ~[na:na]
+	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) ~[na:na]
+	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48) ~[na:na]
+	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_275]
+	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_275]
+	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) ~[na:na]
+	at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_275]
+Caused by: java.io.FileNotFoundException: should_return_non_fraud.yml
+	at org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.fileToBytes(ContractVerifierUtil.java:78) ~[spring-cloud-contract-verifier-3.0.3.jar:3.0.3]
+	... 95 common frames omitted
+
+
+
+
+
+ +
+ + diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/css/base-style.css b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/css/base-style.css new file mode 100644 index 0000000..4afa73e --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/css/base-style.css @@ -0,0 +1,179 @@ + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + font-size: 12pt; +} + +body, a, a:visited { + color: #303030; +} + +#content { + padding-left: 50px; + padding-right: 50px; + padding-top: 30px; + padding-bottom: 30px; +} + +#content h1 { + font-size: 160%; + margin-bottom: 10px; +} + +#footer { + margin-top: 100px; + font-size: 80%; + white-space: nowrap; +} + +#footer, #footer a { + color: #a0a0a0; +} + +#line-wrapping-toggle { + vertical-align: middle; +} + +#label-for-line-wrapping-toggle { + vertical-align: middle; +} + +ul { + margin-left: 0; +} + +h1, h2, h3 { + white-space: nowrap; +} + +h2 { + font-size: 120%; +} + +ul.tabLinks { + padding-left: 0; + padding-top: 10px; + padding-bottom: 10px; + overflow: auto; + min-width: 800px; + width: auto !important; + width: 800px; +} + +ul.tabLinks li { + float: left; + height: 100%; + list-style: none; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 0; + -moz-border-radius: 7px; + border-radius: 7px; + margin-right: 25px; + border: solid 1px #d4d4d4; + background-color: #f0f0f0; +} + +ul.tabLinks li:hover { + background-color: #fafafa; +} + +ul.tabLinks li.selected { + background-color: #c5f0f5; + border-color: #c5f0f5; +} + +ul.tabLinks a { + font-size: 120%; + display: block; + outline: none; + text-decoration: none; + margin: 0; + padding: 0; +} + +ul.tabLinks li h2 { + margin: 0; + padding: 0; +} + +div.tab { +} + +div.selected { + display: block; +} + +div.deselected { + display: none; +} + +div.tab table { + min-width: 350px; + width: auto !important; + width: 350px; + border-collapse: collapse; +} + +div.tab th, div.tab table { + border-bottom: solid #d0d0d0 1px; +} + +div.tab th { + text-align: left; + white-space: nowrap; + padding-left: 6em; +} + +div.tab th:first-child { + padding-left: 0; +} + +div.tab td { + white-space: nowrap; + padding-left: 6em; + padding-top: 5px; + padding-bottom: 5px; +} + +div.tab td:first-child { + padding-left: 0; +} + +div.tab td.numeric, div.tab th.numeric { + text-align: right; +} + +span.code { + display: inline-block; + margin-top: 0em; + margin-bottom: 1em; +} + +span.code pre { + font-size: 11pt; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 10px; + padding-right: 10px; + margin: 0; + background-color: #f7f7f7; + border: solid 1px #d0d0d0; + min-width: 700px; + width: auto !important; + width: 700px; +} + +span.wrapped pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: break-all; +} + +label.hidden { + display: none; +} \ No newline at end of file diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/css/style.css b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/css/style.css new file mode 100644 index 0000000..3dc4913 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/css/style.css @@ -0,0 +1,84 @@ + +#summary { + margin-top: 30px; + margin-bottom: 40px; +} + +#summary table { + border-collapse: collapse; +} + +#summary td { + vertical-align: top; +} + +.breadcrumbs, .breadcrumbs a { + color: #606060; +} + +.infoBox { + width: 110px; + padding-top: 15px; + padding-bottom: 15px; + text-align: center; +} + +.infoBox p { + margin: 0; +} + +.counter, .percent { + font-size: 120%; + font-weight: bold; + margin-bottom: 8px; +} + +#duration { + width: 125px; +} + +#successRate, .summaryGroup { + border: solid 2px #d0d0d0; + -moz-border-radius: 10px; + border-radius: 10px; +} + +#successRate { + width: 140px; + margin-left: 35px; +} + +#successRate .percent { + font-size: 180%; +} + +.success, .success a { + color: #008000; +} + +div.success, #successRate.success { + background-color: #bbd9bb; + border-color: #008000; +} + +.failures, .failures a { + color: #b60808; +} + +.skipped, .skipped a { + color: #c09853; +} + +div.failures, #successRate.failures { + background-color: #ecdada; + border-color: #b60808; +} + +ul.linkList { + padding-left: 0; +} + +ul.linkList li { + list-style: none; + margin-bottom: 5px; +} diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/index.html b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/index.html new file mode 100644 index 0000000..6e2b8d4 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/index.html @@ -0,0 +1,133 @@ + + + + + +Test results - Test Summary + + + + + +
+

Test Summary

+
+ + + + + +
+
+ + + + + + + +
+
+
2
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.891s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Packages

+ + + + + + + + + + + + + + + + + + + + + +
PackageTestsFailuresIgnoredDurationSuccess rate
+contracts +2000.891s100%
+
+
+

Classes

+ + + + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+contracts.ContractVerifierTest +2000.891s100%
+
+
+ +
+ + diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/js/report.js b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/js/report.js new file mode 100644 index 0000000..83bab4a --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/js/report.js @@ -0,0 +1,194 @@ +(function (window, document) { + "use strict"; + + var tabs = {}; + + function changeElementClass(element, classValue) { + if (element.getAttribute("className")) { + element.setAttribute("className", classValue); + } else { + element.setAttribute("class", classValue); + } + } + + function getClassAttribute(element) { + if (element.getAttribute("className")) { + return element.getAttribute("className"); + } else { + return element.getAttribute("class"); + } + } + + function addClass(element, classValue) { + changeElementClass(element, getClassAttribute(element) + " " + classValue); + } + + function removeClass(element, classValue) { + changeElementClass(element, getClassAttribute(element).replace(classValue, "")); + } + + function initTabs() { + var container = document.getElementById("tabs"); + + tabs.tabs = findTabs(container); + tabs.titles = findTitles(tabs.tabs); + tabs.headers = findHeaders(container); + tabs.select = select; + tabs.deselectAll = deselectAll; + tabs.select(0); + + return true; + } + + function getCheckBox() { + return document.getElementById("line-wrapping-toggle"); + } + + function getLabelForCheckBox() { + return document.getElementById("label-for-line-wrapping-toggle"); + } + + function findCodeBlocks() { + var spans = document.getElementById("tabs").getElementsByTagName("span"); + var codeBlocks = []; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].className.indexOf("code") >= 0) { + codeBlocks.push(spans[i]); + } + } + return codeBlocks; + } + + function forAllCodeBlocks(operation) { + var codeBlocks = findCodeBlocks(); + + for (var i = 0; i < codeBlocks.length; ++i) { + operation(codeBlocks[i], "wrapped"); + } + } + + function toggleLineWrapping() { + var checkBox = getCheckBox(); + + if (checkBox.checked) { + forAllCodeBlocks(addClass); + } else { + forAllCodeBlocks(removeClass); + } + } + + function initControls() { + if (findCodeBlocks().length > 0) { + var checkBox = getCheckBox(); + var label = getLabelForCheckBox(); + + checkBox.onclick = toggleLineWrapping; + checkBox.checked = false; + + removeClass(label, "hidden"); + } + } + + function switchTab() { + var id = this.id.substr(1); + + for (var i = 0; i < tabs.tabs.length; i++) { + if (tabs.tabs[i].id === id) { + tabs.select(i); + break; + } + } + + return false; + } + + function select(i) { + this.deselectAll(); + + changeElementClass(this.tabs[i], "tab selected"); + changeElementClass(this.headers[i], "selected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var h2 = document.createElement("H2"); + + h2.appendChild(document.createTextNode(this.titles[i])); + this.headers[i].appendChild(h2); + } + + function deselectAll() { + for (var i = 0; i < this.tabs.length; i++) { + changeElementClass(this.tabs[i], "tab deselected"); + changeElementClass(this.headers[i], "deselected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var a = document.createElement("A"); + + a.setAttribute("id", "ltab" + i); + a.setAttribute("href", "#tab" + i); + a.onclick = switchTab; + a.appendChild(document.createTextNode(this.titles[i])); + + this.headers[i].appendChild(a); + } + } + + function findTabs(container) { + return findChildElements(container, "DIV", "tab"); + } + + function findHeaders(container) { + var owner = findChildElements(container, "UL", "tabLinks"); + return findChildElements(owner[0], "LI", null); + } + + function findTitles(tabs) { + var titles = []; + + for (var i = 0; i < tabs.length; i++) { + var tab = tabs[i]; + var header = findChildElements(tab, "H2", null)[0]; + + header.parentNode.removeChild(header); + + if (header.innerText) { + titles.push(header.innerText); + } else { + titles.push(header.textContent); + } + } + + return titles; + } + + function findChildElements(container, name, targetClass) { + var elements = []; + var children = container.childNodes; + + for (var i = 0; i < children.length; i++) { + var child = children.item(i); + + if (child.nodeType === 1 && child.nodeName === name) { + if (targetClass && child.className.indexOf(targetClass) < 0) { + continue; + } + + elements.push(child); + } + } + + return elements; + } + + // Entry point. + + window.onload = function() { + initTabs(); + initControls(); + }; +} (window, window.document)); \ No newline at end of file diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/packages/contracts.html b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/packages/contracts.html new file mode 100644 index 0000000..a552ae6 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/packages/contracts.html @@ -0,0 +1,103 @@ + + + + + +Test results - Package contracts + + + + + +
+

Package contracts

+ +
+ + + + + +
+
+ + + + + + + +
+
+
2
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.891s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Classes

+ + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+ContractVerifierTest +2000.891s100%
+
+
+ +
+ + diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/css/base-style.css b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/css/base-style.css new file mode 100644 index 0000000..4afa73e --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/css/base-style.css @@ -0,0 +1,179 @@ + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + font-size: 12pt; +} + +body, a, a:visited { + color: #303030; +} + +#content { + padding-left: 50px; + padding-right: 50px; + padding-top: 30px; + padding-bottom: 30px; +} + +#content h1 { + font-size: 160%; + margin-bottom: 10px; +} + +#footer { + margin-top: 100px; + font-size: 80%; + white-space: nowrap; +} + +#footer, #footer a { + color: #a0a0a0; +} + +#line-wrapping-toggle { + vertical-align: middle; +} + +#label-for-line-wrapping-toggle { + vertical-align: middle; +} + +ul { + margin-left: 0; +} + +h1, h2, h3 { + white-space: nowrap; +} + +h2 { + font-size: 120%; +} + +ul.tabLinks { + padding-left: 0; + padding-top: 10px; + padding-bottom: 10px; + overflow: auto; + min-width: 800px; + width: auto !important; + width: 800px; +} + +ul.tabLinks li { + float: left; + height: 100%; + list-style: none; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 0; + -moz-border-radius: 7px; + border-radius: 7px; + margin-right: 25px; + border: solid 1px #d4d4d4; + background-color: #f0f0f0; +} + +ul.tabLinks li:hover { + background-color: #fafafa; +} + +ul.tabLinks li.selected { + background-color: #c5f0f5; + border-color: #c5f0f5; +} + +ul.tabLinks a { + font-size: 120%; + display: block; + outline: none; + text-decoration: none; + margin: 0; + padding: 0; +} + +ul.tabLinks li h2 { + margin: 0; + padding: 0; +} + +div.tab { +} + +div.selected { + display: block; +} + +div.deselected { + display: none; +} + +div.tab table { + min-width: 350px; + width: auto !important; + width: 350px; + border-collapse: collapse; +} + +div.tab th, div.tab table { + border-bottom: solid #d0d0d0 1px; +} + +div.tab th { + text-align: left; + white-space: nowrap; + padding-left: 6em; +} + +div.tab th:first-child { + padding-left: 0; +} + +div.tab td { + white-space: nowrap; + padding-left: 6em; + padding-top: 5px; + padding-bottom: 5px; +} + +div.tab td:first-child { + padding-left: 0; +} + +div.tab td.numeric, div.tab th.numeric { + text-align: right; +} + +span.code { + display: inline-block; + margin-top: 0em; + margin-bottom: 1em; +} + +span.code pre { + font-size: 11pt; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 10px; + padding-right: 10px; + margin: 0; + background-color: #f7f7f7; + border: solid 1px #d0d0d0; + min-width: 700px; + width: auto !important; + width: 700px; +} + +span.wrapped pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: break-all; +} + +label.hidden { + display: none; +} \ No newline at end of file diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/css/style.css b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/css/style.css new file mode 100644 index 0000000..3dc4913 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/css/style.css @@ -0,0 +1,84 @@ + +#summary { + margin-top: 30px; + margin-bottom: 40px; +} + +#summary table { + border-collapse: collapse; +} + +#summary td { + vertical-align: top; +} + +.breadcrumbs, .breadcrumbs a { + color: #606060; +} + +.infoBox { + width: 110px; + padding-top: 15px; + padding-bottom: 15px; + text-align: center; +} + +.infoBox p { + margin: 0; +} + +.counter, .percent { + font-size: 120%; + font-weight: bold; + margin-bottom: 8px; +} + +#duration { + width: 125px; +} + +#successRate, .summaryGroup { + border: solid 2px #d0d0d0; + -moz-border-radius: 10px; + border-radius: 10px; +} + +#successRate { + width: 140px; + margin-left: 35px; +} + +#successRate .percent { + font-size: 180%; +} + +.success, .success a { + color: #008000; +} + +div.success, #successRate.success { + background-color: #bbd9bb; + border-color: #008000; +} + +.failures, .failures a { + color: #b60808; +} + +.skipped, .skipped a { + color: #c09853; +} + +div.failures, #successRate.failures { + background-color: #ecdada; + border-color: #b60808; +} + +ul.linkList { + padding-left: 0; +} + +ul.linkList li { + list-style: none; + margin-bottom: 5px; +} diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/index.html b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/index.html new file mode 100644 index 0000000..b9e5f27 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/index.html @@ -0,0 +1,92 @@ + + + + + +Test results - Test Summary + + + + + +
+

Test Summary

+
+ + + + + +
+
+ + + + + + + +
+
+
0
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
-
+

duration

+
+
+
+
+
+
-
+

successful

+
+
+
+
+ +
+

Classes

+ + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+
+
+ +
+ + diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/js/report.js b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/js/report.js new file mode 100644 index 0000000..83bab4a --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/js/report.js @@ -0,0 +1,194 @@ +(function (window, document) { + "use strict"; + + var tabs = {}; + + function changeElementClass(element, classValue) { + if (element.getAttribute("className")) { + element.setAttribute("className", classValue); + } else { + element.setAttribute("class", classValue); + } + } + + function getClassAttribute(element) { + if (element.getAttribute("className")) { + return element.getAttribute("className"); + } else { + return element.getAttribute("class"); + } + } + + function addClass(element, classValue) { + changeElementClass(element, getClassAttribute(element) + " " + classValue); + } + + function removeClass(element, classValue) { + changeElementClass(element, getClassAttribute(element).replace(classValue, "")); + } + + function initTabs() { + var container = document.getElementById("tabs"); + + tabs.tabs = findTabs(container); + tabs.titles = findTitles(tabs.tabs); + tabs.headers = findHeaders(container); + tabs.select = select; + tabs.deselectAll = deselectAll; + tabs.select(0); + + return true; + } + + function getCheckBox() { + return document.getElementById("line-wrapping-toggle"); + } + + function getLabelForCheckBox() { + return document.getElementById("label-for-line-wrapping-toggle"); + } + + function findCodeBlocks() { + var spans = document.getElementById("tabs").getElementsByTagName("span"); + var codeBlocks = []; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].className.indexOf("code") >= 0) { + codeBlocks.push(spans[i]); + } + } + return codeBlocks; + } + + function forAllCodeBlocks(operation) { + var codeBlocks = findCodeBlocks(); + + for (var i = 0; i < codeBlocks.length; ++i) { + operation(codeBlocks[i], "wrapped"); + } + } + + function toggleLineWrapping() { + var checkBox = getCheckBox(); + + if (checkBox.checked) { + forAllCodeBlocks(addClass); + } else { + forAllCodeBlocks(removeClass); + } + } + + function initControls() { + if (findCodeBlocks().length > 0) { + var checkBox = getCheckBox(); + var label = getLabelForCheckBox(); + + checkBox.onclick = toggleLineWrapping; + checkBox.checked = false; + + removeClass(label, "hidden"); + } + } + + function switchTab() { + var id = this.id.substr(1); + + for (var i = 0; i < tabs.tabs.length; i++) { + if (tabs.tabs[i].id === id) { + tabs.select(i); + break; + } + } + + return false; + } + + function select(i) { + this.deselectAll(); + + changeElementClass(this.tabs[i], "tab selected"); + changeElementClass(this.headers[i], "selected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var h2 = document.createElement("H2"); + + h2.appendChild(document.createTextNode(this.titles[i])); + this.headers[i].appendChild(h2); + } + + function deselectAll() { + for (var i = 0; i < this.tabs.length; i++) { + changeElementClass(this.tabs[i], "tab deselected"); + changeElementClass(this.headers[i], "deselected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var a = document.createElement("A"); + + a.setAttribute("id", "ltab" + i); + a.setAttribute("href", "#tab" + i); + a.onclick = switchTab; + a.appendChild(document.createTextNode(this.titles[i])); + + this.headers[i].appendChild(a); + } + } + + function findTabs(container) { + return findChildElements(container, "DIV", "tab"); + } + + function findHeaders(container) { + var owner = findChildElements(container, "UL", "tabLinks"); + return findChildElements(owner[0], "LI", null); + } + + function findTitles(tabs) { + var titles = []; + + for (var i = 0; i < tabs.length; i++) { + var tab = tabs[i]; + var header = findChildElements(tab, "H2", null)[0]; + + header.parentNode.removeChild(header); + + if (header.innerText) { + titles.push(header.innerText); + } else { + titles.push(header.textContent); + } + } + + return titles; + } + + function findChildElements(container, name, targetClass) { + var elements = []; + var children = container.childNodes; + + for (var i = 0; i < children.length; i++) { + var child = children.item(i); + + if (child.nodeType === 1 && child.nodeName === name) { + if (targetClass && child.className.indexOf(targetClass) < 0) { + continue; + } + + elements.push(child); + } + } + + return elements; + } + + // Entry point. + + window.onload = function() { + initTabs(); + initControls(); + }; +} (window, window.document)); \ No newline at end of file diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/resources/test/META-INF/spring.factories b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/resources/test/META-INF/spring.factories new file mode 100644 index 0000000..6e51754 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/resources/test/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Auto Configuration +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +contracts.MessagingAutoConfig \ No newline at end of file diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/resources/test/application-messagingtype.yml b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/resources/test/application-messagingtype.yml new file mode 100644 index 0000000..462cdc5 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/resources/test/application-messagingtype.yml @@ -0,0 +1 @@ +stubrunner.camel.enabled: false \ No newline at end of file diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/stubs/META-INF/com.example/smarttesting/0.0.1.RELEASE/contracts/should_return_fraud.yml b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/stubs/META-INF/com.example/smarttesting/0.0.1.RELEASE/contracts/should_return_fraud.yml new file mode 100644 index 0000000..a9a8afa --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/stubs/META-INF/com.example/smarttesting/0.0.1.RELEASE/contracts/should_return_fraud.yml @@ -0,0 +1,15 @@ +request: + method: POST + url: /fraudCheck + headers: + Content-Type: application/json + body: + uuid: 89c878e3-38f7-4831-af6c-c3b4a0669022 + person: + name: Stefania + surname: Stefanowska + date_of_birth: "2020-01-01" + gender: FEMALE + national_id_number: "1234567890" +response: + status: 401 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/stubs/META-INF/com.example/smarttesting/0.0.1.RELEASE/contracts/should_return_non_fraud.yml b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/stubs/META-INF/com.example/smarttesting/0.0.1.RELEASE/contracts/should_return_non_fraud.yml new file mode 100644 index 0000000..d2b1417 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/stubs/META-INF/com.example/smarttesting/0.0.1.RELEASE/contracts/should_return_non_fraud.yml @@ -0,0 +1,15 @@ +request: + method: POST + url: /fraudCheck + headers: + Content-Type: application/json + body: + uuid: 6cb4521f-49da-48e5-9ea2-4a1d3899581d + person: + name: Jacek + surname: Dubilas + date_of_birth: "1980-03-08" + gender: MALE + national_id_number: "80030818293" +response: + status: 200 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/stubs/META-INF/com.example/smarttesting/0.0.1.RELEASE/mappings/should_return_fraud.json b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/stubs/META-INF/com.example/smarttesting/0.0.1.RELEASE/mappings/should_return_fraud.json new file mode 100644 index 0000000..643e95f --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/stubs/META-INF/com.example/smarttesting/0.0.1.RELEASE/mappings/should_return_fraud.json @@ -0,0 +1,30 @@ +{ + "id" : "de9432be-1600-4b66-8c33-935041a3a7fd", + "request" : { + "urlPath" : "/fraudCheck", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "matchesJsonPath" : "$[?(@.['uuid'] == '89c878e3-38f7-4831-af6c-c3b4a0669022')]" + }, { + "matchesJsonPath" : "$.['person'][?(@.['name'] == 'Stefania')]" + }, { + "matchesJsonPath" : "$.['person'][?(@.['surname'] == 'Stefanowska')]" + }, { + "matchesJsonPath" : "$.['person'][?(@.['date_of_birth'] == '2020-01-01')]" + }, { + "matchesJsonPath" : "$.['person'][?(@.['gender'] == 'FEMALE')]" + }, { + "matchesJsonPath" : "$.['person'][?(@.['national_id_number'] == '1234567890')]" + } ] + }, + "response" : { + "status" : 401, + "transformers" : [ "response-template", "spring-cloud-contract" ] + }, + "uuid" : "de9432be-1600-4b66-8c33-935041a3a7fd" +} diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/stubs/META-INF/com.example/smarttesting/0.0.1.RELEASE/mappings/should_return_non_fraud.json b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/stubs/META-INF/com.example/smarttesting/0.0.1.RELEASE/mappings/should_return_non_fraud.json new file mode 100644 index 0000000..f0edec7 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/stubs/META-INF/com.example/smarttesting/0.0.1.RELEASE/mappings/should_return_non_fraud.json @@ -0,0 +1,30 @@ +{ + "id" : "fa1098e0-1ac6-464d-b5a2-df5b8ee6e336", + "request" : { + "urlPath" : "/fraudCheck", + "method" : "POST", + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + }, + "bodyPatterns" : [ { + "matchesJsonPath" : "$[?(@.['uuid'] == '6cb4521f-49da-48e5-9ea2-4a1d3899581d')]" + }, { + "matchesJsonPath" : "$.['person'][?(@.['name'] == 'Jacek')]" + }, { + "matchesJsonPath" : "$.['person'][?(@.['surname'] == 'Dubilas')]" + }, { + "matchesJsonPath" : "$.['person'][?(@.['date_of_birth'] == '1980-03-08')]" + }, { + "matchesJsonPath" : "$.['person'][?(@.['gender'] == 'MALE')]" + }, { + "matchesJsonPath" : "$.['person'][?(@.['national_id_number'] == '80030818293')]" + } ] + }, + "response" : { + "status" : 200, + "transformers" : [ "response-template", "spring-cloud-contract" ] + }, + "uuid" : "fa1098e0-1ac6-464d-b5a2-df5b8ee6e336" +} diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/TEST-contracts.ContractVerifierTest.xml b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/TEST-contracts.ContractVerifierTest.xml new file mode 100644 index 0000000..6befbec --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/TEST-contracts.ContractVerifierTest.xml @@ -0,0 +1,250 @@ + + + + + + false]]]. +18:29:43.920 [Test worker] DEBUG org.springframework.test.context.support.TestPropertySourceUtils - Adding inlined properties to environment: {spring.jmx.enabled=false, org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true} + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v2.4.6) + +2023-09-15 18:29:44.212 INFO 122 --- [ Test worker] contracts.ContractVerifierTest : Starting ContractVerifierTest using Java 1.8.0_275 on fad7d23085aa with PID 122 (started by root in /spring-cloud-contract) +2023-09-15 18:29:44.222 INFO 122 --- [ Test worker] contracts.ContractVerifierTest : No active profile set, falling back to default profiles: default +2023-09-15 18:29:45.103 INFO 122 --- [ Test worker] trationDelegate$BeanPostProcessorChecker : Bean 'org.apache.camel.spring.boot.CamelAutoConfiguration' of type [org.apache.camel.spring.boot.CamelAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) +2023-09-15 18:29:45.325 INFO 122 --- [ Test worker] o.apache.camel.support.LRUCacheFactory : Detected and using LRUCacheFactory: camel-caffeine-lrucache +2023-09-15 18:29:46.912 INFO 122 --- [ Test worker] o.s.c.c.v.m.i.ContractVerifierMessaging : The message verifier implementation is of type [class org.springframework.cloud.contract.verifier.messaging.camel.CamelStubMessages] +2023-09-15 18:29:46.946 INFO 122 --- [ Test worker] o.a.c.s.boot.SpringBootRoutesCollector : Loading additional Camel XML routes from: classpath:camel/*.xml +2023-09-15 18:29:46.947 INFO 122 --- [ Test worker] o.a.c.s.boot.SpringBootRoutesCollector : Loading additional Camel XML rests from: classpath:camel-rest/*.xml +2023-09-15 18:29:46.976 INFO 122 --- [ Test worker] o.a.c.impl.engine.AbstractCamelContext : Apache Camel 3.4.3 (camel-1) is starting +2023-09-15 18:29:46.982 INFO 122 --- [ Test worker] o.a.c.impl.engine.AbstractCamelContext : StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html +2023-09-15 18:29:46.982 INFO 122 --- [ Test worker] o.a.c.impl.engine.AbstractCamelContext : Using HealthCheck: camel-health +2023-09-15 18:29:46.983 INFO 122 --- [ Test worker] o.a.c.impl.engine.AbstractCamelContext : Total 0 routes, of which 0 are started +2023-09-15 18:29:46.983 INFO 122 --- [ Test worker] o.a.c.impl.engine.AbstractCamelContext : Apache Camel 3.4.3 (camel-1) started in 0.007 seconds +2023-09-15 18:29:46.986 INFO 122 --- [ Test worker] contracts.ContractVerifierTest : Started ContractVerifierTest in 3.065 seconds (JVM running for 3.899) +2023-09-15 18:29:47.180 WARN 122 --- [ Test worker] contracts.ContractTestsBase : An exception occurred while trying to setup messaging from contract + +java.lang.IllegalStateException: java.io.FileNotFoundException: should_return_fraud.yml + at org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.fileToBytes(ContractVerifierUtil.java:83) ~[spring-cloud-contract-verifier-3.0.3.jar:3.0.3] + at org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.contract(ContractVerifierUtil.java:136) ~[spring-cloud-contract-verifier-3.0.3.jar:3.0.3] + at contracts.ContractTestsBase.setupMessagingFromContract(ContractTestsBase.java:104) [test/:na] + at contracts.ContractTestsBase.setup(ContractTestsBase.java:99) [test/:na] + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_275] + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_275] + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_275] + at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_275] + at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688) [junit-platform-commons-1.7.2.jar:1.7.2] + at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) [junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) [junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149) [junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.extension.TimeoutExtension.interceptLifecycleMethod(TimeoutExtension.java:126) [junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.extension.TimeoutExtension.interceptBeforeEachMethod(TimeoutExtension.java:76) [junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeMethodInExtensionContext(ClassBasedTestDescriptor.java:490) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$synthesizeBeforeEachMethodAdapter$19(ClassBasedTestDescriptor.java:475) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeEachMethods$2(TestMethodTestDescriptor.java:167) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeMethodsOrCallbacksUntilExceptionOccurs$5(TestMethodTestDescriptor.java:195) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(TestMethodTestDescriptor.java:195) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeEachMethods(TestMethodTestDescriptor.java:164) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:127) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at java.util.ArrayList.forEach(ArrayList.java:1259) ~[na:1.8.0_275] + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at java.util.ArrayList.forEach(ArrayList.java:1259) ~[na:1.8.0_275] + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248) ~[na:na] + at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211) ~[na:na] + at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226) ~[na:na] + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199) ~[na:na] + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132) ~[na:na] + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99) ~[na:na] + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79) ~[na:na] + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75) ~[na:na] + at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61) ~[na:na] + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_275] + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_275] + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_275] + at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_275] + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na] + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na] + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) ~[na:na] + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) ~[na:na] + at com.sun.proxy.$Proxy2.stop(Unknown Source) ~[na:na] + at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:132) ~[na:na] + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_275] + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_275] + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_275] + at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_275] + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na] + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na] + at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182) ~[na:na] + at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164) ~[na:na] + at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:413) ~[na:na] + at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) ~[na:na] + at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48) ~[na:na] + at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_275] + at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_275] + at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) ~[na:na] + at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_275] +Caused by: java.io.FileNotFoundException: should_return_fraud.yml + at org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.fileToBytes(ContractVerifierUtil.java:78) ~[spring-cloud-contract-verifier-3.0.3.jar:3.0.3] + ... 95 common frames omitted + +2023-09-15 18:29:47.824 WARN 122 --- [ Test worker] contracts.ContractTestsBase : An exception occurred while trying to setup messaging from contract + +java.lang.IllegalStateException: java.io.FileNotFoundException: should_return_non_fraud.yml + at org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.fileToBytes(ContractVerifierUtil.java:83) ~[spring-cloud-contract-verifier-3.0.3.jar:3.0.3] + at org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.contract(ContractVerifierUtil.java:136) ~[spring-cloud-contract-verifier-3.0.3.jar:3.0.3] + at contracts.ContractTestsBase.setupMessagingFromContract(ContractTestsBase.java:104) [test/:na] + at contracts.ContractTestsBase.setup(ContractTestsBase.java:99) [test/:na] + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_275] + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_275] + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_275] + at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_275] + at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688) [junit-platform-commons-1.7.2.jar:1.7.2] + at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) [junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) [junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149) [junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.extension.TimeoutExtension.interceptLifecycleMethod(TimeoutExtension.java:126) [junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.extension.TimeoutExtension.interceptBeforeEachMethod(TimeoutExtension.java:76) [junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeMethodInExtensionContext(ClassBasedTestDescriptor.java:490) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$synthesizeBeforeEachMethodAdapter$19(ClassBasedTestDescriptor.java:475) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeEachMethods$2(TestMethodTestDescriptor.java:167) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeBeforeMethodsOrCallbacksUntilExceptionOccurs$5(TestMethodTestDescriptor.java:195) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeMethodsOrCallbacksUntilExceptionOccurs(TestMethodTestDescriptor.java:195) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeBeforeEachMethods(TestMethodTestDescriptor.java:164) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:127) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65) ~[junit-jupiter-engine-5.7.2.jar:5.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at java.util.ArrayList.forEach(ArrayList.java:1259) ~[na:1.8.0_275] + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at java.util.ArrayList.forEach(ArrayList.java:1259) ~[na:1.8.0_275] + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51) ~[junit-platform-engine-1.7.2.jar:1.7.2] + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248) ~[na:na] + at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211) ~[na:na] + at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226) ~[na:na] + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199) ~[na:na] + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132) ~[na:na] + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99) ~[na:na] + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79) ~[na:na] + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75) ~[na:na] + at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61) ~[na:na] + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_275] + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_275] + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_275] + at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_275] + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na] + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na] + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) ~[na:na] + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) ~[na:na] + at com.sun.proxy.$Proxy2.stop(Unknown Source) ~[na:na] + at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:132) ~[na:na] + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_275] + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_275] + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_275] + at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_275] + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) ~[na:na] + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) ~[na:na] + at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182) ~[na:na] + at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164) ~[na:na] + at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:413) ~[na:na] + at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) ~[na:na] + at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48) ~[na:na] + at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_275] + at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_275] + at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) ~[na:na] + at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_275] +Caused by: java.io.FileNotFoundException: should_return_non_fraud.yml + at org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.fileToBytes(ContractVerifierUtil.java:78) ~[spring-cloud-contract-verifier-3.0.3.jar:3.0.3] + ... 95 common frames omitted + +]]> + + diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/binary/output.bin b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/binary/output.bin new file mode 100644 index 0000000..e3969e5 Binary files /dev/null and b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/binary/output.bin differ diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/binary/output.bin.idx b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/binary/output.bin.idx new file mode 100644 index 0000000..c9e2dad Binary files /dev/null and b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/binary/output.bin.idx differ diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/binary/results.bin b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/binary/results.bin new file mode 100644 index 0000000..ed8a58b Binary files /dev/null and b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/binary/results.bin differ diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/test/binary/output.bin b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/test/binary/output.bin new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/test/binary/output.bin.idx b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/test/binary/output.bin.idx new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/test/binary/output.bin.idx differ diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/test/binary/results.bin b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/test/binary/results.bin new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/tmp/test/jar_extract_7295776964697644061_tmp b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/tmp/test/jar_extract_7295776964697644061_tmp new file mode 100644 index 0000000..d9208c0 Binary files /dev/null and b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/tmp/test/jar_extract_7295776964697644061_tmp differ diff --git a/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/tmp/verifierStubsJar/MANIFEST.MF b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/tmp/verifierStubsJar/MANIFEST.MF new file mode 100644 index 0000000..58630c0 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/tmp/verifierStubsJar/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/05-architecture/05-03-cdc/05-03-01-producer/tests/__init__.py b/05-architecture/05-03-cdc/05-03-01-producer/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-03-cdc/05-03-01-producer/tests/architecture_tests.py b/05-architecture/05-03-cdc/05-03-01-producer/tests/architecture_tests.py new file mode 100644 index 0000000..0205f84 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-01-producer/tests/architecture_tests.py @@ -0,0 +1,31 @@ +from pathlib import Path + +import pytest +from pylint import epylint + + +class TestArchitecture: + @pytest.fixture(autouse=True) + def setup(self): + root_dir = Path(__file__).parents[1] + self.paths_to_check = [ + str(root_dir / module_name) + for module_name in ( + "smarttesting", + "smarttesting_api", + "smarttesting_main", + "tests", + ) + ] + + def test_run_pylint_checks_on_modules(self) -> None: + """Uruchomienie pylinta programatycznie. + + Po więcej szczegółów zajrzyj do 05-02-packages. + """ + pylint_arg = " ".join(self.paths_to_check) + std_out, _std_err = epylint.py_run(pylint_arg, return_std=True) + + std_out.seek(0) + output = std_out.read() + assert "Your code has been rated at 10.00/10" in output, output diff --git a/05-architecture/05-03-cdc/05-03-02-consumer/consumer/__init__.py b/05-architecture/05-03-cdc/05-03-02-consumer/consumer/__init__.py new file mode 100644 index 0000000..a6574fa --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-02-consumer/consumer/__init__.py @@ -0,0 +1,49 @@ +import json +from datetime import date +from http import HTTPStatus +from typing import Any, TypedDict +from uuid import UUID + +import requests + +FRAUD_CHECK_URL = "http://localhost:5051/fraudCheck" + + +class PersonPayload(TypedDict): + name: str + surname: str + national_id_number: str + gender: str + date_of_birth: date + + +class CustomerCheckPayload(TypedDict): + uuid: UUID + person: PersonPayload + + +def check_customer(payload: CustomerCheckPayload) -> bool: + response = requests.post( + FRAUD_CHECK_URL, + data=json.dumps(payload, cls=CustomJSONEncoder), + headers={"Content-Type": "application/json"}, + ) + + if response.status_code == HTTPStatus.OK: + return True + elif response.status_code == HTTPStatus.UNAUTHORIZED: + return False + else: + raise ValueError( + f"Unexpected response code from fraud service - {response.status_code}" + ) + + +class CustomJSONEncoder(json.JSONEncoder): + def default(self, o: Any) -> Any: + if isinstance(o, UUID): + return str(o) + elif isinstance(o, date): + return o.strftime("%Y-%m-%d") + else: + return json.JSONEncoder.default(self, o) diff --git a/05-architecture/05-03-cdc/05-03-02-consumer/consumer/consumer_pact_tests.py b/05-architecture/05-03-cdc/05-03-02-consumer/consumer/consumer_pact_tests.py new file mode 100644 index 0000000..2a07ad4 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-02-consumer/consumer/consumer_pact_tests.py @@ -0,0 +1,93 @@ +import copy +from datetime import date +from pathlib import Path +from uuid import UUID + +import pytest +from consumer import CustomerCheckPayload, check_customer +from pact import Consumer, Pact, Provider + +CONSUMER_DIR = Path(__file__).parent + + +@pytest.fixture(scope="module") +def pact_with_fraud_verify() -> Pact: + pact = Consumer("some_consumer").has_pact_with( + Provider("FraudVerify"), port=5051, pact_dir=str(CONSUMER_DIR) + ) + pact.start_service() + yield pact + pact.stop_service() + + +class TestPactIoBased: + def test_returns_true_if_verification_successful( + self, pact_with_fraud_verify: Pact # pylint: disable=redefined-outer-name + ) -> None: + payload: CustomerCheckPayload = { + "uuid": UUID("6cb4521f-49da-48e5-9ea2-4a1d3899581d"), + "person": { + "name": "Jacek", + "surname": "Dubilas", + "gender": "MALE", + "date_of_birth": date(1980, 3, 8), + "national_id_number": "80030818293", + }, + } + + pact_payload = _get_json_friendly_payload(payload) + headers = {"Content-Type": "application/json"} + pact_with_fraud_verify.given( + "Fraud verification accepts trusted data" + ).upon_receiving("A fraud user sends their data").with_request( + "POST", "/fraudCheck", body=pact_payload, headers=headers + ).will_respond_with( + 200 + ) + + with pact_with_fraud_verify: + result = check_customer(payload) + + assert result is True + + def test_returns_false_if_verification_unsuccessful( + self, pact_with_fraud_verify: Pact # pylint: disable=redefined-outer-name + ) -> None: + payload: CustomerCheckPayload = { + "uuid": UUID("89c878e3-38f7-4831-af6c-c3b4a0669022"), + "person": { + "name": "Stefania", + "surname": "Stefanowska", + "gender": "FEMALE", + "date_of_birth": date(2020, 1, 1), + "national_id_number": "1234567890", + }, + } + + pact_payload = _get_json_friendly_payload(payload) + headers = {"Content-Type": "application/json"} + pact_with_fraud_verify.given( + "Fraud verification rejects suspicious data" + ).upon_receiving("An honest user sends their data").with_request( + "POST", "/fraudCheck", body=pact_payload, headers=headers + ).will_respond_with( + 401 + ) + + with pact_with_fraud_verify: + result = check_customer(payload) + + assert result is False + + +def _get_json_friendly_payload(payload: CustomerCheckPayload) -> dict: + """Niestety nie ma w Pact możliwości użycia swojego JSONEncodera. + + Przekonwertujemy typy na lubiane przez json.dumps ręcznie. + """ + pact_payload: dict = copy.deepcopy(payload) # type: ignore + pact_payload["uuid"] = str(pact_payload["uuid"]) + pact_payload["person"]["date_of_birth"] = pact_payload["person"][ + "date_of_birth" + ].strftime("%Y-%m-%d") + return pact_payload diff --git a/05-architecture/05-03-cdc/05-03-02-consumer/consumer/consumer_spring_cloud_contract_tests.py b/05-architecture/05-03-cdc/05-03-02-consumer/consumer/consumer_spring_cloud_contract_tests.py new file mode 100644 index 0000000..5e27746 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-02-consumer/consumer/consumer_spring_cloud_contract_tests.py @@ -0,0 +1,64 @@ +import time +from datetime import date, datetime, timedelta +from urllib.parse import urlparse +from uuid import UUID + +import pytest +import requests +from consumer import FRAUD_CHECK_URL, CustomerCheckPayload, check_customer +from requests.exceptions import RequestException + + +@pytest.mark.uses_docker +class TestSpringCloudContractBased: + @pytest.fixture(scope="class", autouse=True) + def make_sure_stub_is_running(self) -> None: + ping_url = urlparse(FRAUD_CHECK_URL)._replace(path="/ping").geturl() + stop_at = datetime.now() + timedelta(seconds=30) + while stop_at > datetime.now(): + try: + response = requests.get(ping_url) + response.raise_for_status() + except RequestException: + time.sleep(0.3) + continue + else: + break + + yield + + reset_url = urlparse(FRAUD_CHECK_URL)._replace(path="/__admin/requests/reset").geturl() + response = requests.post(reset_url) + response.raise_for_status() + + def test_returns_false_if_verification_unsuccessful(self) -> None: + payload: CustomerCheckPayload = { + "uuid": UUID("89c878e3-38f7-4831-af6c-c3b4a0669022"), + "person": { + "name": "Stefania", + "surname": "Stefanowska", + "gender": "FEMALE", + "date_of_birth": date(2020, 1, 1), + "national_id_number": "1234567890", + }, + } + + result = check_customer(payload) + + assert result is False + + def test_returns_true_if_verification_successful(self) -> None: + payload: CustomerCheckPayload = { + "uuid": UUID("6cb4521f-49da-48e5-9ea2-4a1d3899581d"), + "person": { + "name": "Jacek", + "surname": "Dubilas", + "gender": "MALE", + "date_of_birth": date(1980, 3, 8), + "national_id_number": "80030818293", + }, + } + + result = check_customer(payload) + + assert result is True diff --git a/05-architecture/05-03-cdc/05-03-02-consumer/consumer/some_consumer-fraudverify.json b/05-architecture/05-03-cdc/05-03-02-consumer/consumer/some_consumer-fraudverify.json new file mode 100644 index 0000000..b55aa59 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-02-consumer/consumer/some_consumer-fraudverify.json @@ -0,0 +1,67 @@ +{ + "consumer": { + "name": "some_consumer" + }, + "provider": { + "name": "FraudVerify" + }, + "interactions": [ + { + "description": "An honest user sends their data", + "providerState": "Fraud verification rejects suspicious data", + "request": { + "method": "POST", + "path": "/fraudCheck", + "headers": { + "Content-Type": "application/json" + }, + "body": { + "uuid": "89c878e3-38f7-4831-af6c-c3b4a0669022", + "person": { + "name": "Stefania", + "surname": "Stefanowska", + "gender": "FEMALE", + "date_of_birth": "2020-01-01", + "national_id_number": "1234567890" + } + } + }, + "response": { + "status": 401, + "headers": { + } + } + }, + { + "description": "A fraud user sends their data", + "providerState": "Fraud verification accepts trusted data", + "request": { + "method": "POST", + "path": "/fraudCheck", + "headers": { + "Content-Type": "application/json" + }, + "body": { + "uuid": "6cb4521f-49da-48e5-9ea2-4a1d3899581d", + "person": { + "name": "Jacek", + "surname": "Dubilas", + "gender": "MALE", + "date_of_birth": "1980-03-08", + "national_id_number": "80030818293" + } + } + }, + "response": { + "status": 200, + "headers": { + } + } + } + ], + "metadata": { + "pactSpecification": { + "version": "2.0.0" + } + } +} \ No newline at end of file diff --git a/05-architecture/05-03-cdc/05-03-02-consumer/some_consumer-fraudverify.json b/05-architecture/05-03-cdc/05-03-02-consumer/some_consumer-fraudverify.json new file mode 100644 index 0000000..7ffad08 --- /dev/null +++ b/05-architecture/05-03-cdc/05-03-02-consumer/some_consumer-fraudverify.json @@ -0,0 +1,67 @@ +{ + "consumer": { + "name": "some_consumer" + }, + "provider": { + "name": "FraudVerify" + }, + "interactions": [ + { + "description": "A fraud user sends their data", + "providerState": "Fraud verification rejects suspicious data", + "request": { + "method": "POST", + "path": "/fraudCheck", + "headers": { + "Content-Type": "application/json" + }, + "body": { + "uuid": "6cb4521f-49da-48e5-9ea2-4a1d3899581d", + "person": { + "name": "Jacek", + "surname": "Dubilas", + "gender": "MALE", + "date_of_birth": "1980-03-08", + "national_id_number": "80030818293" + } + } + }, + "response": { + "status": 200, + "headers": { + } + } + }, + { + "description": "An honest user sends their data", + "providerState": "Fraud verification accepts trusted data", + "request": { + "method": "POST", + "path": "/fraudCheck", + "headers": { + "Content-Type": "application/json" + }, + "body": { + "uuid": "89c878e3-38f7-4831-af6c-c3b4a0669022", + "person": { + "name": "Stefania", + "surname": "Stefanowska", + "gender": "FEMALE", + "date_of_birth": "2020-01-01", + "national_id_number": "1234567890" + } + } + }, + "response": { + "status": 401, + "headers": { + } + } + } + ], + "metadata": { + "pactSpecification": { + "version": "2.0.0" + } + } +} \ No newline at end of file diff --git a/05-architecture/05-03-cdc/README.adoc b/05-architecture/05-03-cdc/README.adoc new file mode 100644 index 0000000..5e5b816 --- /dev/null +++ b/05-architecture/05-03-cdc/README.adoc @@ -0,0 +1,45 @@ += Testowanie kontraktowe + +Moduł 05-03-01 to producent wiadomości, zaś 05-03-02 jego konsument. + +Zarówno konsument, jak i producent mogą być napisani w różnych językach i technologiach. + +Pokazujemy dwa różne narzędzia i dwa różne flow contract testingu. + +== a) z użyciem Spring Cloud Contracts + +Teraz użyjemy flow od strony producenta - do istniejącego napiszemy kontrakty, zweryfikujemy je i wygenerujemy stuby, by potem napisać klienta używając tych ostatnich. + +=== Po stronie producenta +1. Piszemy kontrakt(y) (`05-architecture/05-03-cdc/05-03-01-producer/contracts`) +2. Uruchamiamy aplikację (patrz na samym dole) +3. Uruchamiamy Spring Cloud Contracts w dockerze (`01_generate_stubs_with_scc.sh`) +4. Cieszymy się z weryfikacji kontraktów i wygenerowanych stubów :) (`spring-cloud-contract-output/libs/smarttesting-0.0.1.RELEASE-stubs.jar`) + +UWAGA: W tym przykładzie nie wykorzystujemy żadnego repozytorium na stuby jak Artifactory. Więcej informacji w dokumentacji Spring Cloud Contracts: https://docs.spring.io/spring-cloud-contract/docs/current/reference/html/docker-project.html#docker-how-it-works + +=== Po stronie konsumenta + +1. Najpierw uruchamiamy stubrunnera, który stawia nam i konfiguruje Wiremocka dla sprawdzonych kontraktów (`02_run_stubrunner.sh`) +2. Potem możemy tradycyjnie uruchomić testy `pytest 05-architecture/05-03-cdc/05-03-02-consumer` + +== b) z użyciem Pact + +Teraz użyjemy flow od strony klienta - tak zwane Consumer-Driven Contract Testing (no prawie, bo usługa weryfikacji już istnieje 😀) + +=== Po stronie konsumenta + +1. Uruchamiamy testy `pytest 05-architecture/05-03-cdc/05-03-02-consumer/consumer/consumer_pact_tests.py` +2. Testy wygenerowały nam plik .json `some_consumer_fraudverify.json` (jest już w repo) + +=== Po stronie producenta + +1. Uruchamiamy aplikację producenta (patrz niżej) +A. Albo weryfikujemy serwer używajac wbudowanego narzędzia CLI `pact-verifier --provider-base-url="http://localhost:5050/" --pact-url=05-architecture/05-03-cdc/05-03-02-consumer/consumer/some_consumer-fraudverify.json` +B. Albo piszemy test używajac Python API, np. opakowaując to w pytest https://github.com/pact-foundation/pact-python#python-api + +== Uruchamianie aplikacji: +```bash +cd 05-architecture/05-03-cdc/05-03-01-producer/ +APP_ENV=DEV FLASK_APP=smarttesting_api.web_app:app flask run --port 5050 +``` diff --git a/05-architecture/05-04-chaos/README.adoc b/05-architecture/05-04-chaos/README.adoc new file mode 100644 index 0000000..7fae976 --- /dev/null +++ b/05-architecture/05-04-chaos/README.adoc @@ -0,0 +1,45 @@ += Testy chaosu + +== Kod + +Jedyny plik z testami - `chaos_test.py` - gdzie uruchamiamy eksperymenty inżynierii chaosu. + +== Uruchomienie + +Możemy od razu uruchomić testy bez podnoszenia całej aplikacji. + +Wstrzyknęliśmy opóźnienie i wyjątek używając monkey-patchingu. + +Oba testy się wywalą. + +* `test_returns_401_within_500_ms_when_calling_fraud_check_with_introduced_latency` - opóźnienie wynosi od 1 do 3 sekund więc po odczekaniu tego czasu zobaczymy błąd. +* `test_returns_401_within_500ms_when_calling_fraud_check_with_db_issues` - dostaniemy `INTERNAL_SERVER_ERROR` i status `500`, ponieważ poleci nam wyjątek z kodu bazodanowego, którego nie obsługujemy. + +Następnie należy zakomentować kod w `CustomerVerifier` odpowiedzialny za połączenie z bazą danych i odkomentowanie tego, który dodaje obsługę błędów. Po uruchomieniu ponownym testów, jeden test przejdzie, a drugi, z oczywistych względów się wywali. + +``` +diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verifier.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verifier.py +index fb5d218..0dd3f7b 100644 +--- a/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verifier.py ++++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verifier.py +@@ -39,7 +39,8 @@ class CustomerVerifier: + przypadku zapisuje wynik weryfikacji w bazie danych. Weryfikacja wówczas + zachodzi poprzez odpytanie BIKu o stan naszego klienta. + """ +- prior_result = self._repository.find_by_user_id(customer.uuid) # zakomentuj ++ # prior_result = self._repository.find_by_user_id(customer.uuid) # zakomentuj +- # prior_result = None # odkomentuj ++ prior_result = None # odkomentuj + if prior_result: + return CustomerVerificationResult( + prior_result.uuid, Status(prior_result.status) +@@ -49,7 +50,7 @@ class CustomerVerifier: + + def _verify_customer(self, customer: Customer) -> CustomerVerificationResult: + result = self._perform_checks(customer) +- self._save_verification_result(customer, result) # zakomentuj ++ # self._save_verification_result(customer, result) # zakomentuj + if not result.passed: + customer_verification = CustomerVerification(customer.person, result) + self._fraud_alert_task.delay(customer_verification=customer_verification) +``` diff --git a/05-architecture/05-04-chaos/smarttesting/__init__.py b/05-architecture/05-04-chaos/smarttesting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-04-chaos/smarttesting/customer/__init__.py b/05-architecture/05-04-chaos/smarttesting/customer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-04-chaos/smarttesting/customer/customer.py b/05-architecture/05-04-chaos/smarttesting/customer/customer.py new file mode 100644 index 0000000..e3abc18 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/customer/customer.py @@ -0,0 +1,28 @@ +from dataclasses import dataclass +from uuid import UUID + +from smarttesting.customer.person import Person + + +@dataclass +class Customer: + """Klient. Klasa opakowująca osobę do zweryfikowania.""" + + _uuid: UUID + _person: Person + + @property + def uuid(self) -> UUID: + return self._uuid + + @property + def person(self) -> Person: + return self._person + + @property + def is_student(self) -> bool: + return self._person.is_student + + @property + def student(self): + return self._person.student diff --git a/05-architecture/05-04-chaos/smarttesting/customer/person.py b/05-architecture/05-04-chaos/smarttesting/customer/person.py new file mode 100644 index 0000000..7825d55 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/customer/person.py @@ -0,0 +1,64 @@ +import enum +from dataclasses import dataclass +from datetime import date + + +class Gender(enum.Enum): + MALE = enum.auto() + FEMALE = enum.auto() + + +class Status(enum.Enum): + STUDENT = enum.auto() + NOT_STUDENT = enum.auto() + + +@dataclass +class Person: + """Reprezentuje osobę do zweryfikowania.""" + + _name: str + _surname: str + _date_of_birth: date + _gender: Gender + _national_id_number: str + _status: Status = Status.NOT_STUDENT + + @property + def name(self) -> str: + return self._name + + @property + def surname(self) -> str: + return self._surname + + @property + def date_of_birth(self) -> date: + return self._date_of_birth + + @property + def gender(self) -> Gender: + return self._gender + + @property + def national_id_number(self) -> str: + return self._national_id_number + + @property + def is_student(self) -> bool: + return self._status == Status.STUDENT + + def student(self) -> None: + self._status = Status.STUDENT + + @property + def age(self): + today = date.today() + years_diff = today.year - self._date_of_birth.year + had_birthday_this_year = ( + today.replace(year=self._date_of_birth.year) < self._date_of_birth + ) + if had_birthday_this_year: + years_diff -= 1 + + return years_diff diff --git a/05-architecture/05-04-chaos/smarttesting/message.py b/05-architecture/05-04-chaos/smarttesting/message.py new file mode 100644 index 0000000..1a51c39 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/message.py @@ -0,0 +1,17 @@ +from typing import ClassVar, Dict, Type + + +class Message: + """Klasa bazowa dla wszystkich wiadomości. + + Potrzebna jest nam 'jedynie' do implementacji własnego kodeka do tasków Celery + by można było przekazywać instancje dataclass jako argumenty wywołania tasków.""" + + __messages_by_name: ClassVar[Dict[str, Type]] = {} + + def __init_subclass__(cls) -> None: + cls.__messages_by_name[cls.__name__] = cls + + @classmethod + def subclass_for_name(cls, name: str) -> Type: + return cls.__messages_by_name[name] diff --git a/05-architecture/05-04-chaos/smarttesting/serialization.py b/05-architecture/05-04-chaos/smarttesting/serialization.py new file mode 100644 index 0000000..c69545c --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/serialization.py @@ -0,0 +1,53 @@ +import functools +import json +from typing import Any, Hashable, Type, cast + +from marshmallow import Schema +from marshmallow_dataclass import class_schema +from smarttesting.message import Message + +__all__ = [ + "dataclass_dump", + "dataclass_load", +] + + +def dataclass_dump(data: Any) -> str: + return json.dumps(data, cls=Encoder) + + +def dataclass_load(data: Any) -> Any: + return json.loads(data, object_hook=decoder) + + +class Encoder(json.JSONEncoder): + def default(self, o: Any) -> Any: + try: + schema = _get_schema_for_dataclass(cast(Hashable, type(o))) + except TypeError: + return json.JSONEncoder.default(self, o) + else: + dict_repr = schema.dump(o) + dict_repr["__dataclass_name__"] = type(o).__name__ + return dict_repr + + +def decoder(obj: Any) -> Any: + if "__dataclass_name__" in obj: + dataclass_name = obj.pop("__dataclass_name__") + dataclass = Message.subclass_for_name(dataclass_name) + schema = _get_schema_for_dataclass(cast(Hashable, dataclass)) + return schema.load(obj) + else: + return obj + + +@functools.lru_cache(maxsize=None) +def _get_schema_for_dataclass(dataclass_obj: Type) -> Schema: + """ + Funkcja budująca instancję schemę dla podanej klasy udekorowanej @dataclass. + + Schema jest bezstanowa, więc bezpieczne jest reużywanie obiektów. + """ + schema_cls = class_schema(dataclass_obj) + return schema_cls() diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/__init__.py b/05-architecture/05-04-chaos/smarttesting/verifier/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/__init__.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/bik_verification_service.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/bik_verification_service.py new file mode 100644 index 0000000..f38d835 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/bik_verification_service.py @@ -0,0 +1,35 @@ +import logging +from dataclasses import dataclass + +import requests +from requests.exceptions import RequestException +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, + Status, +) + +logger = logging.getLogger(__name__) + + +@dataclass +class BIKVerificationService: + """Klient do komunikacji z Biurem Informacji Kredytowej.""" + + _bik_service_url: str + + def verify(self, customer: Customer) -> CustomerVerificationResult: + """ + Weryfikuje czy dana osoba jest oszustem poprzez wysłanie zapytania po HTTP + do BIK. Do wykonania zapytania po HTTP wykorzystujemy bibliotekę `requests`. + """ + try: + id_number = customer.person.national_id_number + response = requests.get(self._bik_service_url + id_number) + + if response.text == Status.VERIFICATION_PASSED.name: + return CustomerVerificationResult.create_passed(customer.uuid) + except RequestException: + logger.exception("HTTP request failed") + + return CustomerVerificationResult.create_failed(customer.uuid) diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verification.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verification.py new file mode 100644 index 0000000..af6ed67 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verification.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass + +from smarttesting.customer.person import Person +from smarttesting.message import Message +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, +) + + +@dataclass(frozen=True) +class CustomerVerification(Message): + """Klasa wiadomości, którą wysyłamy poprzez brokera. + + Reprezentuje osobę i rezultat weryfikacji. + """ + + person: Person + result: CustomerVerificationResult diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verification_result.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verification_result.py new file mode 100644 index 0000000..3f3006f --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verification_result.py @@ -0,0 +1,36 @@ +import enum +from dataclasses import dataclass +from uuid import UUID + + +class Status(enum.Enum): + VERIFICATION_PASSED = "VERIFICATION_PASSED" + VERIFICATION_FAILED = "VERIFICATION_FAILED" + + +@dataclass(frozen=True) +class CustomerVerificationResult: + """Rezultat weryfikacji klienta.""" + + _user_id: UUID + _status: Status + + @property + def user_id(self) -> UUID: + return self._user_id + + @property + def status(self) -> Status: + return self._status + + @property + def passed(self) -> bool: + return self._status == Status.VERIFICATION_PASSED + + @staticmethod + def create_passed(user_id: UUID) -> "CustomerVerificationResult": + return CustomerVerificationResult(user_id, Status.VERIFICATION_PASSED) + + @staticmethod + def create_failed(user_id: UUID) -> "CustomerVerificationResult": + return CustomerVerificationResult(user_id, Status.VERIFICATION_FAILED) diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verifier.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verifier.py new file mode 100644 index 0000000..682dbc7 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verifier.py @@ -0,0 +1,81 @@ +from dataclasses import dataclass +from typing import Set + +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting.verifier.customer.customer_verification import CustomerVerification +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, + Status, +) +from smarttesting.verifier.customer.fraud_alert_task import FraudAlertTask +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto +from smarttesting.verifier.verification import Verification + + +@dataclass +class CustomerVerifier: + """Weryfikacja czy klient jest oszustem czy nie. + + Przechodzi po różnych implementacjach weryfikacji i jeśli, przy którejś okaże się, + że użytkownik jest oszustem, wówczas wysyłamy wiadomość do brokera, z informacją + o oszuście. + """ + + _bik_verification_service: BIKVerificationService + _verifications: Set[Verification] + _repository: VerificationRepository + _fraud_alert_task: FraudAlertTask + + def verify(self, customer: Customer) -> CustomerVerificationResult: + """ + Główna metoda biznesowa. Sprawdza, czy już nie doszło do weryfikacji klienta + i jeśli rezultat zostanie odnaleziony w bazie danych to go zwraca. W innym + przypadku zapisuje wynik weryfikacji w bazie danych. Weryfikacja wówczas + zachodzi poprzez odpytanie BIKu o stan naszego klienta. + """ + prior_result = self._repository.find_by_user_id(customer.uuid) # zakomentuj + # prior_result = None # odkomentuj + if prior_result: + return CustomerVerificationResult( + prior_result.uuid, Status(prior_result.status) + ) + else: + return self._verify_customer(customer) + + def _verify_customer(self, customer: Customer) -> CustomerVerificationResult: + result = self._perform_checks(customer) + self._save_verification_result(customer, result) # zakomentuj + if not result.passed: + customer_verification = CustomerVerification(customer.person, result) + self._fraud_alert_task.delay(customer_verification=customer_verification) + return result + + def _perform_checks(self, customer: Customer) -> CustomerVerificationResult: + external_result = self._bik_verification_service.verify(customer) + + person = customer.person + verifications_passed = all( + verification.passes(person) for verification in self._verifications + ) + + if external_result.passed and verifications_passed: + return CustomerVerificationResult.create_passed(customer.uuid) + else: + return CustomerVerificationResult.create_failed(customer.uuid) + + def _save_verification_result( + self, customer: Customer, result: CustomerVerificationResult + ) -> None: + self._repository.save( + VerifiedPersonDto( + uuid=customer.uuid, + national_identification_number=customer.person.national_id_number, + status=result.status, + ) + ) diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/fraud_alert_task.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/fraud_alert_task.py new file mode 100644 index 0000000..010ab5c --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/fraud_alert_task.py @@ -0,0 +1,17 @@ +from typing import Protocol + +from smarttesting.verifier.customer.customer_verification import CustomerVerification + + +class TaskResult(Protocol): + """Prosty protokół opokowujący AsyncResult z Celery.""" + + def get(self) -> None: + ... + + +class FraudAlertTask(Protocol): + """Prosty protokół opakowaujący taska celery z danym argumentem.""" + + def delay(self, *, customer_verification: CustomerVerification) -> TaskResult: + ... diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/fraud_detected_handler.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/fraud_detected_handler.py new file mode 100644 index 0000000..91396f3 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/fraud_detected_handler.py @@ -0,0 +1,26 @@ +import logging + +from injector import Inject +from smarttesting.verifier.customer.customer_verification import CustomerVerification +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto + +logger = logging.getLogger(__name__) + + +def fraud_detected_handler( + repo: Inject[VerificationRepository], *, customer_verification: CustomerVerification +) -> None: + """Implementacja zadania przechodzącego przez brokera RabbitMQ.""" + logger.info("Got customer verification: %s", customer_verification) + person = customer_verification.person + result = customer_verification.result + repo.save( + VerifiedPersonDto( + uuid=result.user_id, + national_identification_number=person.national_id_number, + status=result.status, + ) + ) diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/module.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/module.py new file mode 100644 index 0000000..4005ee3 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/module.py @@ -0,0 +1,34 @@ +from typing import Set + +import injector +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting.verifier.customer.customer_verifier import ( + CustomerVerifier, + FraudAlertTask, +) +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.verification import Verification + + +class CustomerModule(injector.Module): + """Moduł injectora dla modułu klienta.""" + + @injector.provider + def bik_verification_service(self) -> BIKVerificationService: + return BIKVerificationService("http://localhost") + + @injector.provider + def customer_verifier( + self, + bik_verification_service: BIKVerificationService, + verifications: Set[Verification], + repo: VerificationRepository, + fraud_alert_task: FraudAlertTask, + ) -> CustomerVerifier: + return CustomerVerifier( + bik_verification_service, verifications, repo, fraud_alert_task + ) diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/__init__.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/age.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/age.py new file mode 100644 index 0000000..2bebb13 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/age.py @@ -0,0 +1,11 @@ +from smarttesting.customer.person import Person +from smarttesting.verifier.verification import Verification + + +class AgeVerification(Verification): + """Weryfikacja wieku osoby wnioskującej o udzielenie pożyczki.""" + + def passes(self, person: Person) -> bool: + if person.age < 0: + raise ValueError("Age cannot be negative!") + return 18 <= person.age <= 99 diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/identification_number.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/identification_number.py new file mode 100644 index 0000000..194cdc8 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/identification_number.py @@ -0,0 +1,45 @@ +from smarttesting.customer.person import Gender, Person +from smarttesting.verifier.verification import Verification + + +class IdentificationNumberVerification(Verification): + """Weryfikacja poprawności numeru PESEL. + + Zobacz: https://pl.wikipedia.org/wiki/PESEL#Cyfra_kontrolna_i_sprawdzanie_poprawno.C5.9Bci_numeru + """ + + def passes(self, person: Person) -> bool: + return ( + self._gender_matches_id_number(person) + and self._starts_with_date_of_birth(person) + and self._weight_is_correct(person) + ) + + def _gender_matches_id_number(self, person: Person) -> bool: + tenth_character = person.national_id_number[9:10] + if int(tenth_character) % 2 == 0: + return person.gender == Gender.FEMALE + else: + return person.gender == Gender.MALE + + def _starts_with_date_of_birth(self, person: Person) -> bool: + dob_formatted = person.date_of_birth.strftime("%y%m%d") + if dob_formatted[0] == "0": + month = person.date_of_birth.month + 20 + dob_formatted = dob_formatted[:2] + str(month) + dob_formatted[4:] + + return dob_formatted == person.national_id_number[:6] + + def _weight_is_correct(self, person: Person) -> bool: + if len(person.national_id_number) != 11: + return False + + weights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3] + weight_sum = sum( + int(person.national_id_number[index]) * weights[index] + for index in range(10) + ) + actual_sum = (10 - weight_sum % 10) % 10 + + check_sum = int(person.national_id_number[10]) + return actual_sum == check_sum diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/module.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/module.py new file mode 100644 index 0000000..1775bcf --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/module.py @@ -0,0 +1,24 @@ +from typing import Set + +import injector +from smarttesting.verifier.customer.verification.age import AgeVerification +from smarttesting.verifier.customer.verification.identification_number import ( + IdentificationNumberVerification, +) +from smarttesting.verifier.verification import Verification + + +class VerificationModule(injector.Module): + @injector.provider + def age(self) -> AgeVerification: + return AgeVerification() + + @injector.provider + def id_number(self) -> IdentificationNumberVerification: + return IdentificationNumberVerification() + + @injector.provider + def verifications( + self, age: AgeVerification, id_number: IdentificationNumberVerification + ) -> Set[Verification]: + return {age, id_number} diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification_repository.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification_repository.py new file mode 100644 index 0000000..17060de --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/verification_repository.py @@ -0,0 +1,15 @@ +import abc +from typing import Optional +from uuid import UUID + +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto + + +class VerificationRepository(abc.ABC): + @abc.abstractmethod + def find_by_user_id(self, user_id: UUID) -> Optional[VerifiedPersonDto]: + pass + + @abc.abstractmethod + def save(self, verified_person: VerifiedPersonDto) -> None: + pass diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/verified_person.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/verified_person.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/customer/verified_person_dto.py b/05-architecture/05-04-chaos/smarttesting/verifier/customer/verified_person_dto.py new file mode 100644 index 0000000..936d466 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/customer/verified_person_dto.py @@ -0,0 +1,11 @@ +from dataclasses import dataclass +from uuid import UUID + +from smarttesting.verifier.customer.customer_verification_result import Status + + +@dataclass +class VerifiedPersonDto: + uuid: UUID + national_identification_number: str + status: Status diff --git a/05-architecture/05-04-chaos/smarttesting/verifier/verification.py b/05-architecture/05-04-chaos/smarttesting/verifier/verification.py new file mode 100644 index 0000000..fb44e92 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting/verifier/verification.py @@ -0,0 +1,9 @@ +import abc + +from smarttesting.customer.person import Person + + +class Verification(abc.ABC): + @abc.abstractmethod + def passes(self, person: Person) -> bool: + pass diff --git a/05-architecture/05-04-chaos/smarttesting_api/__init__.py b/05-architecture/05-04-chaos/smarttesting_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-04-chaos/smarttesting_api/web_app.py b/05-architecture/05-04-chaos/smarttesting_api/web_app.py new file mode 100644 index 0000000..44c4355 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting_api/web_app.py @@ -0,0 +1,63 @@ +"""Prosta, demonstracyjna aplikacja flaskowa.""" +import os +from http import HTTPStatus + +import marshmallow +import marshmallow_dataclass +from flask import Flask, Response, jsonify, request +from flask_expects_json import expects_json +from flask_injector import FlaskInjector +from marshmallow import ValidationError +from marshmallow.fields import Field +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.customer_verifier import CustomerVerifier +from smarttesting_main.smart_testing_application import assemble +from sqlalchemy.orm import Session + +DEV_MODE = False +if os.environ.get("APP_ENV") == "DEV": + os.environ["FLASK_ENV"] = "development" + DEV_MODE = True + + +app = Flask(__name__) + + +@app.after_request # type: ignore +def close_tx(response: Response, session: Session) -> Response: + session.commit() + session.close() + return response + + +class PrivateFieldsCapableSchema(marshmallow.Schema): + def on_bind_field(self, field_name: str, field_obj: Field) -> None: + # Dataclasses (w przeciwieństwie do attrs) nie aliasują prywatnych pól + # w __init__, więc żeby API nie wymagało podawania pól w formacie "_uuid", + # aliasujemy je usuwając podkreślnik + field_obj.data_key = field_name.lstrip("_") + + +CustomerSchema = marshmallow_dataclass.class_schema( + Customer, base_schema=PrivateFieldsCapableSchema +) + + +@app.route("/fraudCheck", methods=["POST"]) +@expects_json() +def fraud_check(verifier: CustomerVerifier): + try: + customer = CustomerSchema().load(request.json) # type: ignore + except ValidationError as validation_error: + return jsonify(validation_error.messages), HTTPStatus.BAD_REQUEST + + result = verifier.verify(customer=customer) + if result.passed: + return jsonify({"message": "Weryfikacja udana"}) + else: + return jsonify({"message": "Bagiety już jadą"}), HTTPStatus.UNAUTHORIZED + + +APP_ENV = "DEV" if DEV_MODE else "PROD" +app_injector = assemble(env=APP_ENV) # type: ignore +FlaskInjector(app=app, injector=app_injector) diff --git a/05-architecture/05-04-chaos/smarttesting_main/__init__.py b/05-architecture/05-04-chaos/smarttesting_main/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-04-chaos/smarttesting_main/celery_app.py b/05-architecture/05-04-chaos/smarttesting_main/celery_app.py new file mode 100644 index 0000000..55b6ca9 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting_main/celery_app.py @@ -0,0 +1,9 @@ +import os + +from celery import Celery +from smarttesting_main.smart_testing_application import assemble + +env = os.environ.get("APP_ENV", "DEV") +app_injector = assemble(env=env) # type: ignore + +app = app_injector.get(Celery) diff --git a/05-architecture/05-04-chaos/smarttesting_main/celery_module.py b/05-architecture/05-04-chaos/smarttesting_main/celery_module.py new file mode 100644 index 0000000..8df53d9 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting_main/celery_module.py @@ -0,0 +1,42 @@ +from typing import List, NewType, cast + +import injector +from celery import Celery, Task +from kombu.serialization import register +from smarttesting import serialization +from smarttesting.verifier.customer.fraud_alert_task import FraudAlertTask +from smarttesting.verifier.customer.fraud_detected_handler import fraud_detected_handler +from smarttesting_main.task import task_with_injectables + +CeleryConfig = NewType("CeleryConfig", object) + + +class CeleryModule(injector.Module): + def __init__(self) -> None: + register( + "dataclasses_serialization", + serialization.dataclass_dump, + serialization.dataclass_load, + content_type="application/json", + content_encoding="utf-8", + ) + + @injector.singleton + @injector.provider + def celery(self, container: injector.Injector, config: CeleryConfig) -> Celery: + app = Celery(config_source=config) + app.__injector__ = container + return app + + @injector.singleton + @injector.provider + def fraud_alert_task(self, celery: Celery) -> FraudAlertTask: + # To robimy zamiast dekorowania funkcji zadania @app.task + registered_celery_task = celery.task(typing=False)(fraud_detected_handler) + task_with_injected_dependencies = task_with_injectables(registered_celery_task) + return cast(FraudAlertTask, task_with_injected_dependencies) + + @injector.multiprovider + def tasks(self, fraud_alert_task: FraudAlertTask) -> List[Task]: + # Potrzebne do rejestracji zadań przez Celery + return [fraud_alert_task] # type: ignore diff --git a/05-architecture/05-04-chaos/smarttesting_main/dev_modules.py b/05-architecture/05-04-chaos/smarttesting_main/dev_modules.py new file mode 100644 index 0000000..112fd68 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting_main/dev_modules.py @@ -0,0 +1,23 @@ +import injector +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, +) + + +class DevModule(injector.Module): + """Moduł injectora nadpisujący niektóre klasy na potrzeby lokalnego środowiska.""" + + @injector.provider + def stubbed_bik_verification_service(self) -> BIKVerificationService: + class BIKVerificationServiceStub(BIKVerificationService): + def verify(self, customer: Customer) -> CustomerVerificationResult: + if customer.person.surname == "Fraudeusz": + return CustomerVerificationResult.create_failed(customer.uuid) + else: + return CustomerVerificationResult.create_passed(customer.uuid) + + return BIKVerificationServiceStub(_bik_service_url="") diff --git a/05-architecture/05-04-chaos/smarttesting_main/infrastructure/__init__.py b/05-architecture/05-04-chaos/smarttesting_main/infrastructure/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-04-chaos/smarttesting_main/infrastructure/module.py b/05-architecture/05-04-chaos/smarttesting_main/infrastructure/module.py new file mode 100644 index 0000000..2c1dc4c --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting_main/infrastructure/module.py @@ -0,0 +1,14 @@ +import injector +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting_main.infrastructure.verification_repo import ( + SqlAlchemyVerificationRepository, +) +from sqlalchemy.orm import Session + + +class InfrastructureModule(injector.Module): + @injector.provider + def repo(self, session: Session) -> VerificationRepository: + return SqlAlchemyVerificationRepository(session) diff --git a/05-architecture/05-04-chaos/smarttesting_main/infrastructure/verification_repo.py b/05-architecture/05-04-chaos/smarttesting_main/infrastructure/verification_repo.py new file mode 100644 index 0000000..23011c2 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting_main/infrastructure/verification_repo.py @@ -0,0 +1,39 @@ +from dataclasses import dataclass +from typing import Optional +from uuid import UUID + +from smarttesting.verifier.customer.customer_verification_result import Status +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto +from smarttesting_main.infrastructure.verified_person import VerifiedPerson +from sqlalchemy.orm import Session + + +@dataclass +class SqlAlchemyVerificationRepository(VerificationRepository): + _session: Session + + def find_by_user_id(self, user_id: UUID) -> Optional[VerifiedPersonDto]: + model = ( + self._session.query(VerifiedPerson) + .filter(VerifiedPerson.uuid == str(user_id)) + .first() + ) + if model: + return VerifiedPersonDto( + uuid=UUID(model.uuid), + national_identification_number=model.national_identification_number, + status=Status(model.status), + ) + return None + + def save(self, verified_person: VerifiedPersonDto) -> None: + model = VerifiedPerson( + uuid=str(verified_person.uuid), + national_identification_number=verified_person.national_identification_number, + status=verified_person.status.value, + ) + self._session.add(model) + self._session.flush() diff --git a/05-architecture/05-04-chaos/smarttesting_main/infrastructure/verified_person.py b/05-architecture/05-04-chaos/smarttesting_main/infrastructure/verified_person.py new file mode 100644 index 0000000..961cc06 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting_main/infrastructure/verified_person.py @@ -0,0 +1,21 @@ +from typing import Any + +from sqlalchemy import Column, Integer, String +from sqlalchemy.ext.declarative import declarative_base + +Base: Any = declarative_base() + + +class VerifiedPerson(Base): + """ + Model bazodanowy. Wykorzystujemy ORM (mapowanie obiektowo relacyjne) + i obiekt tej klasy mapuje się na tabelę "verified". Każde pole klasy to osobna + kolumna w bazie danych. + """ + + __tablename__ = "verified" + + id = Column(Integer(), primary_key=True) + uuid: str = Column(String(36)) + national_identification_number: str = Column(String(255)) + status: str = Column(String(255)) diff --git a/05-architecture/05-04-chaos/smarttesting_main/smart_testing_application.py b/05-architecture/05-04-chaos/smarttesting_main/smart_testing_application.py new file mode 100644 index 0000000..9d01fd3 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting_main/smart_testing_application.py @@ -0,0 +1,91 @@ +import os +from dataclasses import dataclass +from typing import List, Literal, cast + +import injector +from celery import Task +from smarttesting.verifier.customer.module import CustomerModule +from smarttesting.verifier.customer.verification.module import VerificationModule +from smarttesting_main.celery_module import CeleryConfig, CeleryModule +from smarttesting_main.dev_modules import DevModule +from smarttesting_main.infrastructure.module import InfrastructureModule +from smarttesting_main.infrastructure.verified_person import Base +from sqlalchemy import create_engine +from sqlalchemy.orm import Session, scoped_session, sessionmaker + +Env = Literal["PROD", "DEV"] + + +def assemble(env: Env = "PROD") -> injector.Injector: + """Zainicjowanie kontenera IoC.""" + extra_modules: List[injector.Module] = [] + + db_dsn = os.environ.get("DB_URL", "sqlite:///dev_database.db") + broker_url = os.environ.get("BROKER_URL", "memory://") + if env == "PROD": + extra_modules += [ProdConfigModule(broker_url)] + elif env == "DEV": + extra_modules += [DevConfigModule(broker_url), DevModule()] + + modules = [ + VerificationModule(), + CustomerModule(), + CeleryModule(), + DbModule(db_dsn), + InfrastructureModule(), + ] + extra_modules + + container = injector.Injector(modules=modules, auto_bind=False) + + # Zarejestruj taski w Celery wywołując ich wstrzyknięcie + container.get(List[Task]) + + return container + + +@dataclass +class ProdConfigModule(injector.Module): + _broker_url: str + + @injector.singleton + @injector.provider + def celery_config(self) -> CeleryConfig: + CeleryConfigProd.broker_url = self._broker_url + return cast(CeleryConfig, CeleryConfigProd) + + +@dataclass +class DevConfigModule(injector.Module): + _broker_url: str + + @injector.singleton + @injector.provider + def celery_config(self) -> CeleryConfig: + CeleryConfigDev.broker_url = self._broker_url + return cast(CeleryConfig, CeleryConfigDev) + + +class CeleryConfigProd: + accept_content = {"json", "dataclasses_serialization"} + task_serializer = "dataclasses_serialization" + result_backend = "rpc://" + result_persistent = False + broker_url = "" # będzie nadpisane + + +class CeleryConfigDev(CeleryConfigProd): + worker_concurrency = 1 + task_always_eager = True + + +class DbModule(injector.Module): + def __init__(self, db_dsn: str) -> None: + self._db_dsn = db_dsn + self._engine = create_engine(self._db_dsn) + self._scoped_session_factory = scoped_session(sessionmaker(bind=self._engine)) + # Stwórz schemat bazy danych. Normalnie odbywa się to przez migracje + Base.metadata.create_all(self._engine) + + @injector.provider + def session(self) -> Session: + return self._scoped_session_factory() diff --git a/05-architecture/05-04-chaos/smarttesting_main/task.py b/05-architecture/05-04-chaos/smarttesting_main/task.py new file mode 100644 index 0000000..48fa2e6 --- /dev/null +++ b/05-architecture/05-04-chaos/smarttesting_main/task.py @@ -0,0 +1,57 @@ +import functools +import inspect + +from celery import Task +from injector import Injector, get_bindings +from sqlalchemy.orm import Session + + +def task_with_injectables(task: Task) -> Task: + """Dekorator na taski, który zapewni wstrzykiwanie zależności i transakcję. + + Od funkcji-taska wymagane jest, by wszystkie zależności do wstrzyknięte były + argumentami pozycyjnymi zaś wszystkie argumenty niewstrzykiwane były zadeklarowane + jako keyword-only. + + Jest to podyktowane uproszczeniami w tej integracji Celery z Injectorem. Przykład: + ``` + @task_with_injectables + @app.task(typing=False) + def add(x: Inject[int], y: Inject[float], *, z: int) -> None: + print(x + y + z) + ``` + + """ + # Safety-checks zanim przejdziemy dalej + + # Sprawdzamy, czy flaga typing jest ustawiona na False. Inaczej Celery protestuje, + # że wyzwalamy taska bez przekazania wszystkich argumentów (także tych, które potem + # będą wstrzyknięte) + assert ( + task.typing is False + ), "Wymagane jest wyłączenie sprawdzania argumentów przy schedulowaniu taska" + # Upewnijmy się, że niewstrzykiwane argumenty są opisane jako keyword-only + args_spec = inspect.getfullargspec(task.run) + bindings = get_bindings(task.run) + assert set(bindings) == set( + args_spec.args + ), "Wstrzykiwane argumenty muszą być pozycyjne" + assert args_spec.varargs is None, "*args nie jest wspierane" + assert args_spec.varkw is None, "**kwargs nie jest wspierane" + + actual_run = task.run + + @functools.wraps(actual_run) + def wrapped_run(*args, **kwargs): + injector: Injector = task.app.__injector__ + session = injector.get(Session) + try: + result = injector.call_with_injection(actual_run, args=args, kwargs=kwargs) + session.commit() + return result + finally: + session.close() + + task.run = wrapped_run + + return task diff --git a/05-architecture/05-04-chaos/tests/__init__.py b/05-architecture/05-04-chaos/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-04-chaos/tests/chaos_tests.py b/05-architecture/05-04-chaos/tests/chaos_tests.py new file mode 100644 index 0000000..80cdbab --- /dev/null +++ b/05-architecture/05-04-chaos/tests/chaos_tests.py @@ -0,0 +1,97 @@ +# pylint: disable=redefined-outer-name +import random +import time +import uuid +from http import HTTPStatus +from typing import Generator +from unittest import mock + +import pytest +from flask.testing import FlaskClient +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting_api import web_app +from sqlalchemy.exc import OperationalError + + +@pytest.fixture() +def test_client() -> Generator[FlaskClient, None, None]: + with web_app.app.test_client() as client: + yield client + + +@pytest.fixture() +def fraud_payload() -> dict: + return { + "uuid": str(uuid.uuid4()), + "person": { + "date_of_birth": "2020-01-01", + "gender": "FEMALE", + "name": "Stefania", + "national_id_number": "1234567890", + "surname": "Stefanowska", + }, + } + + +@pytest.mark.xfail +def test_returns_401_within_500_ms_when_calling_fraud_check_with_introduced_latency( + test_client: FlaskClient, + fraud_payload: dict, +) -> None: + """ + Hipoteza stanu ustalonego + POST na URL “/fraudCheck”, + reprezentujący oszusta, + odpowie statusem 401w ciągu 500 ms + Metoda + Włączamy opóźnienie mające miejsce w kliencie BIK używając monkey-patchingu + Wycofanie + Wycofujemy monkey-patching + """ + sleep_for = random.randint(1000, 3000) / 1000 + + original_method = BIKVerificationService.verify + + def take_your_time_verifying_national_id_no(*args, **kwargs): + time.sleep(sleep_for) + result = original_method(*args, **kwargs) + return result + + to_patch = ( + "smarttesting.verifier.customer.bik_verification_service." + "BIKVerificationService.verify" + ) + with mock.patch(to_patch, take_your_time_verifying_national_id_no): + start = time.time() + response = test_client.post("/fraudCheck", json=fraud_payload) + assert response.status_code == HTTPStatus.UNAUTHORIZED + + lasted = time.time() - start + assert lasted <= 0.5, "Odpowiedź nie została zwrócona w 500ms!" + + +@pytest.mark.xfail +def test_returns_401_within_500ms_when_calling_fraud_check_with_db_issues( + test_client: FlaskClient, + fraud_payload: dict, +) -> None: + """ + Hipoteza stanu ustalonego + POST na URL “/fraudCheck”, + reprezentujący oszusta, + odpowie statusem 401w ciągu 500 ms + Metoda + Używając monkey-patching zasymulujemy błędy z bazą danych + Wycofanie + Wycofujemy monkey-patching + """ + operational_error = OperationalError("", (), Exception()) + with mock.patch("sqlalchemy.orm.query.Query.first", side_effect=operational_error): + start = time.time() + response = test_client.post("/fraudCheck", json=fraud_payload) + assert response.status_code == HTTPStatus.UNAUTHORIZED + + lasted = time.time() - start + assert lasted <= 0.5, "Odpowiedź nie została zwrócona w 500ms!" diff --git a/05-architecture/05-05-performance/Dockerfile b/05-architecture/05-05-performance/Dockerfile new file mode 100644 index 0000000..f1c267c --- /dev/null +++ b/05-architecture/05-05-performance/Dockerfile @@ -0,0 +1,24 @@ +FROM python:3.11 + +ENV USERNAME python +RUN mkdir /app + +RUN useradd -ms /bin/bash ${USERNAME} +RUN chown ${USERNAME} /app +USER ${USERNAME} + +WORKDIR /app +RUN pip install poetry==1.6.1 +ENV PATH="/home/${USERNAME}/.local/bin:${PATH}" +COPY poetry.lock pyproject.toml /app/ + +RUN poetry config virtualenvs.create false && poetry export -f requirements.txt --output requirements.txt && pip install --user -r requirements.txt + +COPY ./smarttesting /app/smarttesting +COPY ./smarttesting_main /app/smarttesting_main +COPY ./smarttesting_api /app/smarttesting_api +COPY ./tests /app/tests + +STOPSIGNAL SIGINT +ENV FLASK_APP smarttesting.web_app:app +ENTRYPOINT ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "--access-logfile", "-", "smarttesting_api.web_app:app"] diff --git a/05-architecture/05-05-performance/README.adoc b/05-architecture/05-05-performance/README.adoc new file mode 100644 index 0000000..d84fd6b --- /dev/null +++ b/05-architecture/05-05-performance/README.adoc @@ -0,0 +1,77 @@ +# Testy wydajnościowe + +## Microbenchmarking + +Zajrzyj do `tests/microbenchmarking_tests.py`. Opieramy się na bibliotece pytest-benchmarking, która produkuje nam ładnie wyglądające raporty: +``` +--------------------------------------------------- benchmark: 1 tests --------------------------------------------------- +Name (time in us) Min Max Mean StdDev Median IQR Outliers OPS (Kops/s) Rounds Iterations +-------------------------------------------------------------------------------------------------------------------------- +test_processing_fraud 33.5860 410.7760 39.2674 17.9517 36.1160 2.8505 47;136 25.4664 1317 1 +-------------------------------------------------------------------------------------------------------------------------- + +Legend: + Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile. + OPS: Operations Per Second, computed as 1 / Mean +``` + +## Testy z wykorzystaniem Locusta + +Chcemy przeprowadzić load testy, czyli zobaczyć ilu użytkowników jesteśmy w stanie obsłużyć. Oczywiście na potrzeby szkolenia nie przygotowujemy specjalnego środowiska, uruchamiamy po prostu aplikację lokalnie. + +Przechodzimy do tego katalogu (`cd 05-architecture/05-05-performance/`) + +Budujemy i startujemy aplikację używając docker-compose (`docker-compose up`) + +Uruchamiamy locusta korzystając z komendy `locust`. Zaczyta ona nasz locustfile.py i uruchomi swój serwer na porcie 8089. + +``` +class FraudUser(FastHttpUser): + wait_time = between(1, 2) + connection_timeout = 1 # timeout 1 sekunda na połączenie + + @task + def perform_fraud_check(self): + ... +``` +Przechodzimy do interfejsu locusta `http://127.0.0.1:8089/` i podajemy: +a. ilość użytkowników (10000) +b. ilu użytkowników będziemy dodawać w każdej sekundzie (100) +c. adres strony (http://localhost:8000) + +image::resources/images/locust_ustawienia.png[] + +Gdy jesteśmy gotowi, uruchamiamy testy wciskając przycisk `Start swarming`. + +Początkowo, jesteśmy na zakładce Statistics. W pewnym momencie możemy zauważyć, że wyskakują nam błędy. + +image::resources/images/locust_moment_krytyczny.png[] + +Błędy dotyczą połączenia. + +image::resources/images/locust_bledy.png[] + +Warto poświęcić chwilę na eksplorację wykresów: + +image::resources/images/locust_wykresy.png[] + +Na sam koniec, wyłączając `locusta` w konsoli zobaczymy jeszcze raz podsumowanie: + +``` + Name # reqs # fails | Avg Min Max Median | req/s failures/s +-------------------------------------------------------------------------------------------------------------------------------------------- + POST /fraudCheck 60827 49074(80.68%) | 9639 7 93843 2700 | 409.27 330.19 +-------------------------------------------------------------------------------------------------------------------------------------------- + Aggregated 60827 49074(80.68%) | 9639 7 93843 2700 | 409.27 330.19 + +Response time percentiles (approximated) + Type Name 50% 66% 75% 80% 90% 95% 98% 99% 99.9% 99.99% 100% # reqs +--------|------------------------------------------------------------|---------|------|------|------|------|------|------|------|------|------|------|------| + POST /fraudCheck 2700 11000 12000 12000 13000 60000 60000 60000 75000 90000 94000 60827 +--------|------------------------------------------------------------|---------|------|------|------|------|------|------|------|------|------|------|------| + None Aggregated 2700 11000 12000 12000 13000 60000 60000 60000 75000 90000 94000 60827 +``` + +Jednoznacznie można powiedzieć, że szybko popsuliśmy aplikację biorąc pod uwagę, że 80% requestów zakończyło się niepowodzeniem. + +*UWAGA*: Wyniki tak prowadzonych testów obciążeniowych należy brać z dużym przymrużeniem oka. Pomijając brak odwzorowania produkcyjnego środowiska, to sam fakt uruchomienia benchmarku na tej samej maszynie, co sama aplikacja z całą pewnością nie daje miarodajnych wyników. diff --git a/05-architecture/05-05-performance/docker-compose.yml b/05-architecture/05-05-performance/docker-compose.yml new file mode 100644 index 0000000..fb2478b --- /dev/null +++ b/05-architecture/05-05-performance/docker-compose.yml @@ -0,0 +1,30 @@ +version: '3' + +services: + postgres: + image: postgres:14 + environment: + POSTGRES_DB: smarttesting + POSTGRES_USER: smarttesting + POSTGRES_PASSWORD: smarttesting + ports: + - 5432:5432 + rabbitmq: + image: rabbitmq:3.9.7-management-alpine + environment: + RABBITMQ_DEFAULT_USER: smarttesting + RABBITMQ_DEFAULT_PASS: smarttesting + ports: + - 5672:5672 + - 15672:15672 + app: + build: ./ + environment: + APP_ENV: "DEV" + DB_URL: "postgresql://smarttesting:smarttesting@postgres:5432/smarttesting" + BROKER_URL: "amqp://smarttesting:smarttesting@rabbitmq:5672/" + ports: + - 8000:8000 + depends_on: + - rabbitmq + - postgres diff --git a/05-architecture/05-05-performance/locustfile.py b/05-architecture/05-05-performance/locustfile.py new file mode 100644 index 0000000..37ecda0 --- /dev/null +++ b/05-architecture/05-05-performance/locustfile.py @@ -0,0 +1,38 @@ +import uuid + +from locust import between, task +from locust.contrib.fasthttp import FastHttpUser + + +class FraudUser(FastHttpUser): + wait_time = between(1, 2) + connection_timeout = 1 + + def on_start(self): + """Tu może być kod do wykonania przed testem. + + Przykładowo, mógłoby to być uderzenie na endpoint logowania. + W tym przykładzie nie ma jednak takiej potrzeby. + """ + + @task + def perform_fraud_check(self): + current_uuid = str(uuid.uuid4()) + payload = { + "uuid": current_uuid, + "person": { + "name": "Jan", + "surname": f"Fraudowski_{current_uuid}", + "date_of_birth": "1980-01-01", + "gender": "MALE", + "national_id_number": "2345678901", + }, + } + with self.client.post( + "/fraudCheck", json=payload, catch_response=True + ) as response: + # Domyślnie locust potraktuje odpowiedzi o kodzie 401 jak błędy + # Chcemy by uznawał je za sukces, gdyż u nas to normalny wynik oznaczający + # nieprzejście weryfikacji + if response.status_code == 401: + response.success() diff --git a/05-architecture/05-05-performance/poetry.lock b/05-architecture/05-05-performance/poetry.lock new file mode 100644 index 0000000..da5a8c4 --- /dev/null +++ b/05-architecture/05-05-performance/poetry.lock @@ -0,0 +1,4031 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "amqp" +version = "5.1.1" +description = "Low-level AMQP client for Python (fork of amqplib)." +optional = false +python-versions = ">=3.6" +files = [ + {file = "amqp-5.1.1-py3-none-any.whl", hash = "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359"}, + {file = "amqp-5.1.1.tar.gz", hash = "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2"}, +] + +[package.dependencies] +vine = ">=5.0.0" + +[[package]] +name = "annotated-types" +version = "0.5.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.7" +files = [ + {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, + {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, +] + +[[package]] +name = "anyio" +version = "3.7.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] + +[[package]] +name = "assertpy" +version = "1.1" +description = "Simple assertion library for unit testing in python with a fluent API" +optional = false +python-versions = "*" +files = [ + {file = "assertpy-1.1.tar.gz", hash = "sha256:acc64329934ad71a3221de185517a43af33e373bb44dc05b5a9b174394ef4833"}, +] + +[[package]] +name = "astroid" +version = "2.15.6" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "astroid-2.15.6-py3-none-any.whl", hash = "sha256:389656ca57b6108f939cf5d2f9a2a825a3be50ba9d589670f393236e0a03b91c"}, + {file = "astroid-2.15.6.tar.gz", hash = "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd"}, +] + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""} + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[[package]] +name = "bcrypt" +version = "4.0.1" +description = "Modern password hashing for your software and your servers" +optional = false +python-versions = ">=3.6" +files = [ + {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"}, + {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"}, + {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"}, + {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"}, + {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"}, + {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"}, + {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"}, + {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"}, + {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"}, + {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"}, + {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"}, + {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"}, + {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"}, + {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"}, + {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"}, +] + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + +[[package]] +name = "billiard" +version = "4.1.0" +description = "Python multiprocessing fork with improvements and bugfixes" +optional = false +python-versions = ">=3.7" +files = [ + {file = "billiard-4.1.0-py3-none-any.whl", hash = "sha256:0f50d6be051c6b2b75bfbc8bfd85af195c5739c281d3f5b86a5640c65563614a"}, + {file = "billiard-4.1.0.tar.gz", hash = "sha256:1ad2eeae8e28053d729ba3373d34d9d6e210f6e4d8bf0a9c64f92bd053f1edf5"}, +] + +[[package]] +name = "black" +version = "23.7.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, + {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, + {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, + {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, + {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, + {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, + {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, + {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, + {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, + {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, + {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, + {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, + {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, + {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, + {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "blinker" +version = "1.6.2" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.7" +files = [ + {file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"}, + {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"}, +] + +[[package]] +name = "brotli" +version = "1.0.9" +description = "Python bindings for the Brotli compression library" +optional = false +python-versions = "*" +files = [ + {file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"}, + {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"}, + {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6"}, + {file = "Brotli-1.0.9-cp27-cp27m-win32.whl", hash = "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa"}, + {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452"}, + {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7"}, + {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031"}, + {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43"}, + {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"}, + {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"}, + {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"}, + {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, + {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, + {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc0283a406774f465fb45ec7efb66857c09ffefbe49ec20b7882eff6d3c86d3a"}, + {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:11d3283d89af7033236fa4e73ec2cbe743d4f6a81d41bd234f24bf63dde979df"}, + {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1306004d49b84bd0c4f90457c6f57ad109f5cc6067a9664e12b7b79a9948ad"}, + {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1375b5d17d6145c798661b67e4ae9d5496920d9265e2f00f1c2c0b5ae91fbde"}, + {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cab1b5964b39607a66adbba01f1c12df2e55ac36c81ec6ed44f2fca44178bf1a"}, + {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ed6a5b3d23ecc00ea02e1ed8e0ff9a08f4fc87a1f58a2530e71c0f48adf882f"}, + {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb02ed34557afde2d2da68194d12f5719ee96cfb2eacc886352cb73e3808fc5d"}, + {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b3523f51818e8f16599613edddb1ff924eeb4b53ab7e7197f85cbc321cdca32f"}, + {file = "Brotli-1.0.9-cp311-cp311-win32.whl", hash = "sha256:ba72d37e2a924717990f4d7482e8ac88e2ef43fb95491eb6e0d124d77d2a150d"}, + {file = "Brotli-1.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:3ffaadcaeafe9d30a7e4e1e97ad727e4f5610b9fa2f7551998471e3736738679"}, + {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, + {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"}, + {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"}, + {file = "Brotli-1.0.9-cp35-cp35m-win32.whl", hash = "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1"}, + {file = "Brotli-1.0.9-cp35-cp35m-win_amd64.whl", hash = "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea"}, + {file = "Brotli-1.0.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f"}, + {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"}, + {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"}, + {file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"}, + {file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"}, + {file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"}, + {file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"}, + {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"}, + {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"}, + {file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"}, + {file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"}, + {file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"}, + {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"}, + {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8"}, + {file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"}, + {file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"}, + {file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"}, + {file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"}, + {file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"}, + {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"}, + {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7"}, + {file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"}, + {file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"}, + {file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"}, + {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, + {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73fd30d4ce0ea48010564ccee1a26bfe39323fde05cb34b5863455629db61dc7"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02177603aaca36e1fd21b091cb742bb3b305a569e2402f1ca38af471777fb019"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b43775532a5904bc938f9c15b77c613cb6ad6fb30990f3b0afaea82797a402d8"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bf37a08493232fbb0f8229f1824b366c2fc1d02d64e7e918af40acd15f3e337"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:330e3f10cd01da535c70d09c4283ba2df5fb78e915bea0a28becad6e2ac010be"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e1abbeef02962596548382e393f56e4c94acd286bd0c5afba756cffc33670e8a"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3148362937217b7072cf80a2dcc007f09bb5ecb96dae4617316638194113d5be"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:336b40348269f9b91268378de5ff44dc6fbaa2268194f85177b53463d313842a"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b09a16a1950b9ef495a0f8b9d0a87599a9d1f179e2d4ac014b2ec831f87e7"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c8e521a0ce7cf690ca84b8cc2272ddaf9d8a50294fd086da67e517439614c755"}, + {file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"}, +] + +[[package]] +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +optional = false +python-versions = "*" +files = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] + +[[package]] +name = "celery" +version = "5.3.4" +description = "Distributed Task Queue." +optional = false +python-versions = ">=3.8" +files = [ + {file = "celery-5.3.4-py3-none-any.whl", hash = "sha256:1e6ed40af72695464ce98ca2c201ad0ef8fd192246f6c9eac8bba343b980ad34"}, + {file = "celery-5.3.4.tar.gz", hash = "sha256:9023df6a8962da79eb30c0c84d5f4863d9793a466354cc931d7f72423996de28"}, +] + +[package.dependencies] +billiard = ">=4.1.0,<5.0" +click = ">=8.1.2,<9.0" +click-didyoumean = ">=0.3.0" +click-plugins = ">=1.1.1" +click-repl = ">=0.2.0" +kombu = ">=5.3.2,<6.0" +python-dateutil = ">=2.8.2" +tzdata = ">=2022.7" +vine = ">=5.0.0,<6.0" + +[package.extras] +arangodb = ["pyArango (>=2.0.2)"] +auth = ["cryptography (==41.0.3)"] +azureblockblob = ["azure-storage-blob (>=12.15.0)"] +brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] +cassandra = ["cassandra-driver (>=3.25.0,<4)"] +consul = ["python-consul2 (==0.1.5)"] +cosmosdbsql = ["pydocumentdb (==2.3.5)"] +couchbase = ["couchbase (>=3.0.0)"] +couchdb = ["pycouchdb (==1.14.2)"] +django = ["Django (>=2.2.28)"] +dynamodb = ["boto3 (>=1.26.143)"] +elasticsearch = ["elasticsearch (<8.0)"] +eventlet = ["eventlet (>=0.32.0)"] +gevent = ["gevent (>=1.5.0)"] +librabbitmq = ["librabbitmq (>=2.0.0)"] +memcache = ["pylibmc (==1.6.3)"] +mongodb = ["pymongo[srv] (>=4.0.2)"] +msgpack = ["msgpack (==1.0.5)"] +pymemcache = ["python-memcached (==1.59)"] +pyro = ["pyro4 (==4.82)"] +pytest = ["pytest-celery (==0.0.0)"] +redis = ["redis (>=4.5.2,!=4.5.5,<5.0.0)"] +s3 = ["boto3 (>=1.26.143)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +solar = ["ephem (==4.1.4)"] +sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] +sqs = ["boto3 (>=1.26.143)", "kombu[sqs] (>=5.3.0)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] +tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=1.3.1)"] +zstd = ["zstandard (==0.21.0)"] + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.2.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "click-didyoumean" +version = "0.3.0" +description = "Enables git-like *did-you-mean* feature in click" +optional = false +python-versions = ">=3.6.2,<4.0.0" +files = [ + {file = "click-didyoumean-0.3.0.tar.gz", hash = "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"}, + {file = "click_didyoumean-0.3.0-py3-none-any.whl", hash = "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667"}, +] + +[package.dependencies] +click = ">=7" + +[[package]] +name = "click-plugins" +version = "1.1.1" +description = "An extension module for click to enable registering CLI commands via setuptools entry-points." +optional = false +python-versions = "*" +files = [ + {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] + +[package.dependencies] +click = ">=4.0" + +[package.extras] +dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] + +[[package]] +name = "click-repl" +version = "0.3.0" +description = "REPL plugin for Click" +optional = false +python-versions = ">=3.6" +files = [ + {file = "click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9"}, + {file = "click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812"}, +] + +[package.dependencies] +click = ">=7.0" +prompt-toolkit = ">=3.0.36" + +[package.extras] +testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "configargparse" +version = "1.7" +description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables." +optional = false +python-versions = ">=3.5" +files = [ + {file = "ConfigArgParse-1.7-py3-none-any.whl", hash = "sha256:d249da6591465c6c26df64a9f73d2536e743be2f244eb3ebe61114af2f94f86b"}, + {file = "ConfigArgParse-1.7.tar.gz", hash = "sha256:e7067471884de5478c58a511e529f0f9bd1c66bfef1dea90935438d6c23306d1"}, +] + +[package.extras] +test = ["PyYAML", "mock", "pytest"] +yaml = ["PyYAML"] + +[[package]] +name = "coverage" +version = "7.3.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"}, + {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"}, + {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"}, + {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"}, + {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"}, + {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"}, + {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"}, + {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"}, + {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"}, + {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"}, + {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"}, + {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"}, + {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"}, +] + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cryptography" +version = "41.0.3" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, + {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, + {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, + {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "dill" +version = "0.3.7" +description = "serialize all of Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, + {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + +[[package]] +name = "distro" +version = "1.8.0" +description = "Distro - an OS platform information API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "distro-1.8.0-py3-none-any.whl", hash = "sha256:99522ca3e365cac527b44bde033f64c6945d90eb9f769703caaec52b09bbd3ff"}, + {file = "distro-1.8.0.tar.gz", hash = "sha256:02e111d1dc6a50abb8eed6bf31c3e48ed8b0830d1ea2a1b78c61765c2513fdd8"}, +] + +[[package]] +name = "dnspython" +version = "2.4.2" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, + {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, +] + +[package.extras] +dnssec = ["cryptography (>=2.6,<42.0)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] +doq = ["aioquic (>=0.9.20)"] +idna = ["idna (>=2.1,<4.0)"] +trio = ["trio (>=0.14,<0.23)"] +wmi = ["wmi (>=1.5.1,<2.0.0)"] + +[[package]] +name = "docker" +version = "6.1.3" +description = "A Python library for the Docker Engine API." +optional = false +python-versions = ">=3.7" +files = [ + {file = "docker-6.1.3-py3-none-any.whl", hash = "sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9"}, + {file = "docker-6.1.3.tar.gz", hash = "sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20"}, +] + +[package.dependencies] +packaging = ">=14.0" +paramiko = {version = ">=2.4.3", optional = true, markers = "extra == \"ssh\""} +pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} +requests = ">=2.26.0" +urllib3 = ">=1.26.0" +websocket-client = ">=0.32.0" + +[package.extras] +ssh = ["paramiko (>=2.4.3)"] + +[[package]] +name = "docker-compose" +version = "1.29.2" +description = "Multi-container orchestration for Docker" +optional = false +python-versions = ">=3.4" +files = [ + {file = "docker-compose-1.29.2.tar.gz", hash = "sha256:4c8cd9d21d237412793d18bd33110049ee9af8dab3fe2c213bbd0733959b09b7"}, + {file = "docker_compose-1.29.2-py2.py3-none-any.whl", hash = "sha256:8d5589373b35c8d3b1c8c1182c6e4a4ff14bffa3dd0b605fcd08f73c94cef809"}, +] + +[package.dependencies] +colorama = {version = ">=0.4,<1", markers = "sys_platform == \"win32\""} +distro = ">=1.5.0,<2" +docker = {version = ">=5", extras = ["ssh"]} +dockerpty = ">=0.4.1,<1" +docopt = ">=0.6.1,<1" +jsonschema = ">=2.5.1,<4" +python-dotenv = ">=0.13.0,<1" +PyYAML = ">=3.10,<6" +requests = ">=2.20.0,<3" +texttable = ">=0.9.0,<2" +websocket-client = ">=0.32.0,<1" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2)"] +tests = ["ddt (>=1.2.2,<2)", "pytest (<6)"] + +[[package]] +name = "dockerpty" +version = "0.4.1" +description = "Python library to use the pseudo-tty of a docker container" +optional = false +python-versions = "*" +files = [ + {file = "dockerpty-0.4.1.tar.gz", hash = "sha256:69a9d69d573a0daa31bcd1c0774eeed5c15c295fe719c61aca550ed1393156ce"}, +] + +[package.dependencies] +six = ">=1.3.0" + +[[package]] +name = "docopt" +version = "0.6.2" +description = "Pythonic argument parser, that will make you smile" +optional = false +python-versions = "*" +files = [ + {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.3" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "expects" +version = "0.9.0" +description = "Expressive and extensible TDD/BDD assertion library for Python" +optional = false +python-versions = "*" +files = [ + {file = "expects-0.9.0.tar.gz", hash = "sha256:419902ccafe81b7e9559eeb6b7a07ef9d5c5604eddb93000f0642b3b2d594f4c"}, +] + +[[package]] +name = "factory-boy" +version = "3.3.0" +description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby." +optional = false +python-versions = ">=3.7" +files = [ + {file = "factory_boy-3.3.0-py2.py3-none-any.whl", hash = "sha256:a2cdbdb63228177aa4f1c52f4b6d83fab2b8623bf602c7dedd7eb83c0f69c04c"}, + {file = "factory_boy-3.3.0.tar.gz", hash = "sha256:bc76d97d1a65bbd9842a6d722882098eb549ec8ee1081f9fb2e8ff29f0c300f1"}, +] + +[package.dependencies] +Faker = ">=0.7.0" + +[package.extras] +dev = ["Django", "Pillow", "SQLAlchemy", "coverage", "flake8", "isort", "mongoengine", "sqlalchemy-utils", "tox", "wheel (>=0.32.0)", "zest.releaser[recommended]"] +doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] + +[[package]] +name = "faker" +version = "19.3.1" +description = "Faker is a Python package that generates fake data for you." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Faker-19.3.1-py3-none-any.whl", hash = "sha256:e2722fdf622cf24e974aaba15a3dee97a6f8b98d869bd827ff1af9c87695af46"}, + {file = "Faker-19.3.1.tar.gz", hash = "sha256:a6624d9574623bb27dfca33fff94581cd7b23b562901db8ad59acbde9a52543e"}, +] + +[package.dependencies] +python-dateutil = ">=2.4" + +[[package]] +name = "fastapi" +version = "0.103.1" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fastapi-0.103.1-py3-none-any.whl", hash = "sha256:5e5f17e826dbd9e9b5a5145976c5cd90bcaa61f2bf9a69aca423f2bcebe44d83"}, + {file = "fastapi-0.103.1.tar.gz", hash = "sha256:345844e6a82062f06a096684196aaf96c1198b25c06b72c1311b882aa2d8a35d"}, +] + +[package.dependencies] +anyio = ">=3.7.1,<4.0.0" +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.27.0,<0.28.0" +typing-extensions = ">=4.5.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "flake8" +version = "6.1.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" + +[[package]] +name = "flask" +version = "2.3.3" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask-2.3.3-py3-none-any.whl", hash = "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b"}, + {file = "flask-2.3.3.tar.gz", hash = "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=2.3.7" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "flask-admin" +version = "1.6.1" +description = "Simple and extensible admin interface framework for Flask" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Flask-Admin-1.6.1.tar.gz", hash = "sha256:24cae2af832b6a611a01d7dc35f42d266c1d6c75a426b869d8cb241b78233369"}, + {file = "Flask_Admin-1.6.1-py3-none-any.whl", hash = "sha256:fd8190f1ec3355913a22739c46ed3623f1d82b8112cde324c60a6fc9b21c9406"}, +] + +[package.dependencies] +Flask = ">=0.7" +wtforms = "*" + +[package.extras] +aws = ["boto"] +azure = ["azure-storage-blob"] + +[[package]] +name = "flask-basicauth" +version = "0.2.0" +description = "HTTP basic access authentication for Flask." +optional = false +python-versions = "*" +files = [ + {file = "Flask-BasicAuth-0.2.0.tar.gz", hash = "sha256:df5ebd489dc0914c224419da059d991eb72988a01cdd4b956d52932ce7d501ff"}, +] + +[package.dependencies] +Flask = "*" + +[[package]] +name = "flask-cors" +version = "4.0.0" +description = "A Flask extension adding a decorator for CORS support" +optional = false +python-versions = "*" +files = [ + {file = "Flask-Cors-4.0.0.tar.gz", hash = "sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0"}, + {file = "Flask_Cors-4.0.0-py2.py3-none-any.whl", hash = "sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783"}, +] + +[package.dependencies] +Flask = ">=0.9" + +[[package]] +name = "flask-expects-json" +version = "1.7.0" +description = "Decorator for REST endpoints in flask. Validate JSON request data." +optional = false +python-versions = "*" +files = [ + {file = "flask-expects-json-1.7.0.tar.gz", hash = "sha256:4ef186a86f10572a21af82e549546deda024326628b4a96cd14d3a0f71754d62"}, +] + +[package.dependencies] +flask = ">=1.0.2" +jsonschema = ">=3.0.1" + +[package.extras] +async = ["flask[async] (>=2.0.0)"] + +[[package]] +name = "flask-injector" +version = "0.15.0" +description = "Adds Injector, a Dependency Injection framework, support to Flask." +optional = false +python-versions = "*" +files = [ + {file = "Flask-Injector-0.15.0.tar.gz", hash = "sha256:567c69da7657b96565db990bb7c87cc0acf04e383714341954902f1ef85c5875"}, + {file = "Flask_Injector-0.15.0-py2.py3-none-any.whl", hash = "sha256:9908904ab8d8830e5160b274fd5dd73453741c9c618d3fc6deb2b08d894f4ece"}, +] + +[package.dependencies] +Flask = ">=2.2.0" +injector = ">=0.20.0" + +[[package]] +name = "freezegun" +version = "1.2.2" +description = "Let your Python tests travel through time" +optional = false +python-versions = ">=3.6" +files = [ + {file = "freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"}, + {file = "freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"}, +] + +[package.dependencies] +python-dateutil = ">=2.7" + +[[package]] +name = "future" +version = "0.18.3" +description = "Clean single-source support for Python 3 and 2" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"}, +] + +[[package]] +name = "gevent" +version = "23.9.0.post1" +description = "Coroutine-based network library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "gevent-23.9.0.post1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c4b2efc68fb3aef5dde8204d0f71c3585ba621c57e9b937b46ff5678f1cd7404"}, + {file = "gevent-23.9.0.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b3a813ff1151d75538bb5ec821332627cd2c4685cc72702640d203a426041ca"}, + {file = "gevent-23.9.0.post1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2cf108ee9c18c0ea5cf81d3fc7859f512dab61c2d76937b2510c7bf8cfaabfe7"}, + {file = "gevent-23.9.0.post1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ff1771bc8f2ed343f32c2f40dbd25f04fdfe2d83eb02e0401945dc61115dbe"}, + {file = "gevent-23.9.0.post1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:26e308815fb2d4d84e7a55eebd00c4014e5cb07ead8f3f66236e5a797937340c"}, + {file = "gevent-23.9.0.post1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fd8941f5c5cc998114b89e032e1ebabd779d99faa60d004b960587b866195ba"}, + {file = "gevent-23.9.0.post1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:deb353bf15ab724fe8bf587433519d558ddfd89fa35b77f7886de4312517eee4"}, + {file = "gevent-23.9.0.post1-cp310-cp310-win_amd64.whl", hash = "sha256:9a4c1afd3fa2103f11c27f19b060c2ed122ed487cbdf79e7987ef261aa04429f"}, + {file = "gevent-23.9.0.post1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:29ccc476077a317d082ddad4dabf5c68ccf7079aaf14aa5be8e0529b06f569a6"}, + {file = "gevent-23.9.0.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cb909b0649b0e15c069527a61af83f067e4c59ff03a07aa40aa2d5e8e355d20"}, + {file = "gevent-23.9.0.post1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f463a131df0e8d466a8caf7909ad73c80f793ed97c6376e78c7c75a51f19cba0"}, + {file = "gevent-23.9.0.post1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:edb9ceb5f88154e83ee8fc2e4b2d8ca070c62f1266d73f88578109b9c4564003"}, + {file = "gevent-23.9.0.post1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ee6382fde487a84a4a21711988d9eb97ed63c69be085b442e1665dc44022be60"}, + {file = "gevent-23.9.0.post1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9d21796a54dcccabe9fc0053c1bd991dfa63e554873e5a5f9c0885984068b2a"}, + {file = "gevent-23.9.0.post1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d33f997d97f267e9f62db9cd03d42f711df2ddba944173853773b220187ca7a0"}, + {file = "gevent-23.9.0.post1-cp311-cp311-win_amd64.whl", hash = "sha256:4bdca1bd1fb0c3524dbe0a273c87eb9a0428ea7f2533d579a3194426fbb93c92"}, + {file = "gevent-23.9.0.post1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:bccd4e3d21e7c5f7b72e3382523702ce58add691417633dfafa305978bebee84"}, + {file = "gevent-23.9.0.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c24bd27f8a75fe70475e72dde519d569d58f0f5e8f4f6d009493ee660855c3d1"}, + {file = "gevent-23.9.0.post1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc5b637870c325899eb9fc44915670deb2ef413c5c90ad0d96c335e41de1f751"}, + {file = "gevent-23.9.0.post1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bcff1fc4bc0e5610aa541ad14fead244e8b789fda98acbacd268668089c7373"}, + {file = "gevent-23.9.0.post1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c3d665d252903982469b0933f31dd346a249d2e2c45dd0e1c9263889a5dbfbc6"}, + {file = "gevent-23.9.0.post1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f23a560f1731a2b4f582b89e8d8afcbfd66695b025712e295f21aeec3d786413"}, + {file = "gevent-23.9.0.post1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1b2804d7e2909074b0cf6e2371595935a699edc8bd403211a414752e68f7e0ad"}, + {file = "gevent-23.9.0.post1-cp312-cp312-win_amd64.whl", hash = "sha256:f7aa27b8585b66fb5fff3a54e3e7bb837258bda39bb65a788304c8d45b9bb9d4"}, + {file = "gevent-23.9.0.post1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:bc836d91b834fa4ce18ee062861dc6e488f35254def8301ffcac6900331941a7"}, + {file = "gevent-23.9.0.post1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:a21b9c7356e9ab0baaa8afa85fb18406cbff54d3cf8033e1e97e7186a3deb391"}, + {file = "gevent-23.9.0.post1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3c4acda344e9864b2d0755fad1c736dc4effae95b0fd8915a261ff6ace09416f"}, + {file = "gevent-23.9.0.post1-cp38-cp38-win32.whl", hash = "sha256:22d7fdbfc7127c5d59511c3de9f8394a125f32bccc1254915944d95522876a8e"}, + {file = "gevent-23.9.0.post1-cp38-cp38-win_amd64.whl", hash = "sha256:3e6b6c53e1e81b3f22180da316769ac55a41085655971e0e086899f0ddb017b0"}, + {file = "gevent-23.9.0.post1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:f0dbee943865313331ece9f9675a30848d027df653b0ff4881d2be14d0c2ea1c"}, + {file = "gevent-23.9.0.post1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:98de0f1eecd772df87018e04ef8e274b72c3b3127d2e15f76b8b761ed135b803"}, + {file = "gevent-23.9.0.post1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17ebb6f981389c17321b95bc59ff6a65edeb98f3205884babaec9cb514aaa0d3"}, + {file = "gevent-23.9.0.post1-cp39-cp39-win32.whl", hash = "sha256:f731574d908cbe505e103f4c5b4d64fe4e0a82cef371e925212689194ee22198"}, + {file = "gevent-23.9.0.post1-cp39-cp39-win_amd64.whl", hash = "sha256:595706422f1832f2dd29bb9cb3219780f1e158d5a771199fe26b00da1bae8214"}, + {file = "gevent-23.9.0.post1.tar.gz", hash = "sha256:943f26edada39dfd5f50551157bb9011191c7367be36e341d0f1cdecfe07a229"}, +] + +[package.dependencies] +cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} +greenlet = [ + {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.12\""}, + {version = ">=3.0rc1", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.12\""}, +] +"zope.event" = "*" +"zope.interface" = "*" + +[package.extras] +dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] +docs = ["furo", "repoze.sphinx.autointerface", "sphinx", "sphinxcontrib-programoutput", "zope.schema"] +monitor = ["psutil (>=5.7.0)"] +recommended = ["cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)"] +test = ["cffi (>=1.12.2)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idna", "objgraph", "psutil (>=5.7.0)", "requests", "setuptools"] + +[[package]] +name = "geventhttpclient" +version = "2.0.10" +description = "http client library for gevent" +optional = false +python-versions = "*" +files = [ + {file = "geventhttpclient-2.0.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2ba6814f4a31286573f7fd24154bdb9cbe4ae01e754f48d71b1944798bf663"}, + {file = "geventhttpclient-2.0.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01dfb0de68f219b7a534121caa71481e32574bba7fe547fa37ee47a73a7b224"}, + {file = "geventhttpclient-2.0.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1b2c7e6bb15910a2e86f8da375adfd63ac07587a1c764cedc082b00390bcd76e"}, + {file = "geventhttpclient-2.0.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:022801e2323e3e673e3c7034f6bc5440b4651649df03396eb1b3a86a6aba899d"}, + {file = "geventhttpclient-2.0.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfc1489e71b010d8ce8857578cdb1b8ba348626807aa9d077fc73c9864e51e1"}, + {file = "geventhttpclient-2.0.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aef527f67653488000218283368e526fa699604e03e98ce4e0e588e89116977d"}, + {file = "geventhttpclient-2.0.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a673a4b6b1839c8491372e43208912040c25a34254c60bf1d084489ddc300ee"}, + {file = "geventhttpclient-2.0.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2ceae04de8bdb4ef1d0ca7724cd9cad77c6611aac3830a24a7f13747e8b748c7"}, + {file = "geventhttpclient-2.0.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc3effc56065a5c26292ca26127e6fdd0f68429b413e847a8b7bad38972aab53"}, + {file = "geventhttpclient-2.0.10-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:304c1d24d33b19cae53614ffc91c68d1e682d8b60a4d9eefcf87fcd099b1c2f2"}, + {file = "geventhttpclient-2.0.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d39b468aba4dbec358eb0205d41791afc53651eee789566239e544ed6c8b7dbb"}, + {file = "geventhttpclient-2.0.10-cp310-cp310-win32.whl", hash = "sha256:91cd3f680ee413cc83819f0e1f63e49297c550099e85bbee92e73960d3eba041"}, + {file = "geventhttpclient-2.0.10-cp310-cp310-win_amd64.whl", hash = "sha256:9a9c02c44b1e4e6edf929aa7c98b665f4db9cdcd406e4a9b4897f48127a6dd6b"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb079f45fdc8e2bf7157ef55727d8c2bb7c95fb4f754dac61d7a9b91da0c5c1a"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd657ba277f4888b5a4b5da72a587641d6725d1e6ab0dd6875f29ad0a3458ad5"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fd589226e908a6c2556572ff3b13fe00308849b44dec47bb794de27afa0339de"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e38a4123ed63935ccaf18054135e50fe4f798744f10d37faa9d2eaddfcff12f"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9711c629559b1f0be4977a2be79899fb90085a5a1f85ca435ec91d6a5648ff3f"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7435458eada516d1caf8499a2618db1160e62bbe0c8e4d6f3ab03fc507587dff"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:446b17f342315d8c63c020732b9ab939a874014c84cf250d66ffd96c877f6d96"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:68279ab30e20f48fbac4371cd5850c77ecc37f24ef656f8c37afd5454576bc57"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0823827d029aed708d6ed577994cdd3b4c1466690be0b7f27f3b459783ab7c6a"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:505722ef30d680c983812795e047dbb2d09dc0f476015e5d3411251bb960e3b1"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8ebf2ce3ca1308ffc9daae1f45860b2f5a3c1a75f6c46b2d12b9a478f7c4f05e"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-win32.whl", hash = "sha256:4ad20f6f03e34e44366e6794a28bd0b35b09e1dca3350bbf0a72c50d64c9c266"}, + {file = "geventhttpclient-2.0.10-cp311-cp311-win_amd64.whl", hash = "sha256:c1883adee9c69374e30f43f7bea85dd4a7b4cc440e3c6ecf975bef04e1f8a972"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7525bd48fadc93a79d13383cf38a10eed6c9f2f3c65e1f3a8cd4978dfcf023a0"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c6820591122c4444652238806c0c97f6c0de76d790bab255fd242962c8026654"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34a06879a2dbc2a78edf8cfcabbcc67a177d0642b0e4490b771b72ebceea4537"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:441a16eb8743b44c7b6f0acbbdc38d6f407f0763eb859ae0ae6e61427ac86c3e"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf3a7a5c6b244c6d55de9d28245f70ee33cca8353355a9b10ea0c2e08ff24a0"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e65a91c7a4e559d0f60dab4439d15355ade9c30f5c73276bb748b000a062e8f"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d051f15c3c92140ce142336ae05a76868ce05a86b4e15c5becb5431beaf53a07"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:224d1f34e32d683889f8a92f92ce3a1e4bb2f3f4a3d85b931a8df493dd59e9e7"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:309a35f033766476765a48a9c2712ffb988c3e3d50cd4b98eaa77e34b470af7e"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:d9d09579969cfb244e88bb599ac93549d8c0b56018de1f1ffade4a986951ad1d"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:15ecc1599646755d7e2e5648606c21ace00c3337c2d72d33b4e2de5d66b4ed65"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-win32.whl", hash = "sha256:c99dd907622f28523c3f90b8718643c103ce6519be7128e75730c398fd23d157"}, + {file = "geventhttpclient-2.0.10-cp312-cp312-win_amd64.whl", hash = "sha256:9532ee9066072737fe0eac1714c99792d7007769d529a056bc0c238946f67fdf"}, + {file = "geventhttpclient-2.0.10-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7197223e650b9e07e1b3ddc1b41b8cdc1c2c4b408f392bdf827efa8c0cb6c67b"}, + {file = "geventhttpclient-2.0.10-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304b9d67c91db96633d89b938b68020d7f787ff962580b1cff819d4218d7eb45"}, + {file = "geventhttpclient-2.0.10-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdd4aa396f69f4063b7fcddb2c400c9eea933bcce63f3c65fc28a1869c88179c"}, + {file = "geventhttpclient-2.0.10-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8da6f49e65cdcc19fbc37978d9e3e14ba30c949d9a5492e0f96e1e5270f2305"}, + {file = "geventhttpclient-2.0.10-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba65c48a63042deadc8e4f1f5348409d851d6fa06f8a1b5a58fd6fd24e50daaf"}, + {file = "geventhttpclient-2.0.10-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f3dcbf4a53852498128937e870c4b0ced1ed49b9153c68d12a52a0711652b9cf"}, + {file = "geventhttpclient-2.0.10-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:976e86d63fd1cd3bda4f78ec469b6d1c8dec4259abeb62191464e8dd4d40bb8e"}, + {file = "geventhttpclient-2.0.10-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:05be35bc7d2bd3ad6f0aa5042ae5a0b171ff19ec75ffeae1b4a2698572dd67a4"}, + {file = "geventhttpclient-2.0.10-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b3556a97e1c71d6b323076e6153f3afcf4f2e37ad79c3fe6d5bf006d3c1b5436"}, + {file = "geventhttpclient-2.0.10-cp36-cp36m-win32.whl", hash = "sha256:2b2d801205000f673f879b4edc1064b7dfc1bdd0dc5257bf565e6e7386b818bf"}, + {file = "geventhttpclient-2.0.10-cp36-cp36m-win_amd64.whl", hash = "sha256:7fd672aa9186607ac802b09efd3c835eb808f930a5c3489553dbfcbe78539129"}, + {file = "geventhttpclient-2.0.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:551b147e4c2ea60bfefc4f12dd061bfe84792497a32a369f8dab7f503da5aa05"}, + {file = "geventhttpclient-2.0.10-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:113c001d339b9e5c209f6f9da9271b0011341c25a4171d142c1d802bc0889ec4"}, + {file = "geventhttpclient-2.0.10-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fcdfdbbbf3c96265aca110433a5ce68e807336fa58bd7ef0651e66032037575"}, + {file = "geventhttpclient-2.0.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:223daf689883680eef2aa1b773a2bd7e6500749615332b0a0949ee11afeeeff9"}, + {file = "geventhttpclient-2.0.10-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45343be209224de6e525611938a41a4269c36df3b3c1f6e12f99af188d192a4"}, + {file = "geventhttpclient-2.0.10-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c268f7573f2b3cceabdc086abca96a59fb2766acbf60fb349ccbc208b6051e7c"}, + {file = "geventhttpclient-2.0.10-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c8117ef8e405fa05f5ea50fd9eb1d538bb7eeb86dba2849fb25d8296fabb70fc"}, + {file = "geventhttpclient-2.0.10-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fb70548781b3ba3531ec3572ae6f4cd87f387822c412fff1ee6fe52c2e4b66cf"}, + {file = "geventhttpclient-2.0.10-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b87dcef1ca6eb9127fd60844f1dd77063081335079b498bc1e1cd8e4764b6863"}, + {file = "geventhttpclient-2.0.10-cp37-cp37m-win32.whl", hash = "sha256:8ff70f24183705f2cb63dc132b4dd4e0eec58b8f182fde76f5a205e4608266cd"}, + {file = "geventhttpclient-2.0.10-cp37-cp37m-win_amd64.whl", hash = "sha256:f30d83152339779650a97471f27ef2fb2e6804ce660c96790c0d01c66648483f"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:786e120946a4dc1c7ede5a04943119540a1ccc22227029cdb7988a3d216885b1"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c27943dff539c4bc19f31ea8bffbb79a215e3b3f72b87686791956af39586ac4"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a377934706416ef04b92291540b609c7dde004a7ccb3b4e047873af2432d78e4"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b91c1a73e99ef4542e7098a997d1a4bce08cafb935e24a7b6d02c6da2359c91d"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80683052062f4cb6f18e7d2b1dba91f9536f7955a12660d599ed64bb4aa56d1e"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44c06c9882cc50201e5c7fe667eae73a491b6590018aa43c54c79e88c30abdb0"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271834908b41954fedc777478ffdc10050387bb5863805e19301b80e0fd85618"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f48dfd37d341c8433e7a2f76108b3d21614ccf2fbe00051d9dd29b3258efa6"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d6110eb2f685c6dcaff56e9b3b161da2eb432eea15b68cee0f51ec5d28c886ea"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a9e9c1c080a527dd9047e0421231cdd2394eeb92f94836f4ad7d7fece937ba26"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fa34492682347420384e49bd7c15886330af685924fc64badefce642867e4856"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-win32.whl", hash = "sha256:ea66408103b4c8954cbd3cc464be0e968a139d073987555a280760fb56fed41f"}, + {file = "geventhttpclient-2.0.10-cp38-cp38-win_amd64.whl", hash = "sha256:ef45b4facbaf7793373a32cab3f3e9460b76eb5f854b066f53b4753eca0efa7d"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75e09f122824d1d4aa3e9e48089a5e6f5c0925c817dfb99a65aeafa173243a27"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c9b4269873ce14bdd0977ae7b3b29e55ba2dc187b1088665cfe78fc094fc6795"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39db24bf426471cf81530675459ea209355032bf73f55a6e111f28853fe7564f"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf9148ce0072e6f2c644522b38d22d2749367dd599a4b32991ca9fc5feb112c5"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:452624f7e1b16e27c5df5b4f639a5a45f9372d9d9a8e7d2754f2f034b04d43d3"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57ecf4d4a09b502d2d4b25bc074b10290d595d80b6ce86294ecdd992cff80fb9"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e338a3b23d05c7550a6163b8d367de953be25f1d94949d043b325390df72d527"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6d2b0b87bb933911dadb702df74a7da91a4cdd86b6d75800db8494d7e5709e70"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5396759194bef95b92128dfd8b80d332f809de23193a2529071a519afd6e9250"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c13ba845f042d0807f6024b80c458b22b615028cc4f4ad23bd67c6db9de9969e"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24b33b88fc7b815139b80736bb46a7f6113abc7edd3d846cd99fc8d8c31d8439"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-win32.whl", hash = "sha256:e545fa59f298f6fc5219403f06819271f77a67216d1352f5cf703f292be05c3e"}, + {file = "geventhttpclient-2.0.10-cp39-cp39-win_amd64.whl", hash = "sha256:216e451ea72fe2372bc72e34f214ef8bc9d62b049d9048f88f81338e1c6008a5"}, + {file = "geventhttpclient-2.0.10-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:71a224032b2da3fe30d75fb57fb9d3e8ff323895e14facd9374e585d5bf52d01"}, + {file = "geventhttpclient-2.0.10-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bed5b6051582bdf39c7ff15051ec0758d1a0ebcb6ff09b6ae600717caf3f379e"}, + {file = "geventhttpclient-2.0.10-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e2e2eed1d821854f9f32f475d258af605a87ce12dc4d93abe61c022bf2bb06e"}, + {file = "geventhttpclient-2.0.10-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2cd1ecc9a389ca54a5e538769c3f35a82478006dc50eb505989d2ff6c3cf518"}, + {file = "geventhttpclient-2.0.10-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fb7018bfd4f6808d4e3b9cdda2dcb52cc01236a4bb65e18021fb8816996e9cd3"}, + {file = "geventhttpclient-2.0.10-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6df4957678eb05a6ccfbbb96a9490345758620b53fe5653052979df94819765b"}, + {file = "geventhttpclient-2.0.10-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508bcc82f7259e316f2060025e7ff899acc8032c645e765bb780083e39060c07"}, + {file = "geventhttpclient-2.0.10-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:961dac20f0f4b8cfa4e2eaafe2d20d74448a5a04239135fa1867a9a1bc3fd986"}, + {file = "geventhttpclient-2.0.10-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:826fa6487ba1c1b7186dcdc300c216fd9b8cf34e307335b4cad1769736988ce9"}, + {file = "geventhttpclient-2.0.10-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b30c9495b43b2f67b6a0102ee3fd497762b6bf972e435e5974fd8d2cb5263235"}, + {file = "geventhttpclient-2.0.10-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e2a9a5e52abc8d3275902c1199ff810264b033e346bcf19da920f9b6de1ea916"}, + {file = "geventhttpclient-2.0.10-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:993b653c7c3d4e0683067b2e324fd749649e87b453205def6a4809dd30498b44"}, + {file = "geventhttpclient-2.0.10-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aa110a66936c37a8bbc2a51515fc0f7e99404e245ef15af8346fa2f6b4f6698"}, + {file = "geventhttpclient-2.0.10-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ce4699fcadc17786cba0b461ff59d0231277155439b274fa12861f93fa764c8"}, + {file = "geventhttpclient-2.0.10-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:23fedc0bf219cc42f9b3d565953a08a429c09642859b86174535977bb281f1c1"}, + {file = "geventhttpclient-2.0.10-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a9e99fa203a43a8393cf01e5a6433e74524c97bf67e39c4f062c8fff27f49360"}, + {file = "geventhttpclient-2.0.10-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dfcfdc3a05e2145fe15f8b9cc7dd8b9fcd545dd42d051be0a2a828b8b3cc4a3"}, + {file = "geventhttpclient-2.0.10-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b56abccfea3fe154f29dbc518737c70d63c1f44da4c956e31e9a5ff65074de19"}, + {file = "geventhttpclient-2.0.10-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9f8d335e7e3579608904e001ba9b055d3f30b184db550c32901b3b86af71b92"}, + {file = "geventhttpclient-2.0.10-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:96f1a63f18eb4a66ea9d6ab211d756afe8b6d5c6e80beff298cd7b65a3d710c7"}, + {file = "geventhttpclient-2.0.10.tar.gz", hash = "sha256:b7c97b26511957a36a894ec54651c08890a69e118b69755f8e74bfc37c63391b"}, +] + +[package.dependencies] +brotli = "*" +certifi = "*" +gevent = ">=0.13" +six = "*" + +[[package]] +name = "glob2" +version = "0.7" +description = "Version of the glob module that can capture patterns and supports recursive wildcards" +optional = false +python-versions = "*" +files = [ + {file = "glob2-0.7.tar.gz", hash = "sha256:85c3dbd07c8aa26d63d7aacee34fa86e9a91a3873bc30bf62ec46e531f92ab8c"}, +] + +[[package]] +name = "greenlet" +version = "3.0.0rc1" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.0rc1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51a59ccc4b96dbaf4a10c241a1869cf78b4529634c491671931ed1604d187271"}, + {file = "greenlet-3.0.0rc1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3960cf0fa3fb078a2a7c4f4f77048df2655d07b8d8b85584e5f24bd37d52a7ad"}, + {file = "greenlet-3.0.0rc1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c05ac3d2d6c8ae0b140a08a9b8f23ed62b64d17729132bcf13040a39399d6f8"}, + {file = "greenlet-3.0.0rc1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24dcc5c6ee38b729243f90b7c3ff1f1a20edd65f7737e5f9e4476000c9be452e"}, + {file = "greenlet-3.0.0rc1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:63c5a3207117d82e3fe0882b9fbc5c10d85acca82e7c55cc4f33fc9d52d412c2"}, + {file = "greenlet-3.0.0rc1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:835ae36a31b8f10c9229604bfdb4e33d4b01d37748f2bcfa139ff741a2ffd346"}, + {file = "greenlet-3.0.0rc1-cp310-cp310-win_amd64.whl", hash = "sha256:28366e7135acdf5d9b907a02d29406cb6165438df1dcc04507f2035631e49274"}, + {file = "greenlet-3.0.0rc1-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:0bcbff28b0c8306e558d8254547f64463d276bb985dfca2a03fc199524b023a3"}, + {file = "greenlet-3.0.0rc1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:608aaa885bf20690f97c2856cc42db43a8bfe24e8fdf3df15a2918ad684daf4b"}, + {file = "greenlet-3.0.0rc1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa2f74909b9713b3a7ae3640daafe5bfd01bffdb8e6954208bec56c2413b16bd"}, + {file = "greenlet-3.0.0rc1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f7d2cfe8d5d4d7839c37c15d3c377e06590c26f0f4f271879fa547e760296dc"}, + {file = "greenlet-3.0.0rc1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afdbe8b0cfbd90c19bd18c8d31d52b231e94dc355a056bcd89fede06e2a54d01"}, + {file = "greenlet-3.0.0rc1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe97d4828310299e5b06fe57b7b5ee550b7c7bc00186e0596578745b36fe94df"}, + {file = "greenlet-3.0.0rc1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:27e49a68231abe36c437a38c59e7374eca4292d4984d1298c7a550a4bf0063c1"}, + {file = "greenlet-3.0.0rc1-cp311-cp311-win_amd64.whl", hash = "sha256:9f44ee49ef97d313309a990f46194b1cac39461919e9e7344397cea2c53ede51"}, + {file = "greenlet-3.0.0rc1-cp311-universal2-macosx_10_9_universal2.whl", hash = "sha256:c60dc7bab6ec007ae6fbc229879d0521ac6965846dbc898587998cb7eed34d6d"}, + {file = "greenlet-3.0.0rc1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a28f1fad22e9fe5c1d45046e51e1fafc6041295823ee5037c527825107704adb"}, + {file = "greenlet-3.0.0rc1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1dfda4239260a8028ce67272a3aa01d80e5996f610a48e24b558e959eba926a1"}, + {file = "greenlet-3.0.0rc1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecb05bb229f6ccad9240ae6d65be6234f4ff6f1366101cdb356c6d956a541277"}, + {file = "greenlet-3.0.0rc1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c33c9b6b50f9e23f7c8c6bd65608bcb1116d2daafa924496397145bce9d13c8"}, + {file = "greenlet-3.0.0rc1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c91e1e821d4dfc834fe09a8cf92228457d74c00c8422d1f7e31f3c0920b3c9c5"}, + {file = "greenlet-3.0.0rc1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a3b6bc53373ace36a567146a133e6ae29468fa86c0b71a1541306780df1d3d23"}, + {file = "greenlet-3.0.0rc1-cp312-cp312-win_amd64.whl", hash = "sha256:5c21f1b14dbb3f6c971adbbed6c9fa5c38f613b05bff183fba0de27ed60954f4"}, + {file = "greenlet-3.0.0rc1-cp312-universal2-macosx_10_9_universal2.whl", hash = "sha256:d75632388c6140f387a5fbabfb606606c6ffb51c3408dc650823b4ee5dad31f8"}, + {file = "greenlet-3.0.0rc1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:65028dc0d9c93f72ecb18d01361b0e9d374e927f582b3b6c3e744ebd2cbc7cd3"}, + {file = "greenlet-3.0.0rc1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17dc2e63b7772f92e3a10d8ba420972e89595b7a191c345e0407f0dfa8c81c96"}, + {file = "greenlet-3.0.0rc1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:825899d8cab64af9207b7f1e694196c9e9bbd8c57dfd198c49b1c6c05e498f26"}, + {file = "greenlet-3.0.0rc1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c850da9ab8e185682126eb8f78a6ef3358b5e65921e20ef7cf6907df6569338"}, + {file = "greenlet-3.0.0rc1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1764017fae4186cfc9158adf19646802a1e25e303dec56615255bd7df7a6e3d0"}, + {file = "greenlet-3.0.0rc1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2629dc3d9da78e113ae9639d37bd4566846e3647338e2becba31d0c0913b85cb"}, + {file = "greenlet-3.0.0rc1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2ac4c92ef3c65442ffb61e9c17689629fd5b65a4ee9e15779aa1898365452d4d"}, + {file = "greenlet-3.0.0rc1-cp37-cp37m-win32.whl", hash = "sha256:13cca2ef67d778d0efd955da0e78c33720306785e30e6524a0cab0e0fc66e83a"}, + {file = "greenlet-3.0.0rc1-cp37-cp37m-win_amd64.whl", hash = "sha256:43aa50a2c85faef8328dda6e14da9d6257324f55b11c382e82e15f5fb50de866"}, + {file = "greenlet-3.0.0rc1-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:b6f4229b7b5cc55f01b5112344cdf934de03085d5467a14fe59c68e92e03c1e4"}, + {file = "greenlet-3.0.0rc1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f31cde4e4d1a202b4519acc3a516e8dc2b634fe9f678d6da3a2a5b43f6f9ff71"}, + {file = "greenlet-3.0.0rc1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d2fe1f9ede486107c542284b23fb16090ebd9d68570d2e7c3bc7622273fa45d"}, + {file = "greenlet-3.0.0rc1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97cac26bdf37f8008c678fd005693c50827fe721e99d339892226a5716e18742"}, + {file = "greenlet-3.0.0rc1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21e23b95c99732402cf50802d4c850d024c6f5ec2615d7d33312862e05fd0815"}, + {file = "greenlet-3.0.0rc1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62395bdc98432964379b55256af01192ba9f5ea336befec8411b07cf25aae83c"}, + {file = "greenlet-3.0.0rc1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ef48cf845f724e363912f086c455c9e5f16c3faf82e1157fc35aaf0ecd836c68"}, + {file = "greenlet-3.0.0rc1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6039832b100c8ab4981b76cf04a97c9cc73017ae7d93a4461e476ae5398d0d0b"}, + {file = "greenlet-3.0.0rc1-cp38-cp38-win32.whl", hash = "sha256:3827ca11ae94bce62924aa47735a60facecbe62abe1f610aea8e2e0665945530"}, + {file = "greenlet-3.0.0rc1-cp38-cp38-win_amd64.whl", hash = "sha256:b55163588a651ceb042fd2a28df61aaf988ea739b5fbd10ff872e9fb4054f532"}, + {file = "greenlet-3.0.0rc1-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:5288d886605833bba2f0b973b1636869b17f2f50c21ddb253e737a5a9891d57d"}, + {file = "greenlet-3.0.0rc1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:79c5fa02d02d7d7be84ae109f853dc2291e49dd1dd8dfb80a52ce96ac1f9a88e"}, + {file = "greenlet-3.0.0rc1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29236a2bea8481d446761f09b74107d0f8b437f0a2c246c93d35def39a011d1"}, + {file = "greenlet-3.0.0rc1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8097f1d9e8840fc0ce5e57464080ae8ba68fe0c848a38cd5bab4be81a046713"}, + {file = "greenlet-3.0.0rc1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716ec1cb900e05e05dd13d602297ff9659fe4c08abcd5ca58884dfdc0c57b51c"}, + {file = "greenlet-3.0.0rc1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1ccc41d1b2b8e08b2fdece8a0e621905caa8b58259e8d5844d1b2097fec8406"}, + {file = "greenlet-3.0.0rc1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ed681062c3f30df74d9ee6999f5568ded089762051a728fd038561a08a6549a6"}, + {file = "greenlet-3.0.0rc1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3dd591f9a8b28caa4165db7632f7e894bc277284181072fb6a1cfc6c6808f46e"}, + {file = "greenlet-3.0.0rc1-cp39-cp39-win32.whl", hash = "sha256:b0cb4449c6c13d515bde4b1d5456b5af07a9c8d1723785a4ed40b69d3e5d15fa"}, + {file = "greenlet-3.0.0rc1-cp39-cp39-win_amd64.whl", hash = "sha256:0ccc9f789507c3f407c9c734b421acb537341f143cc899a27aa325ee64efd22c"}, + {file = "greenlet-3.0.0rc1-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:426bf96ec469d7180d050659c8c7eec094d07c4609d187e7f539f5bd6b3b6252"}, + {file = "greenlet-3.0.0rc1.tar.gz", hash = "sha256:5033ed6e89255d989a2cc32fcacc22c105a19716409c59d45e2af2d1e8bcccb6"}, +] + +[package.extras] +docs = ["Sphinx"] +test = ["objgraph", "psutil"] + +[[package]] +name = "gunicorn" +version = "21.2.0" +description = "WSGI HTTP Server for UNIX" +optional = false +python-versions = ">=3.5" +files = [ + {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, + {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "0.16.3" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.7" +files = [ + {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, + {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, +] + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = "==1.*" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "httpx" +version = "0.23.3" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.7" +files = [ + {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, + {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, +] + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.17.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.8.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "importlib-resources" +version = "5.13.0" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-5.13.0-py3-none-any.whl", hash = "sha256:9f7bd0c97b79972a6cce36a366356d16d5e13b09679c11a58f1014bfdf8e64b2"}, + {file = "importlib_resources-5.13.0.tar.gz", hash = "sha256:82d5c6cca930697dbbd86c93333bb2c2e72861d4789a11c2662b933e5ad2b528"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "injector" +version = "0.21.0" +description = "Injector - Python dependency injection framework, inspired by Guice" +optional = false +python-versions = "*" +files = [ + {file = "injector-0.21.0-py2.py3-none-any.whl", hash = "sha256:3942c5e4c501d390d5ad1b8d7d486ef93b844934afeeb17211acb6c4ca29eeb4"}, + {file = "injector-0.21.0.tar.gz", hash = "sha256:919eb6b9f96f40bf98fda34c79762b217bd1544d9adc35805ff2948e92356c9c"}, +] + +[package.extras] +dev = ["black (==23.3.0)", "build (==0.10.0)", "check-manifest (==0.49)", "click (==8.1.3)", "coverage (==7.2.7)", "exceptiongroup (==1.1.1)", "iniconfig (==2.0.0)", "mypy (==1.4.1)", "mypy-extensions (==1.0.0)", "packaging (==23.1)", "pathspec (==0.11.1)", "platformdirs (==3.8.0)", "pluggy (==1.2.0)", "pyproject-hooks (==1.0.0)", "pytest (==7.4.0)", "pytest-cov (==4.1.0)", "tomli (==2.0.1)", "typing-extensions (==4.7.0)"] + +[[package]] +name = "invoke" +version = "2.2.0" +description = "Pythonic task execution" +optional = false +python-versions = ">=3.6" +files = [ + {file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"}, + {file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"}, +] + +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.7" +files = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + +[[package]] +name = "jsonschema" +version = "3.2.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = "*" +files = [ + {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, + {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, +] + +[package.dependencies] +attrs = ">=17.4.0" +pyrsistent = ">=0.14.0" +setuptools = "*" +six = ">=1.11.0" + +[package.extras] +format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] +format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "webcolors"] + +[[package]] +name = "junit-xml" +version = "1.9" +description = "Creates JUnit XML test result documents that can be read by tools such as Jenkins" +optional = false +python-versions = "*" +files = [ + {file = "junit-xml-1.9.tar.gz", hash = "sha256:de16a051990d4e25a3982b2dd9e89d671067548718866416faec14d9de56db9f"}, + {file = "junit_xml-1.9-py2.py3-none-any.whl", hash = "sha256:ec5ca1a55aefdd76d28fcc0b135251d156c7106fa979686a4b48d62b761b4732"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "kombu" +version = "5.3.2" +description = "Messaging library for Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "kombu-5.3.2-py3-none-any.whl", hash = "sha256:b753c9cfc9b1e976e637a7cbc1a65d446a22e45546cd996ea28f932082b7dc9e"}, + {file = "kombu-5.3.2.tar.gz", hash = "sha256:0ba213f630a2cb2772728aef56ac6883dc3a2f13435e10048f6e97d48506dbbd"}, +] + +[package.dependencies] +amqp = ">=5.1.1,<6.0.0" +vine = "*" + +[package.extras] +azureservicebus = ["azure-servicebus (>=7.10.0)"] +azurestoragequeues = ["azure-identity (>=1.12.0)", "azure-storage-queue (>=12.6.0)"] +confluentkafka = ["confluent-kafka (==2.1.1)"] +consul = ["python-consul2"] +librabbitmq = ["librabbitmq (>=2.0.0)"] +mongodb = ["pymongo (>=4.1.1)"] +msgpack = ["msgpack"] +pyro = ["pyro4"] +qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"] +redis = ["redis (>=4.5.2)"] +slmq = ["softlayer-messaging (>=1.0.3)"] +sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"] +sqs = ["boto3 (>=1.26.143)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] +yaml = ["PyYAML (>=3.10)"] +zookeeper = ["kazoo (>=2.8.0)"] + +[[package]] +name = "lazy-object-proxy" +version = "1.9.0" +description = "A fast and thorough lazy object proxy." +optional = false +python-versions = ">=3.7" +files = [ + {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, +] + +[[package]] +name = "locust" +version = "2.16.1" +description = "Developer friendly load testing framework" +optional = false +python-versions = ">=3.7" +files = [ + {file = "locust-2.16.1-py3-none-any.whl", hash = "sha256:d0f01f9fca6a7d9be987b32185799d9e219fce3b9a3b8250ea03e88003335804"}, + {file = "locust-2.16.1.tar.gz", hash = "sha256:cd54f179b679ae927e9b3ffd2b6a7c89c1078103cfbe96b4dd53c7872774b619"}, +] + +[package.dependencies] +ConfigArgParse = ">=1.0" +flask = ">=2.0.0" +Flask-BasicAuth = ">=0.2.0" +Flask-Cors = ">=3.0.10" +gevent = ">=20.12.1" +geventhttpclient = ">=2.0.2" +msgpack = ">=0.6.2" +psutil = ">=5.6.7" +pywin32 = {version = "*", markers = "platform_system == \"Windows\""} +pyzmq = ">=22.2.1,<23.0.0 || >23.0.0" +requests = ">=2.23.0" +roundrobin = ">=0.0.2" +typing-extensions = ">=3.7.4.3" +Werkzeug = ">=2.0.0" + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "marshmallow" +version = "3.20.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.20.1-py3-none-any.whl", hash = "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"}, + {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "marshmallow-dataclass" +version = "8.5.14" +description = "Python library to convert dataclasses into marshmallow schemas." +optional = false +python-versions = ">=3.6" +files = [ + {file = "marshmallow_dataclass-8.5.14-py3-none-any.whl", hash = "sha256:73859c8963774b5aad6b23ff3dd8456c8463e7c96bf5076051e1656fd4e101d0"}, + {file = "marshmallow_dataclass-8.5.14.tar.gz", hash = "sha256:233c414dd24a6d512bcdb6fb840076bf7a29b7daaaea40634f155a5933377d2e"}, +] + +[package.dependencies] +marshmallow = ">=3.13.0,<4.0" +typing-inspect = ">=0.8.0,<1.0" + +[package.extras] +dev = ["marshmallow (>=3.18.0,<4.0)", "marshmallow-enum", "pre-commit (>=2.17,<3.0)", "pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0)", "sphinx", "typeguard (>=2.4.1,<4.0.0)"] +docs = ["sphinx"] +enum = ["marshmallow (>=3.18.0,<4.0)", "marshmallow-enum"] +lint = ["pre-commit (>=2.17,<3.0)"] +tests = ["pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0)"] +union = ["typeguard (>=2.4.1,<4.0.0)"] + +[[package]] +name = "marshmallow-enum" +version = "1.5.1" +description = "Enum field for Marshmallow" +optional = false +python-versions = "*" +files = [ + {file = "marshmallow-enum-1.5.1.tar.gz", hash = "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58"}, + {file = "marshmallow_enum-1.5.1-py2.py3-none-any.whl", hash = "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072"}, +] + +[package.dependencies] +marshmallow = ">=2.0.0" + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "msgpack" +version = "1.0.5" +description = "MessagePack serializer" +optional = false +python-versions = "*" +files = [ + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, + {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, + {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, + {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, + {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, + {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, + {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, + {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, + {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, + {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, + {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, + {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, + {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, + {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, + {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, + {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, +] + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + +[[package]] +name = "mutmut" +version = "2.4.4" +description = "mutation testing for Python 3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mutmut-2.4.4.tar.gz", hash = "sha256:babd1ac579a817f94d5b944af70f1c5fee5ed117775be0a13c99b96b96841b23"}, +] + +[package.dependencies] +click = "*" +glob2 = "*" +junit-xml = ">=1.8,<2" +parso = "*" +pony = "*" +toml = "*" + +[package.extras] +coverage = ["coverage"] +patch = ["whatthepatch (==0.0.6)"] +pytest = ["pytest", "pytest-cov"] + +[[package]] +name = "mypy" +version = "1.5.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, + {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, + {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, + {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, + {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, + {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, + {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, + {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, + {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, + {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, + {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, + {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, + {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, + {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, + {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, + {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, + {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, + {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "outcome" +version = "1.2.0" +description = "Capture the outcome of Python function calls." +optional = false +python-versions = ">=3.7" +files = [ + {file = "outcome-1.2.0-py2.py3-none-any.whl", hash = "sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"}, + {file = "outcome-1.2.0.tar.gz", hash = "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672"}, +] + +[package.dependencies] +attrs = ">=19.2.0" + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pact-python" +version = "2.0.1" +description = "Tools for creating and verifying consumer driven contracts using the Pact framework." +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "pact-python-2.0.1.tar.gz", hash = "sha256:151d18c22cd997d8f25a3b584189fb9ae17cae9b99d76eca585636a45355ee59"}, +] + +[package.dependencies] +click = ">=8.1.3" +fastapi = ">=0.67.0" +httpx = "0.23.3" +psutil = ">=5.9.4" +requests = ">=2.28.0" +six = ">=1.16.0" +urllib3 = ">=1.26.12" +uvicorn = ">=0.19.0" + +[[package]] +name = "paho-mqtt" +version = "1.6.1" +description = "MQTT version 5.0/3.1.1 client class" +optional = false +python-versions = "*" +files = [ + {file = "paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"}, +] + +[package.extras] +proxy = ["PySocks"] + +[[package]] +name = "paramiko" +version = "3.3.1" +description = "SSH2 protocol library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "paramiko-3.3.1-py3-none-any.whl", hash = "sha256:b7bc5340a43de4287bbe22fe6de728aa2c22468b2a849615498dd944c2f275eb"}, + {file = "paramiko-3.3.1.tar.gz", hash = "sha256:6a3777a961ac86dbef375c5f5b8d50014a1a96d0fd7f054a43bc880134b0ff77"}, +] + +[package.dependencies] +bcrypt = ">=3.2" +cryptography = ">=3.3" +pynacl = ">=1.5" + +[package.extras] +all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +invoke = ["invoke (>=2.0)"] + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "pbr" +version = "5.11.1" +description = "Python Build Reasonableness" +optional = false +python-versions = ">=2.6" +files = [ + {file = "pbr-5.11.1-py2.py3-none-any.whl", hash = "sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b"}, + {file = "pbr-5.11.1.tar.gz", hash = "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"}, +] + +[[package]] +name = "platformdirs" +version = "3.10.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "pluggy" +version = "1.3.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "polling" +version = "0.3.2" +description = "Powerful polling utility with many configurable options" +optional = false +python-versions = "*" +files = [ + {file = "polling-0.3.2.tar.gz", hash = "sha256:3afd62320c99b725c70f379964bf548b302fc7f04d4604e6c315d9012309cc9a"}, +] + +[[package]] +name = "pony" +version = "0.7.16" +description = "Pony Object-Relational Mapper" +optional = false +python-versions = "*" +files = [ + {file = "pony-0.7.16-py3-none-any.whl", hash = "sha256:608a1c1d662983bad2590e650f2bbc1cd6ed48558894ad8f50da4739ff98f614"}, + {file = "pony-0.7.16.tar.gz", hash = "sha256:5f45fc67587f4520c560a57148cc573b097d42f82f5cb200d72c957b5708198d"}, +] + +[[package]] +name = "prometheus-client" +version = "0.17.1" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.6" +files = [ + {file = "prometheus_client-0.17.1-py3-none-any.whl", hash = "sha256:e537f37160f6807b8202a6fc4764cdd19bac5480ddd3e0d463c3002b34462101"}, + {file = "prometheus_client-0.17.1.tar.gz", hash = "sha256:21e674f39831ae3f8acde238afd9a27a37d0d2fb5a28ea094f0ce25d2cbf2091"}, +] + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "prometheus-flask-exporter" +version = "0.22.4" +description = "Prometheus metrics exporter for Flask" +optional = false +python-versions = "*" +files = [ + {file = "prometheus_flask_exporter-0.22.4-py3-none-any.whl", hash = "sha256:e130179c26d5a9b903c12c0d8826127ae491b04b298cae0b92b98677dcf2c06f"}, + {file = "prometheus_flask_exporter-0.22.4.tar.gz", hash = "sha256:959b69f1e740b6736ea53fe5f28dc2ab6229b2ebeade6582b3dbb5d74c7d58e4"}, +] + +[package.dependencies] +flask = "*" +prometheus-client = "*" + +[[package]] +name = "prompt-toolkit" +version = "3.0.39" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, + {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "5.9.5" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, + {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, + {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, + {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, + {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, + {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, + {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, + {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "psycopg2-binary" +version = "2.9.7" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.6" +files = [ + {file = "psycopg2-binary-2.9.7.tar.gz", hash = "sha256:1b918f64a51ffe19cd2e230b3240ba481330ce1d4b7875ae67305bd1d37b041c"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ea5f8ee87f1eddc818fc04649d952c526db4426d26bab16efbe5a0c52b27d6ab"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2993ccb2b7e80844d534e55e0f12534c2871952f78e0da33c35e648bf002bbff"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbbc3c5d15ed76b0d9db7753c0db40899136ecfe97d50cbde918f630c5eb857a"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:692df8763b71d42eb8343f54091368f6f6c9cfc56dc391858cdb3c3ef1e3e584"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dcfd5d37e027ec393a303cc0a216be564b96c80ba532f3d1e0d2b5e5e4b1e6e"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17cc17a70dfb295a240db7f65b6d8153c3d81efb145d76da1e4a096e9c5c0e63"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e5666632ba2b0d9757b38fc17337d84bdf932d38563c5234f5f8c54fd01349c9"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7db7b9b701974c96a88997d458b38ccb110eba8f805d4b4f74944aac48639b42"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c82986635a16fb1fa15cd5436035c88bc65c3d5ced1cfaac7f357ee9e9deddd4"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4fe13712357d802080cfccbf8c6266a3121dc0e27e2144819029095ccf708372"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-win32.whl", hash = "sha256:122641b7fab18ef76b18860dd0c772290566b6fb30cc08e923ad73d17461dc63"}, + {file = "psycopg2_binary-2.9.7-cp310-cp310-win_amd64.whl", hash = "sha256:f8651cf1f144f9ee0fa7d1a1df61a9184ab72962531ca99f077bbdcba3947c58"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ecc15666f16f97709106d87284c136cdc82647e1c3f8392a672616aed3c7151"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fbb1184c7e9d28d67671992970718c05af5f77fc88e26fd7136613c4ece1f89"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a7968fd20bd550431837656872c19575b687f3f6f98120046228e451e4064df"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:094af2e77a1976efd4956a031028774b827029729725e136514aae3cdf49b87b"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26484e913d472ecb6b45937ea55ce29c57c662066d222fb0fbdc1fab457f18c5"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f309b77a7c716e6ed9891b9b42953c3ff7d533dc548c1e33fddc73d2f5e21f9"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d92e139ca388ccfe8c04aacc163756e55ba4c623c6ba13d5d1595ed97523e4b"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2df562bb2e4e00ee064779902d721223cfa9f8f58e7e52318c97d139cf7f012d"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4eec5d36dbcfc076caab61a2114c12094c0b7027d57e9e4387b634e8ab36fd44"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1011eeb0c51e5b9ea1016f0f45fa23aca63966a4c0afcf0340ccabe85a9f65bd"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-win32.whl", hash = "sha256:ded8e15f7550db9e75c60b3d9fcbc7737fea258a0f10032cdb7edc26c2a671fd"}, + {file = "psycopg2_binary-2.9.7-cp311-cp311-win_amd64.whl", hash = "sha256:8a136c8aaf6615653450817a7abe0fc01e4ea720ae41dfb2823eccae4b9062a3"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2dec5a75a3a5d42b120e88e6ed3e3b37b46459202bb8e36cd67591b6e5feebc1"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc10da7e7df3380426521e8c1ed975d22df678639da2ed0ec3244c3dc2ab54c8"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee919b676da28f78f91b464fb3e12238bd7474483352a59c8a16c39dfc59f0c5"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb1c0e682138f9067a58fc3c9a9bf1c83d8e08cfbee380d858e63196466d5c86"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00d8db270afb76f48a499f7bb8fa70297e66da67288471ca873db88382850bf4"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b0c2b466b2f4d89ccc33784c4ebb1627989bd84a39b79092e560e937a11d4ac"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:51d1b42d44f4ffb93188f9b39e6d1c82aa758fdb8d9de65e1ddfe7a7d250d7ad"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:11abdbfc6f7f7dea4a524b5f4117369b0d757725798f1593796be6ece20266cb"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f02f4a72cc3ab2565c6d9720f0343cb840fb2dc01a2e9ecb8bc58ccf95dc5c06"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-win32.whl", hash = "sha256:81d5dd2dd9ab78d31a451e357315f201d976c131ca7d43870a0e8063b6b7a1ec"}, + {file = "psycopg2_binary-2.9.7-cp37-cp37m-win_amd64.whl", hash = "sha256:62cb6de84d7767164a87ca97e22e5e0a134856ebcb08f21b621c6125baf61f16"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:59f7e9109a59dfa31efa022e94a244736ae401526682de504e87bd11ce870c22"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:95a7a747bdc3b010bb6a980f053233e7610276d55f3ca506afff4ad7749ab58a"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c721ee464e45ecf609ff8c0a555018764974114f671815a0a7152aedb9f3343"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4f37bbc6588d402980ffbd1f3338c871368fb4b1cfa091debe13c68bb3852b3"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac83ab05e25354dad798401babaa6daa9577462136ba215694865394840e31f8"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:024eaeb2a08c9a65cd5f94b31ace1ee3bb3f978cd4d079406aef85169ba01f08"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1c31c2606ac500dbd26381145684d87730a2fac9a62ebcfbaa2b119f8d6c19f4"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:42a62ef0e5abb55bf6ffb050eb2b0fcd767261fa3faf943a4267539168807522"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7952807f95c8eba6a8ccb14e00bf170bb700cafcec3924d565235dffc7dc4ae8"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e02bc4f2966475a7393bd0f098e1165d470d3fa816264054359ed4f10f6914ea"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-win32.whl", hash = "sha256:fdca0511458d26cf39b827a663d7d87db6f32b93efc22442a742035728603d5f"}, + {file = "psycopg2_binary-2.9.7-cp38-cp38-win_amd64.whl", hash = "sha256:d0b16e5bb0ab78583f0ed7ab16378a0f8a89a27256bb5560402749dbe8a164d7"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6822c9c63308d650db201ba22fe6648bd6786ca6d14fdaf273b17e15608d0852"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f94cb12150d57ea433e3e02aabd072205648e86f1d5a0a692d60242f7809b15"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5ee89587696d808c9a00876065d725d4ae606f5f7853b961cdbc348b0f7c9a1"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad5ec10b53cbb57e9a2e77b67e4e4368df56b54d6b00cc86398578f1c635f329"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:642df77484b2dcaf87d4237792246d8068653f9e0f5c025e2c692fc56b0dda70"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6a8b575ac45af1eaccbbcdcf710ab984fd50af048fe130672377f78aaff6fc1"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f955aa50d7d5220fcb6e38f69ea126eafecd812d96aeed5d5f3597f33fad43bb"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ad26d4eeaa0d722b25814cce97335ecf1b707630258f14ac4d2ed3d1d8415265"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ced63c054bdaf0298f62681d5dcae3afe60cbae332390bfb1acf0e23dcd25fc8"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2b04da24cbde33292ad34a40db9832a80ad12de26486ffeda883413c9e1b1d5e"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-win32.whl", hash = "sha256:18f12632ab516c47c1ac4841a78fddea6508a8284c7cf0f292cb1a523f2e2379"}, + {file = "psycopg2_binary-2.9.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb3b8d55924a6058a26db69fb1d3e7e32695ff8b491835ba9f479537e14dcf9f"}, +] + +[[package]] +name = "py-cpuinfo" +version = "9.0.0" +description = "Get CPU info with pure Python" +optional = false +python-versions = "*" +files = [ + {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"}, + {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"}, +] + +[[package]] +name = "pybuilder" +version = "0.13.10" +description = "PyBuilder — an easy-to-use build automation tool for Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pybuilder-0.13.10-py3-none-any.whl", hash = "sha256:41a5eb143eddc7fbec95fbb5e8d29e109008513580cfd15fd7332ff816dc2a8b"}, + {file = "pybuilder-0.13.10.tar.gz", hash = "sha256:b4afa8ff8dd84c536511a83be30b781ea67157d0473c816675813829a670b85a"}, +] + +[[package]] +name = "pycodestyle" +version = "2.11.0" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.0-py2.py3-none-any.whl", hash = "sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8"}, + {file = "pycodestyle-2.11.0.tar.gz", hash = "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0"}, +] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pydantic" +version = "2.3.0" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-2.3.0-py3-none-any.whl", hash = "sha256:45b5e446c6dfaad9444819a293b921a40e1db1aa61ea08aede0522529ce90e81"}, + {file = "pydantic-2.3.0.tar.gz", hash = "sha256:1607cc106602284cd4a00882986570472f193fde9cb1259bceeaedb26aa79a6d"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.6.3" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.6.3" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.6.3-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:1a0ddaa723c48af27d19f27f1c73bdc615c73686d763388c8683fe34ae777bad"}, + {file = "pydantic_core-2.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5cfde4fab34dd1e3a3f7f3db38182ab6c95e4ea91cf322242ee0be5c2f7e3d2f"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a7027bfc6b108e17c3383959485087d5942e87eb62bbac69829eae9bc1f7"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84e87c16f582f5c753b7f39a71bd6647255512191be2d2dbf49458c4ef024588"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:522a9c4a4d1924facce7270c84b5134c5cabcb01513213662a2e89cf28c1d309"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaafc776e5edc72b3cad1ccedb5fd869cc5c9a591f1213aa9eba31a781be9ac1"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a750a83b2728299ca12e003d73d1264ad0440f60f4fc9cee54acc489249b728"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e8b374ef41ad5c461efb7a140ce4730661aadf85958b5c6a3e9cf4e040ff4bb"}, + {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b594b64e8568cf09ee5c9501ede37066b9fc41d83d58f55b9952e32141256acd"}, + {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a20c533cb80466c1d42a43a4521669ccad7cf2967830ac62c2c2f9cece63e7e"}, + {file = "pydantic_core-2.6.3-cp310-none-win32.whl", hash = "sha256:04fe5c0a43dec39aedba0ec9579001061d4653a9b53a1366b113aca4a3c05ca7"}, + {file = "pydantic_core-2.6.3-cp310-none-win_amd64.whl", hash = "sha256:6bf7d610ac8f0065a286002a23bcce241ea8248c71988bda538edcc90e0c39ad"}, + {file = "pydantic_core-2.6.3-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:6bcc1ad776fffe25ea5c187a028991c031a00ff92d012ca1cc4714087e575973"}, + {file = "pydantic_core-2.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df14f6332834444b4a37685810216cc8fe1fe91f447332cd56294c984ecbff1c"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b7486d85293f7f0bbc39b34e1d8aa26210b450bbd3d245ec3d732864009819"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a892b5b1871b301ce20d40b037ffbe33d1407a39639c2b05356acfef5536d26a"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:883daa467865e5766931e07eb20f3e8152324f0adf52658f4d302242c12e2c32"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4eb77df2964b64ba190eee00b2312a1fd7a862af8918ec70fc2d6308f76ac64"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce8c84051fa292a5dc54018a40e2a1926fd17980a9422c973e3ebea017aa8da"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22134a4453bd59b7d1e895c455fe277af9d9d9fbbcb9dc3f4a97b8693e7e2c9b"}, + {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:02e1c385095efbd997311d85c6021d32369675c09bcbfff3b69d84e59dc103f6"}, + {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d79f1f2f7ebdb9b741296b69049ff44aedd95976bfee38eb4848820628a99b50"}, + {file = "pydantic_core-2.6.3-cp311-none-win32.whl", hash = "sha256:430ddd965ffd068dd70ef4e4d74f2c489c3a313adc28e829dd7262cc0d2dd1e8"}, + {file = "pydantic_core-2.6.3-cp311-none-win_amd64.whl", hash = "sha256:84f8bb34fe76c68c9d96b77c60cef093f5e660ef8e43a6cbfcd991017d375950"}, + {file = "pydantic_core-2.6.3-cp311-none-win_arm64.whl", hash = "sha256:5a2a3c9ef904dcdadb550eedf3291ec3f229431b0084666e2c2aa8ff99a103a2"}, + {file = "pydantic_core-2.6.3-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:8421cf496e746cf8d6b677502ed9a0d1e4e956586cd8b221e1312e0841c002d5"}, + {file = "pydantic_core-2.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bb128c30cf1df0ab78166ded1ecf876620fb9aac84d2413e8ea1594b588c735d"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a822f630712817b6ecc09ccc378192ef5ff12e2c9bae97eb5968a6cdf3b862"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:240a015102a0c0cc8114f1cba6444499a8a4d0333e178bc504a5c2196defd456"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f90e5e3afb11268628c89f378f7a1ea3f2fe502a28af4192e30a6cdea1e7d5e"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:340e96c08de1069f3d022a85c2a8c63529fd88709468373b418f4cf2c949fb0e"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1480fa4682e8202b560dcdc9eeec1005f62a15742b813c88cdc01d44e85308e5"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f14546403c2a1d11a130b537dda28f07eb6c1805a43dae4617448074fd49c282"}, + {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a87c54e72aa2ef30189dc74427421e074ab4561cf2bf314589f6af5b37f45e6d"}, + {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f93255b3e4d64785554e544c1c76cd32f4a354fa79e2eeca5d16ac2e7fdd57aa"}, + {file = "pydantic_core-2.6.3-cp312-none-win32.whl", hash = "sha256:f70dc00a91311a1aea124e5f64569ea44c011b58433981313202c46bccbec0e1"}, + {file = "pydantic_core-2.6.3-cp312-none-win_amd64.whl", hash = "sha256:23470a23614c701b37252618e7851e595060a96a23016f9a084f3f92f5ed5881"}, + {file = "pydantic_core-2.6.3-cp312-none-win_arm64.whl", hash = "sha256:1ac1750df1b4339b543531ce793b8fd5c16660a95d13aecaab26b44ce11775e9"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:a53e3195f134bde03620d87a7e2b2f2046e0e5a8195e66d0f244d6d5b2f6d31b"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:f2969e8f72c6236c51f91fbb79c33821d12a811e2a94b7aa59c65f8dbdfad34a"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:672174480a85386dd2e681cadd7d951471ad0bb028ed744c895f11f9d51b9ebe"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:002d0ea50e17ed982c2d65b480bd975fc41086a5a2f9c924ef8fc54419d1dea3"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ccc13afee44b9006a73d2046068d4df96dc5b333bf3509d9a06d1b42db6d8bf"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:439a0de139556745ae53f9cc9668c6c2053444af940d3ef3ecad95b079bc9987"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63b7545d489422d417a0cae6f9898618669608750fc5e62156957e609e728a5"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b44c42edc07a50a081672e25dfe6022554b47f91e793066a7b601ca290f71e42"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1c721bfc575d57305dd922e6a40a8fe3f762905851d694245807a351ad255c58"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5e4a2cf8c4543f37f5dc881de6c190de08096c53986381daebb56a355be5dfe6"}, + {file = "pydantic_core-2.6.3-cp37-none-win32.whl", hash = "sha256:d9b4916b21931b08096efed090327f8fe78e09ae8f5ad44e07f5c72a7eedb51b"}, + {file = "pydantic_core-2.6.3-cp37-none-win_amd64.whl", hash = "sha256:a8acc9dedd304da161eb071cc7ff1326aa5b66aadec9622b2574ad3ffe225525"}, + {file = "pydantic_core-2.6.3-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:5e9c068f36b9f396399d43bfb6defd4cc99c36215f6ff33ac8b9c14ba15bdf6b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e61eae9b31799c32c5f9b7be906be3380e699e74b2db26c227c50a5fc7988698"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85463560c67fc65cd86153a4975d0b720b6d7725cf7ee0b2d291288433fc21b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9616567800bdc83ce136e5847d41008a1d602213d024207b0ff6cab6753fe645"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e9b65a55bbabda7fccd3500192a79f6e474d8d36e78d1685496aad5f9dbd92c"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f468d520f47807d1eb5d27648393519655eadc578d5dd862d06873cce04c4d1b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9680dd23055dd874173a3a63a44e7f5a13885a4cfd7e84814be71be24fba83db"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a718d56c4d55efcfc63f680f207c9f19c8376e5a8a67773535e6f7e80e93170"}, + {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8ecbac050856eb6c3046dea655b39216597e373aa8e50e134c0e202f9c47efec"}, + {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:788be9844a6e5c4612b74512a76b2153f1877cd845410d756841f6c3420230eb"}, + {file = "pydantic_core-2.6.3-cp38-none-win32.whl", hash = "sha256:07a1aec07333bf5adebd8264047d3dc518563d92aca6f2f5b36f505132399efc"}, + {file = "pydantic_core-2.6.3-cp38-none-win_amd64.whl", hash = "sha256:621afe25cc2b3c4ba05fff53525156d5100eb35c6e5a7cf31d66cc9e1963e378"}, + {file = "pydantic_core-2.6.3-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:813aab5bfb19c98ae370952b6f7190f1e28e565909bfc219a0909db168783465"}, + {file = "pydantic_core-2.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:50555ba3cb58f9861b7a48c493636b996a617db1a72c18da4d7f16d7b1b9952b"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e20f8baedd7d987bd3f8005c146e6bcbda7cdeefc36fad50c66adb2dd2da48"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0a5d7edb76c1c57b95df719af703e796fc8e796447a1da939f97bfa8a918d60"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f06e21ad0b504658a3a9edd3d8530e8cea5723f6ea5d280e8db8efc625b47e49"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea053cefa008fda40f92aab937fb9f183cf8752e41dbc7bc68917884454c6362"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:171a4718860790f66d6c2eda1d95dd1edf64f864d2e9f9115840840cf5b5713f"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ed7ceca6aba5331ece96c0e328cd52f0dcf942b8895a1ed2642de50800b79d3"}, + {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:acafc4368b289a9f291e204d2c4c75908557d4f36bd3ae937914d4529bf62a76"}, + {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1aa712ba150d5105814e53cb141412217146fedc22621e9acff9236d77d2a5ef"}, + {file = "pydantic_core-2.6.3-cp39-none-win32.whl", hash = "sha256:44b4f937b992394a2e81a5c5ce716f3dcc1237281e81b80c748b2da6dd5cf29a"}, + {file = "pydantic_core-2.6.3-cp39-none-win_amd64.whl", hash = "sha256:9b33bf9658cb29ac1a517c11e865112316d09687d767d7a0e4a63d5c640d1b17"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d7050899026e708fb185e174c63ebc2c4ee7a0c17b0a96ebc50e1f76a231c057"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99faba727727b2e59129c59542284efebbddade4f0ae6a29c8b8d3e1f437beb7"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fa159b902d22b283b680ef52b532b29554ea2a7fc39bf354064751369e9dbd7"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:046af9cfb5384f3684eeb3f58a48698ddab8dd870b4b3f67f825353a14441418"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:930bfe73e665ebce3f0da2c6d64455098aaa67e1a00323c74dc752627879fc67"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:85cc4d105747d2aa3c5cf3e37dac50141bff779545ba59a095f4a96b0a460e70"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b25afe9d5c4f60dcbbe2b277a79be114e2e65a16598db8abee2a2dcde24f162b"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e49ce7dc9f925e1fb010fc3d555250139df61fa6e5a0a95ce356329602c11ea9"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2dd50d6a1aef0426a1d0199190c6c43ec89812b1f409e7fe44cb0fbf6dfa733c"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6595b0d8c8711e8e1dc389d52648b923b809f68ac1c6f0baa525c6440aa0daa"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ef724a059396751aef71e847178d66ad7fc3fc969a1a40c29f5aac1aa5f8784"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3c8945a105f1589ce8a693753b908815e0748f6279959a4530f6742e1994dcb6"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c8c6660089a25d45333cb9db56bb9e347241a6d7509838dbbd1931d0e19dbc7f"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:692b4ff5c4e828a38716cfa92667661a39886e71136c97b7dac26edef18767f7"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f1a5d8f18877474c80b7711d870db0eeef9442691fcdb00adabfc97e183ee0b0"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3796a6152c545339d3b1652183e786df648ecdf7c4f9347e1d30e6750907f5bb"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b962700962f6e7a6bd77e5f37320cabac24b4c0f76afeac05e9f93cf0c620014"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ea80269077003eaa59723bac1d8bacd2cd15ae30456f2890811efc1e3d4413"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c0ebbebae71ed1e385f7dfd9b74c1cff09fed24a6df43d326dd7f12339ec34"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:252851b38bad3bfda47b104ffd077d4f9604a10cb06fe09d020016a25107bf98"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6656a0ae383d8cd7cc94e91de4e526407b3726049ce8d7939049cbfa426518c8"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9140ded382a5b04a1c030b593ed9bf3088243a0a8b7fa9f071a5736498c5483"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d38bbcef58220f9c81e42c255ef0bf99735d8f11edef69ab0b499da77105158a"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c9d469204abcca28926cbc28ce98f28e50e488767b084fb3fbdf21af11d3de26"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48c1ed8b02ffea4d5c9c220eda27af02b8149fe58526359b3c07eb391cb353a2"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b2b1bfed698fa410ab81982f681f5b1996d3d994ae8073286515ac4d165c2e7"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf9d42a71a4d7a7c1f14f629e5c30eac451a6fc81827d2beefd57d014c006c4a"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4292ca56751aebbe63a84bbfc3b5717abb09b14d4b4442cc43fd7c49a1529efd"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7dc2ce039c7290b4ef64334ec7e6ca6494de6eecc81e21cb4f73b9b39991408c"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:615a31b1629e12445c0e9fc8339b41aaa6cc60bd53bf802d5fe3d2c0cda2ae8d"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1fa1f6312fb84e8c281f32b39affe81984ccd484da6e9d65b3d18c202c666149"}, + {file = "pydantic_core-2.6.3.tar.gz", hash = "sha256:1508f37ba9e3ddc0189e6ff4e2228bd2d3c3a4641cbe8c07177162f76ed696c7"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pyflakes" +version = "3.1.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, +] + +[[package]] +name = "pyjwt" +version = "2.8.0" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pykwalify" +version = "1.8.0" +description = "Python lib/cli for JSON/YAML schema validation" +optional = false +python-versions = "*" +files = [ + {file = "pykwalify-1.8.0-py2.py3-none-any.whl", hash = "sha256:731dfa87338cca9f559d1fca2bdea37299116e3139b73f78ca90a543722d6651"}, + {file = "pykwalify-1.8.0.tar.gz", hash = "sha256:796b2ad3ed4cb99b88308b533fb2f559c30fa6efb4fa9fda11347f483d245884"}, +] + +[package.dependencies] +docopt = ">=0.6.2" +python-dateutil = ">=2.8.0" +"ruamel.yaml" = ">=0.16.0" + +[[package]] +name = "pylint" +version = "2.17.5" +description = "python code static checker" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "pylint-2.17.5-py3-none-any.whl", hash = "sha256:73995fb8216d3bed149c8d51bba25b2c52a8251a2c8ac846ec668ce38fab5413"}, + {file = "pylint-2.17.5.tar.gz", hash = "sha256:f7b601cbc06fef7e62a754e2b41294c2aa31f1cb659624b9a85bcba29eaf8252"}, +] + +[package.dependencies] +astroid = ">=2.15.6,<=2.17.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pylint-forbidden-imports" +version = "1.0.0" +description = "Plugin for PyLint that checks if we import from permitted modules" +optional = false +python-versions = "*" +files = [ + {file = "pylint_forbidden_imports-1.0.0-py3-none-any.whl", hash = "sha256:e138a184e7b5ad5bf7077d2a75bdcdac7569c53d7e30b0fb4d4aa3ae9ad458b6"}, + {file = "pylint_forbidden_imports-1.0.0.tar.gz", hash = "sha256:d96d9b52e3dfce6814270b1e42f54b03790078ae906383759146df992df009e9"}, +] + +[package.extras] +dev = ["black", "pylint", "pytest"] + +[[package]] +name = "pymongo" +version = "4.5.0" +description = "Python driver for MongoDB " +optional = false +python-versions = ">=3.7" +files = [ + {file = "pymongo-4.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2d4fa1b01fa7e5b7bb8d312e3542e211b320eb7a4e3d8dc884327039d93cb9e0"}, + {file = "pymongo-4.5.0-cp310-cp310-manylinux1_i686.whl", hash = "sha256:dfcd2b9f510411de615ccedd47462dae80e82fdc09fe9ab0f0f32f11cf57eeb5"}, + {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:3e33064f1984db412b34d51496f4ea785a9cff621c67de58e09fb28da6468a52"}, + {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:33faa786cc907de63f745f587e9879429b46033d7d97a7b84b37f4f8f47b9b32"}, + {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:76a262c41c1a7cbb84a3b11976578a7eb8e788c4b7bfbd15c005fb6ca88e6e50"}, + {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:0f4b125b46fe377984fbaecf2af40ed48b05a4b7676a2ff98999f2016d66b3ec"}, + {file = "pymongo-4.5.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:40d5f6e853ece9bfc01e9129b228df446f49316a4252bb1fbfae5c3c9dedebad"}, + {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:152259f0f1a60f560323aacf463a3642a65a25557683f49cfa08c8f1ecb2395a"}, + {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d64878d1659d2a5bdfd0f0a4d79bafe68653c573681495e424ab40d7b6d6d41"}, + {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1bb3a62395ffe835dbef3a1cbff48fbcce709c78bd1f52e896aee990928432b"}, + {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe48f50fb6348511a3268a893bfd4ab5f263f5ac220782449d03cd05964d1ae7"}, + {file = "pymongo-4.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7591a3beea6a9a4fa3080d27d193b41f631130e3ffa76b88c9ccea123f26dc59"}, + {file = "pymongo-4.5.0-cp310-cp310-win32.whl", hash = "sha256:3a7166d57dc74d679caa7743b8ecf7dc3a1235a9fd178654dddb2b2a627ae229"}, + {file = "pymongo-4.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:21b953da14549ff62ea4ae20889c71564328958cbdf880c64a92a48dda4c9c53"}, + {file = "pymongo-4.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ead4f19d0257a756b21ac2e0e85a37a7245ddec36d3b6008d5bfe416525967dc"}, + {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aff6279e405dc953eeb540ab061e72c03cf38119613fce183a8e94f31be608f"}, + {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4c8d6aa91d3e35016847cbe8d73106e3d1c9a4e6578d38e2c346bfe8edb3ca"}, + {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08819da7864f9b8d4a95729b2bea5fffed08b63d3b9c15b4fea47de655766cf5"}, + {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a253b765b7cbc4209f1d8ee16c7287c4268d3243070bf72d7eec5aa9dfe2a2c2"}, + {file = "pymongo-4.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8027c9063579083746147cf401a7072a9fb6829678076cd3deff28bb0e0f50c8"}, + {file = "pymongo-4.5.0-cp311-cp311-win32.whl", hash = "sha256:9d2346b00af524757576cc2406414562cced1d4349c92166a0ee377a2a483a80"}, + {file = "pymongo-4.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:c3c3525ea8658ee1192cdddf5faf99b07ebe1eeaa61bf32821126df6d1b8072b"}, + {file = "pymongo-4.5.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e5a27f348909235a106a3903fc8e70f573d89b41d723a500869c6569a391cff7"}, + {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9a9a39b7cac81dca79fca8c2a6479ef4c7b1aab95fad7544cc0e8fd943595a2"}, + {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:496c9cbcb4951183d4503a9d7d2c1e3694aab1304262f831d5e1917e60386036"}, + {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23cc6d7eb009c688d70da186b8f362d61d5dd1a2c14a45b890bd1e91e9c451f2"}, + {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fff7d17d30b2cd45afd654b3fc117755c5d84506ed25fda386494e4e0a3416e1"}, + {file = "pymongo-4.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6422b6763b016f2ef2beedded0e546d6aa6ba87910f9244d86e0ac7690f75c96"}, + {file = "pymongo-4.5.0-cp312-cp312-win32.whl", hash = "sha256:77cfff95c1fafd09e940b3fdcb7b65f11442662fad611d0e69b4dd5d17a81c60"}, + {file = "pymongo-4.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:e57d859b972c75ee44ea2ef4758f12821243e99de814030f69a3decb2aa86807"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2b0176f9233a5927084c79ff80b51bd70bfd57e4f3d564f50f80238e797f0c8a"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89b3f2da57a27913d15d2a07d58482f33d0a5b28abd20b8e643ab4d625e36257"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:5caee7bd08c3d36ec54617832b44985bd70c4cbd77c5b313de6f7fce0bb34f93"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:1d40ad09d9f5e719bc6f729cc6b17f31c0b055029719406bd31dde2f72fca7e7"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:076afa0a4a96ca9f77fec0e4a0d241200b3b3a1766f8d7be9a905ecf59a7416b"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:3fa3648e4f1e63ddfe53563ee111079ea3ab35c3b09cd25bc22dadc8269a495f"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:44ee985194c426ddf781fa784f31ffa29cb59657b2dba09250a4245431847d73"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b33c17d9e694b66d7e96977e9e56df19d662031483efe121a24772a44ccbbc7e"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d79ae3bb1ff041c0db56f138c88ce1dfb0209f3546d8d6e7c3f74944ecd2439"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d67225f05f6ea27c8dc57f3fa6397c96d09c42af69d46629f71e82e66d33fa4f"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41771b22dd2822540f79a877c391283d4e6368125999a5ec8beee1ce566f3f82"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a1f26bc1f5ce774d99725773901820dfdfd24e875028da4a0252a5b48dcab5c"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3236cf89d69679eaeb9119c840f5c7eb388a2110b57af6bb6baf01a1da387c18"}, + {file = "pymongo-4.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e1f61355c821e870fb4c17cdb318669cfbcf245a291ce5053b41140870c3e5cc"}, + {file = "pymongo-4.5.0-cp37-cp37m-win32.whl", hash = "sha256:49dce6957598975d8b8d506329d2a3a6c4aee911fa4bbcf5e52ffc6897122950"}, + {file = "pymongo-4.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2227a08b091bd41df5aadee0a5037673f691e2aa000e1968b1ea2342afc6880"}, + {file = "pymongo-4.5.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:435228d3c16a375274ac8ab9c4f9aef40c5e57ddb8296e20ecec9e2461da1017"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8e559116e4128630ad3b7e788e2e5da81cbc2344dee246af44471fa650486a70"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:840eaf30ccac122df260b6005f9dfae4ac287c498ee91e3e90c56781614ca238"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b4fe46b58010115514b842c669a0ed9b6a342017b15905653a5b1724ab80917f"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:a8127437ebc196a6f5e8fddd746bd0903a400dc6b5ae35df672dd1ccc7170a2a"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:2988ef5e6b360b3ff1c6d55c53515499de5f48df31afd9f785d788cdacfbe2d3"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e249190b018d63c901678053b4a43e797ca78b93fb6d17633e3567d4b3ec6107"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:1240edc1a448d4ada4bf1a0e55550b6292420915292408e59159fd8bbdaf8f63"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6d2a56fc2354bb6378f3634402eec788a8f3facf0b3e7d468db5f2b5a78d763"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a0aade2b11dc0c326ccd429ee4134d2d47459ff68d449c6d7e01e74651bd255"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74c0da07c04d0781490b2915e7514b1adb265ef22af039a947988c331ee7455b"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3754acbd7efc7f1b529039fcffc092a15e1cf045e31f22f6c9c5950c613ec4d"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:631492573a1bef2f74f9ac0f9d84e0ce422c251644cd81207530af4aa2ee1980"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e2654d1278384cff75952682d17c718ecc1ad1d6227bb0068fd826ba47d426a5"}, + {file = "pymongo-4.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:168172ef7856e20ec024fe2a746bfa895c88b32720138e6438fd765ebd2b62dd"}, + {file = "pymongo-4.5.0-cp38-cp38-win32.whl", hash = "sha256:b25f7bea162b3dbec6d33c522097ef81df7c19a9300722fa6853f5b495aecb77"}, + {file = "pymongo-4.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:b520aafc6cb148bac09ccf532f52cbd31d83acf4d3e5070d84efe3c019a1adbf"}, + {file = "pymongo-4.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8543253adfaa0b802bfa88386db1009c6ebb7d5684d093ee4edc725007553d21"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:bc5d8c3647b8ae28e4312f1492b8f29deebd31479cd3abaa989090fb1d66db83"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:505f8519c4c782a61d94a17b0da50be639ec462128fbd10ab0a34889218fdee3"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:53f2dda54d76a98b43a410498bd12f6034b2a14b6844ca08513733b2b20b7ad8"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:9c04b9560872fa9a91251030c488e0a73bce9321a70f991f830c72b3f8115d0d"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:58a63a26a1e3dc481dd3a18d6d9f8bd1d576cd1ffe0d479ba7dd38b0aeb20066"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:f076b779aa3dc179aa3ed861be063a313ed4e48ae9f6a8370a9b1295d4502111"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:1b1d7d9aabd8629a31d63cd106d56cca0e6420f38e50563278b520f385c0d86e"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37df8f6006286a5896d1cbc3efb8471ced42e3568d38e6cb00857277047b0d63"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56320c401f544d762fc35766936178fbceb1d9261cd7b24fbfbc8fb6f67aa8a5"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbd705d5f3c3d1ff2d169e418bb789ff07ab3c70d567cc6ba6b72b04b9143481"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80a167081c75cf66b32f30e2f1eaee9365af935a86dbd76788169911bed9b5d5"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c42748ccc451dfcd9cef6c5447a7ab727351fd9747ad431db5ebb18a9b78a4d"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf62da7a4cdec9a4b2981fcbd5e08053edffccf20e845c0b6ec1e77eb7fab61d"}, + {file = "pymongo-4.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b5bbb87fa0511bd313d9a2c90294c88db837667c2bda2ea3fa7a35b59fd93b1f"}, + {file = "pymongo-4.5.0-cp39-cp39-win32.whl", hash = "sha256:465fd5b040206f8bce7016b01d7e7f79d2fcd7c2b8e41791be9632a9df1b4999"}, + {file = "pymongo-4.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:63d8019eee119df308a075b8a7bdb06d4720bf791e2b73d5ab0e7473c115d79c"}, + {file = "pymongo-4.5.0.tar.gz", hash = "sha256:681f252e43b3ef054ca9161635f81b730f4d8cadd28b3f2b2004f5a72f853982"}, +] + +[package.dependencies] +dnspython = ">=1.16.0,<3.0.0" + +[package.extras] +aws = ["pymongo-auth-aws (<2.0.0)"] +encryption = ["certifi", "pymongo[aws]", "pymongocrypt (>=1.6.0,<2.0.0)"] +gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] +ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] +snappy = ["python-snappy"] +zstd = ["zstandard"] + +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + +[[package]] +name = "pyrsistent" +version = "0.19.3" +description = "Persistent/Functional/Immutable data structures" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da"}, + {file = "pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b"}, + {file = "pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7"}, + {file = "pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98"}, + {file = "pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64"}, + {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"}, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, + {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, + {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, +] + +[[package]] +name = "pytest" +version = "7.2.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"}, + {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-benchmark" +version = "4.0.0" +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.7" +files = [ + {file = "pytest-benchmark-4.0.0.tar.gz", hash = "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1"}, + {file = "pytest_benchmark-4.0.0-py3-none-any.whl", hash = "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6"}, +] + +[package.dependencies] +py-cpuinfo = "*" +pytest = ">=3.8" + +[package.extras] +aspect = ["aspectlib"] +elasticsearch = ["elasticsearch"] +histogram = ["pygal", "pygaljs"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-docker-compose" +version = "3.2.1" +description = "Manages Docker containers during your integration tests" +optional = false +python-versions = "*" +files = [ + {file = "pytest-docker-compose-3.2.1.tar.gz", hash = "sha256:bb58f1915688e71232ae86f2c3c8348b10afb8af0f9800f7c68a75cda77c9b48"}, + {file = "pytest_docker_compose-3.2.1-py3-none-any.whl", hash = "sha256:5d9dd78138fb03fa4e8c742cd4104f72c5aa9579e0d31cb6f7a0751db4a6f698"}, +] + +[package.dependencies] +docker-compose = "*" +pytest = ">=3.3" + +[[package]] +name = "pytest-randomly" +version = "3.15.0" +description = "Pytest plugin to randomly order tests and control random.seed." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_randomly-3.15.0-py3-none-any.whl", hash = "sha256:0516f4344b29f4e9cdae8bce31c4aeebf59d0b9ef05927c33354ff3859eeeca6"}, + {file = "pytest_randomly-3.15.0.tar.gz", hash = "sha256:b908529648667ba5e54723088edd6f82252f540cc340d748d1fa985539687047"}, +] + +[package.dependencies] +pytest = "*" + +[[package]] +name = "pytest-rerunfailures" +version = "12.0" +description = "pytest plugin to re-run tests to eliminate flaky failures" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-rerunfailures-12.0.tar.gz", hash = "sha256:784f462fa87fe9bdf781d0027d856b47a4bfe6c12af108f6bd887057a917b48e"}, + {file = "pytest_rerunfailures-12.0-py3-none-any.whl", hash = "sha256:9a1afd04e21b8177faf08a9bbbf44de7a0fe3fc29f8ddbe83b9684bd5f8f92a9"}, +] + +[package.dependencies] +packaging = ">=17.1" +pytest = ">=6.2" + +[[package]] +name = "python-box" +version = "6.1.0" +description = "Advanced Python dictionaries with dot notation access" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-box-6.1.0.tar.gz", hash = "sha256:6e7c243b356cb36e2c0f0e5ed7850969fede6aa812a7f501de7768996c7744d7"}, + {file = "python_box-6.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:c14aa4e72bf30f4d573e62ff8030a86548603a100c3fb534561dbedf4a83f454"}, + {file = "python_box-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab13208b053525ef154a36a4a52873b98a12b18b946edd4c939a4d5080e9a218"}, + {file = "python_box-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:d199cd289b4f4d053770eadd70217c76214aac30b92a23adfb9627fd8558d300"}, + {file = "python_box-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac44b3b85714a4575cc273b5dbd39ef739f938ef6c522d6757704a29e7797d16"}, + {file = "python_box-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f0036f91e13958d2b37d2bc74c1197aa36ffd66755342eb64910f63d8a2990f"}, + {file = "python_box-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:af6bcee7e1abe9251e9a41ca9ab677e1f679f6059321cfbae7e78a3831e0b736"}, + {file = "python_box-6.1.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:11cbe62f0dace8a6e2a10d210a5e87b99ad1a1286865568862516794c923a988"}, + {file = "python_box-6.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa4696b5e09ccf695bf05c16bb5ca1fcc95a141a71a31eb262eee8e2ac07189a"}, + {file = "python_box-6.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3638d3559f19ece7fa29f6a6550bc64696cd3b65e3d4154df07a3d06982252ff"}, + {file = "python_box-6.1.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:53998c3b95e31d1f31e46279ef1d27ac30b137746927260901ee61457f8468a0"}, + {file = "python_box-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:594b0363b187df855ff8649488b1301dddbbeea769629b7caeb584efe779b841"}, + {file = "python_box-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:1d29eafaa287857751e27fbe9a08dd856480f0037fe988b221eba4dac33e5852"}, + {file = "python_box-6.1.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:9dbd92b67c443a97326273c9239fce04d3b6958be815d293f96ab65bc4a9dae7"}, + {file = "python_box-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed6d7fe47d756dc2d9dea448702cea103716580a2efee7c859954929295fe28e"}, + {file = "python_box-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7b73f26e40a7adc57b9e39f5687d026dfa8a336f48aefaf852a223b4e37392ad"}, + {file = "python_box-6.1.0-py3-none-any.whl", hash = "sha256:bdec0a5f5a17b01fc538d292602a077aa8c641fb121e1900dff0591791af80e8"}, +] + +[package.extras] +all = ["msgpack", "ruamel.yaml (>=0.17)", "toml"] +msgpack = ["msgpack"] +pyyaml = ["PyYAML"] +ruamel-yaml = ["ruamel.yaml (>=0.17)"] +toml = ["toml"] +tomli = ["tomli", "tomli-w"] +yaml = ["ruamel.yaml (>=0.17)"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "0.21.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-dotenv-0.21.1.tar.gz", hash = "sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49"}, + {file = "python_dotenv-0.21.1-py3-none-any.whl", hash = "sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pyyaml" +version = "5.3.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = "*" +files = [ + {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, + {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, + {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, + {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, + {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, + {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, + {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, + {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, + {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, + {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, + {file = "PyYAML-5.3.1-cp39-cp39-win32.whl", hash = "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a"}, + {file = "PyYAML-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e"}, + {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, +] + +[[package]] +name = "pyzmq" +version = "25.1.1" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76"}, + {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:985bbb1316192b98f32e25e7b9958088431d853ac63aca1d2c236f40afb17c83"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afea96f64efa98df4da6958bae37f1cbea7932c35878b185e5982821bc883369"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76705c9325d72a81155bb6ab48d4312e0032bf045fb0754889133200f7a0d849"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:77a41c26205d2353a4c94d02be51d6cbdf63c06fbc1295ea57dad7e2d3381b71"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:12720a53e61c3b99d87262294e2b375c915fea93c31fc2336898c26d7aed34cd"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:57459b68e5cd85b0be8184382cefd91959cafe79ae019e6b1ae6e2ba8a12cda7"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:292fe3fc5ad4a75bc8df0dfaee7d0babe8b1f4ceb596437213821f761b4589f9"}, + {file = "pyzmq-25.1.1-cp310-cp310-win32.whl", hash = "sha256:35b5ab8c28978fbbb86ea54958cd89f5176ce747c1fb3d87356cf698048a7790"}, + {file = "pyzmq-25.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:11baebdd5fc5b475d484195e49bae2dc64b94a5208f7c89954e9e354fc609d8f"}, + {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:d20a0ddb3e989e8807d83225a27e5c2eb2260eaa851532086e9e0fa0d5287d83"}, + {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e1c1be77bc5fb77d923850f82e55a928f8638f64a61f00ff18a67c7404faf008"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d89528b4943d27029a2818f847c10c2cecc79fa9590f3cb1860459a5be7933eb"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90f26dc6d5f241ba358bef79be9ce06de58d477ca8485e3291675436d3827cf8"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2b92812bd214018e50b6380ea3ac0c8bb01ac07fcc14c5f86a5bb25e74026e9"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f957ce63d13c28730f7fd6b72333814221c84ca2421298f66e5143f81c9f91f"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:047a640f5c9c6ade7b1cc6680a0e28c9dd5a0825135acbd3569cc96ea00b2505"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7f7e58effd14b641c5e4dec8c7dab02fb67a13df90329e61c869b9cc607ef752"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c2910967e6ab16bf6fbeb1f771c89a7050947221ae12a5b0b60f3bca2ee19bca"}, + {file = "pyzmq-25.1.1-cp311-cp311-win32.whl", hash = "sha256:76c1c8efb3ca3a1818b837aea423ff8a07bbf7aafe9f2f6582b61a0458b1a329"}, + {file = "pyzmq-25.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:44e58a0554b21fc662f2712814a746635ed668d0fbc98b7cb9d74cb798d202e6"}, + {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:e1ffa1c924e8c72778b9ccd386a7067cddf626884fd8277f503c48bb5f51c762"}, + {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1af379b33ef33757224da93e9da62e6471cf4a66d10078cf32bae8127d3d0d4a"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cff084c6933680d1f8b2f3b4ff5bbb88538a4aac00d199ac13f49d0698727ecb"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2400a94f7dd9cb20cd012951a0cbf8249e3d554c63a9c0cdfd5cbb6c01d2dec"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d81f1ddae3858b8299d1da72dd7d19dd36aab654c19671aa8a7e7fb02f6638a"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:255ca2b219f9e5a3a9ef3081512e1358bd4760ce77828e1028b818ff5610b87b"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a882ac0a351288dd18ecae3326b8a49d10c61a68b01419f3a0b9a306190baf69"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:724c292bb26365659fc434e9567b3f1adbdb5e8d640c936ed901f49e03e5d32e"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ca1ed0bb2d850aa8471387882247c68f1e62a4af0ce9c8a1dbe0d2bf69e41fb"}, + {file = "pyzmq-25.1.1-cp312-cp312-win32.whl", hash = "sha256:b3451108ab861040754fa5208bca4a5496c65875710f76789a9ad27c801a0075"}, + {file = "pyzmq-25.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:eadbefd5e92ef8a345f0525b5cfd01cf4e4cc651a2cffb8f23c0dd184975d787"}, + {file = "pyzmq-25.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:db0b2af416ba735c6304c47f75d348f498b92952f5e3e8bff449336d2728795d"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c133e93b405eb0d36fa430c94185bdd13c36204a8635470cccc200723c13bb"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:273bc3959bcbff3f48606b28229b4721716598d76b5aaea2b4a9d0ab454ec062"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cbc8df5c6a88ba5ae385d8930da02201165408dde8d8322072e3e5ddd4f68e22"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:18d43df3f2302d836f2a56f17e5663e398416e9dd74b205b179065e61f1a6edf"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:73461eed88a88c866656e08f89299720a38cb4e9d34ae6bf5df6f71102570f2e"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c850ce7976d19ebe7b9d4b9bb8c9dfc7aac336c0958e2651b88cbd46682123"}, + {file = "pyzmq-25.1.1-cp36-cp36m-win32.whl", hash = "sha256:d2045d6d9439a0078f2a34b57c7b18c4a6aef0bee37f22e4ec9f32456c852c71"}, + {file = "pyzmq-25.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:458dea649f2f02a0b244ae6aef8dc29325a2810aa26b07af8374dc2a9faf57e3"}, + {file = "pyzmq-25.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7cff25c5b315e63b07a36f0c2bab32c58eafbe57d0dce61b614ef4c76058c115"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1579413ae492b05de5a6174574f8c44c2b9b122a42015c5292afa4be2507f28"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d0a409d3b28607cc427aa5c30a6f1e4452cc44e311f843e05edb28ab5e36da0"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21eb4e609a154a57c520e3d5bfa0d97e49b6872ea057b7c85257b11e78068222"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:034239843541ef7a1aee0c7b2cb7f6aafffb005ede965ae9cbd49d5ff4ff73cf"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f8115e303280ba09f3898194791a153862cbf9eef722ad8f7f741987ee2a97c7"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a5d26fe8f32f137e784f768143728438877d69a586ddeaad898558dc971a5ae"}, + {file = "pyzmq-25.1.1-cp37-cp37m-win32.whl", hash = "sha256:f32260e556a983bc5c7ed588d04c942c9a8f9c2e99213fec11a031e316874c7e"}, + {file = "pyzmq-25.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:abf34e43c531bbb510ae7e8f5b2b1f2a8ab93219510e2b287a944432fad135f3"}, + {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:87e34f31ca8f168c56d6fbf99692cc8d3b445abb5bfd08c229ae992d7547a92a"}, + {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c9c6c9b2c2f80747a98f34ef491c4d7b1a8d4853937bb1492774992a120f475d"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5619f3f5a4db5dbb572b095ea3cb5cc035335159d9da950830c9c4db2fbb6995"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a34d2395073ef862b4032343cf0c32a712f3ab49d7ec4f42c9661e0294d106f"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25f0e6b78220aba09815cd1f3a32b9c7cb3e02cb846d1cfc526b6595f6046618"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3669cf8ee3520c2f13b2e0351c41fea919852b220988d2049249db10046a7afb"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2d163a18819277e49911f7461567bda923461c50b19d169a062536fffe7cd9d2"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:df27ffddff4190667d40de7beba4a950b5ce78fe28a7dcc41d6f8a700a80a3c0"}, + {file = "pyzmq-25.1.1-cp38-cp38-win32.whl", hash = "sha256:a382372898a07479bd34bda781008e4a954ed8750f17891e794521c3e21c2e1c"}, + {file = "pyzmq-25.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:52533489f28d62eb1258a965f2aba28a82aa747202c8fa5a1c7a43b5db0e85c1"}, + {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:03b3f49b57264909aacd0741892f2aecf2f51fb053e7d8ac6767f6c700832f45"}, + {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:330f9e188d0d89080cde66dc7470f57d1926ff2fb5576227f14d5be7ab30b9fa"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ca57a5be0389f2a65e6d3bb2962a971688cbdd30b4c0bd188c99e39c234f414"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d457aed310f2670f59cc5b57dcfced452aeeed77f9da2b9763616bd57e4dbaae"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c56d748ea50215abef7030c72b60dd723ed5b5c7e65e7bc2504e77843631c1a6"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f03d3f0d01cb5a018debeb412441996a517b11c5c17ab2001aa0597c6d6882c"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:820c4a08195a681252f46926de10e29b6bbf3e17b30037bd4250d72dd3ddaab8"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17ef5f01d25b67ca8f98120d5fa1d21efe9611604e8eb03a5147360f517dd1e2"}, + {file = "pyzmq-25.1.1-cp39-cp39-win32.whl", hash = "sha256:04ccbed567171579ec2cebb9c8a3e30801723c575601f9a990ab25bcac6b51e2"}, + {file = "pyzmq-25.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:e61f091c3ba0c3578411ef505992d356a812fb200643eab27f4f70eed34a29ef"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ade6d25bb29c4555d718ac6d1443a7386595528c33d6b133b258f65f963bb0f6"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c95ddd4f6e9fca4e9e3afaa4f9df8552f0ba5d1004e89ef0a68e1f1f9807c7"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48e466162a24daf86f6b5ca72444d2bf39a5e58da5f96370078be67c67adc978"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abc719161780932c4e11aaebb203be3d6acc6b38d2f26c0f523b5b59d2fc1996"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ccf825981640b8c34ae54231b7ed00271822ea1c6d8ba1090ebd4943759abf5"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c2f20ce161ebdb0091a10c9ca0372e023ce24980d0e1f810f519da6f79c60800"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:deee9ca4727f53464daf089536e68b13e6104e84a37820a88b0a057b97bba2d2"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa8d6cdc8b8aa19ceb319aaa2b660cdaccc533ec477eeb1309e2a291eaacc43a"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019e59ef5c5256a2c7378f2fb8560fc2a9ff1d315755204295b2eab96b254d0a"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b9af3757495c1ee3b5c4e945c1df7be95562277c6e5bccc20a39aec50f826cd0"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:548d6482dc8aadbe7e79d1b5806585c8120bafa1ef841167bc9090522b610fa6"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:057e824b2aae50accc0f9a0570998adc021b372478a921506fddd6c02e60308e"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2243700cc5548cff20963f0ca92d3e5e436394375ab8a354bbea2b12911b20b0"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79986f3b4af059777111409ee517da24a529bdbd46da578b33f25580adcff728"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:11d58723d44d6ed4dd677c5615b2ffb19d5c426636345567d6af82be4dff8a55"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:49d238cf4b69652257db66d0c623cd3e09b5d2e9576b56bc067a396133a00d4a"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fedbdc753827cf014c01dbbee9c3be17e5a208dcd1bf8641ce2cd29580d1f0d4"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc16ac425cc927d0a57d242589f87ee093884ea4804c05a13834d07c20db203c"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11c1d2aed9079c6b0c9550a7257a836b4a637feb334904610f06d70eb44c56d2"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e8a701123029cc240cea61dd2d16ad57cab4691804143ce80ecd9286b464d180"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61706a6b6c24bdece85ff177fec393545a3191eeda35b07aaa1458a027ad1304"}, + {file = "pyzmq-25.1.1.tar.gz", hash = "sha256:259c22485b71abacdfa8bf79720cd7bcf4b9d128b30ea554f01ae71fdbfdaa23"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "responses" +version = "0.23.3" +description = "A utility library for mocking out the `requests` Python library." +optional = false +python-versions = ">=3.7" +files = [ + {file = "responses-0.23.3-py3-none-any.whl", hash = "sha256:e6fbcf5d82172fecc0aa1860fd91e58cbfd96cee5e96da5b63fa6eb3caa10dd3"}, + {file = "responses-0.23.3.tar.gz", hash = "sha256:205029e1cb334c21cb4ec64fc7599be48b859a0fd381a42443cdd600bfe8b16a"}, +] + +[package.dependencies] +pyyaml = "*" +requests = ">=2.30.0,<3.0" +types-PyYAML = "*" +urllib3 = ">=1.25.10,<3.0" + +[package.extras] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-requests"] + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +optional = false +python-versions = "*" +files = [ + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, +] + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "roundrobin" +version = "0.0.4" +description = "Collection of roundrobin utilities" +optional = false +python-versions = "*" +files = [ + {file = "roundrobin-0.0.4.tar.gz", hash = "sha256:7e9d19a5bd6123d99993fb935fa86d25c88bb2096e493885f61737ed0f5e9abd"}, +] + +[[package]] +name = "ruamel-yaml" +version = "0.17.32" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +optional = false +python-versions = ">=3" +files = [ + {file = "ruamel.yaml-0.17.32-py3-none-any.whl", hash = "sha256:23cd2ed620231677564646b0c6a89d138b6822a0d78656df7abda5879ec4f447"}, + {file = "ruamel.yaml-0.17.32.tar.gz", hash = "sha256:ec939063761914e14542972a5cba6d33c23b0859ab6342f61cf070cfc600efc2"}, +] + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.12\""} + +[package.extras] +docs = ["ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.7" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +optional = false +python-versions = ">=3.5" +files = [ + {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71"}, + {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7"}, + {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80"}, + {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab"}, + {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, + {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, + {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072"}, + {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-macosx_12_0_arm64.whl", hash = "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8"}, + {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3"}, + {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763"}, + {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-win32.whl", hash = "sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e"}, + {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-win_amd64.whl", hash = "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646"}, + {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f"}, + {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0"}, + {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282"}, + {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7"}, + {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-win32.whl", hash = "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93"}, + {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b"}, + {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb"}, + {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307"}, + {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697"}, + {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b"}, + {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-win32.whl", hash = "sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac"}, + {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f"}, + {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9"}, + {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1"}, + {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640"}, + {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b"}, + {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-win32.whl", hash = "sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8"}, + {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5"}, + {file = "ruamel.yaml.clib-0.2.7.tar.gz", hash = "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497"}, +] + +[[package]] +name = "selene" +version = "2.0.0rc4" +description = "User-oriented browser tests in Python (Selenide port)" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "selene-2.0.0rc4-py3-none-any.whl", hash = "sha256:50b007df6629a1a25e8336eaef5d275a416b6ac9adce666acfe30ae7b0bede9c"}, + {file = "selene-2.0.0rc4.tar.gz", hash = "sha256:863b3adda4c2d23cc817e80d79ee7a625ba8ca4786f72f55a1d6236d4c66e25b"}, +] + +[package.dependencies] +future = "*" +selenium = ">=4.4.3" +typing-extensions = ">=4.6.1" +webdriver-manager = "3.8.6" + +[[package]] +name = "selenium" +version = "4.12.0" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "selenium-4.12.0-py3-none-any.whl", hash = "sha256:b2c48b1440db54a0653300d9955f5421390723d53b36ec835e18de8e13bbd401"}, + {file = "selenium-4.12.0.tar.gz", hash = "sha256:95be6aa449a0ab4ac1198bb9de71bbe9170405e04b9752f4b450dc7292a21828"}, +] + +[package.dependencies] +certifi = ">=2021.10.8" +trio = ">=0.17,<1.0" +trio-websocket = ">=0.9,<1.0" +urllib3 = {version = ">=1.26,<3", extras = ["socks"]} + +[[package]] +name = "setuptools" +version = "68.1.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, + {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.20" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759b51346aa388c2e606ee206c0bc6f15a5299f6174d1e10cadbe4530d3c7a98"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1506e988ebeaaf316f183da601f24eedd7452e163010ea63dbe52dc91c7fc70e"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5768c268df78bacbde166b48be788b83dddaa2a5974b8810af422ddfe68a9bc8"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3f0dd6d15b6dc8b28a838a5c48ced7455c3e1fb47b89da9c79cc2090b072a50"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:243d0fb261f80a26774829bc2cee71df3222587ac789b7eaf6555c5b15651eed"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb6d77c31e1bf4268b4d61b549c341cbff9842f8e115ba6904249c20cb78a61"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-win32.whl", hash = "sha256:bcb04441f370cbe6e37c2b8d79e4af9e4789f626c595899d94abebe8b38f9a4d"}, + {file = "SQLAlchemy-2.0.20-cp310-cp310-win_amd64.whl", hash = "sha256:d32b5ffef6c5bcb452723a496bad2d4c52b346240c59b3e6dba279f6dcc06c14"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd81466bdbc82b060c3c110b2937ab65ace41dfa7b18681fdfad2f37f27acdd7"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6fe7d61dc71119e21ddb0094ee994418c12f68c61b3d263ebaae50ea8399c4d4"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4e571af672e1bb710b3cc1a9794b55bce1eae5aed41a608c0401885e3491179"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3364b7066b3c7f4437dd345d47271f1251e0cfb0aba67e785343cdbdb0fff08c"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1be86ccea0c965a1e8cd6ccf6884b924c319fcc85765f16c69f1ae7148eba64b"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1d35d49a972649b5080557c603110620a86aa11db350d7a7cb0f0a3f611948a0"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-win32.whl", hash = "sha256:27d554ef5d12501898d88d255c54eef8414576f34672e02fe96d75908993cf53"}, + {file = "SQLAlchemy-2.0.20-cp311-cp311-win_amd64.whl", hash = "sha256:411e7f140200c02c4b953b3dbd08351c9f9818d2bd591b56d0fa0716bd014f1e"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3c6aceebbc47db04f2d779db03afeaa2c73ea3f8dcd3987eb9efdb987ffa09a3"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d3f175410a6db0ad96b10bfbb0a5530ecd4fcf1e2b5d83d968dd64791f810ed"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8186be85da6587456c9ddc7bf480ebad1a0e6dcbad3967c4821233a4d4df57"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c3d99ba99007dab8233f635c32b5cd24fb1df8d64e17bc7df136cedbea427897"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:76fdfc0f6f5341987474ff48e7a66c3cd2b8a71ddda01fa82fedb180b961630a"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-win32.whl", hash = "sha256:d3793dcf5bc4d74ae1e9db15121250c2da476e1af8e45a1d9a52b1513a393459"}, + {file = "SQLAlchemy-2.0.20-cp37-cp37m-win_amd64.whl", hash = "sha256:79fde625a0a55220d3624e64101ed68a059c1c1f126c74f08a42097a72ff66a9"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:599ccd23a7146e126be1c7632d1d47847fa9f333104d03325c4e15440fc7d927"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1a58052b5a93425f656675673ef1f7e005a3b72e3f2c91b8acca1b27ccadf5f4"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79543f945be7a5ada9943d555cf9b1531cfea49241809dd1183701f94a748624"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63e73da7fb030ae0a46a9ffbeef7e892f5def4baf8064786d040d45c1d6d1dc5"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ce5e81b800a8afc870bb8e0a275d81957e16f8c4b62415a7b386f29a0cb9763"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb0d3e94c2a84215532d9bcf10229476ffd3b08f481c53754113b794afb62d14"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-win32.whl", hash = "sha256:8dd77fd6648b677d7742d2c3cc105a66e2681cc5e5fb247b88c7a7b78351cf74"}, + {file = "SQLAlchemy-2.0.20-cp38-cp38-win_amd64.whl", hash = "sha256:6f8a934f9dfdf762c844e5164046a9cea25fabbc9ec865c023fe7f300f11ca4a"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:26a3399eaf65e9ab2690c07bd5cf898b639e76903e0abad096cd609233ce5208"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4cde2e1096cbb3e62002efdb7050113aa5f01718035ba9f29f9d89c3758e7e4e"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b09ba72e4e6d341bb5bdd3564f1cea6095d4c3632e45dc69375a1dbe4e26ec"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b74eeafaa11372627ce94e4dc88a6751b2b4d263015b3523e2b1e57291102f0"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:77d37c1b4e64c926fa3de23e8244b964aab92963d0f74d98cbc0783a9e04f501"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eefebcc5c555803065128401a1e224a64607259b5eb907021bf9b175f315d2a6"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-win32.whl", hash = "sha256:3423dc2a3b94125094897118b52bdf4d37daf142cbcf26d48af284b763ab90e9"}, + {file = "SQLAlchemy-2.0.20-cp39-cp39-win_amd64.whl", hash = "sha256:5ed61e3463021763b853628aef8bc5d469fe12d95f82c74ef605049d810f3267"}, + {file = "SQLAlchemy-2.0.20-py3-none-any.whl", hash = "sha256:63a368231c53c93e2b67d0c5556a9836fdcd383f7e3026a39602aad775b14acf"}, + {file = "SQLAlchemy-2.0.20.tar.gz", hash = "sha256:ca8a5ff2aa7f3ade6c498aaafce25b1eaeabe4e42b73e25519183e4566a16fc6"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.2.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx-oracle (>=7)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "starlette" +version = "0.27.0" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.7" +files = [ + {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"}, + {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] + +[[package]] +name = "stevedore" +version = "4.1.1" +description = "Manage dynamic plugins for Python applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "stevedore-4.1.1-py3-none-any.whl", hash = "sha256:aa6436565c069b2946fe4ebff07f5041e0c8bf18c7376dd29edf80cf7d524e4e"}, + {file = "stevedore-4.1.1.tar.gz", hash = "sha256:7f8aeb6e3f90f96832c301bff21a7eb5eefbe894c88c506483d355565d88cc1a"}, +] + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[[package]] +name = "tavern" +version = "2.2.0" +description = "Simple testing of RESTful APIs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tavern-2.2.0-py3-none-any.whl", hash = "sha256:9c6621e3b8d4a1a106b8ca743893562ba98ab309354709e441b38b48aa3b2a1c"}, + {file = "tavern-2.2.0.tar.gz", hash = "sha256:50e23c1ee7f14a3d140d525c9c08a2746d2367a04105440a0b5b7edbdab10c30"}, +] + +[package.dependencies] +jmespath = ">=1,<2" +jsonschema = ">=3.2.0,<5" +paho-mqtt = ">=1.3.1,<=1.6.1" +pyjwt = ">=2.4.0,<3" +pykwalify = ">=1.8.0,<2" +pytest = ">=7,<7.3" +python-box = ">=6,<7" +PyYAML = ">=5.3.1,<7" +requests = ">=2.22.0,<3" +stevedore = ">=4,<5" + +[package.extras] +dev = ["Faker", "allure-pytest", "black (==23.3.0)", "bump2version", "colorlog", "coverage[toml]", "docker-compose", "flask (>=2.2.3)", "flit (>=3.2,<4)", "fluent-logger", "itsdangerous", "mypy", "mypy-extensions", "pip-tools", "pre-commit", "py", "pygments", "pytest-cov", "pytest-xdist", "ruff (>=0.0.270)", "tox (>=3,<4)", "tox-travis", "twine", "types-PyYAML", "types-requests", "types-setuptools", "wheel"] + +[[package]] +name = "texttable" +version = "1.6.7" +description = "module to create simple ASCII tables" +optional = false +python-versions = "*" +files = [ + {file = "texttable-1.6.7-py2.py3-none-any.whl", hash = "sha256:b7b68139aa8a6339d2c320ca8b1dc42d13a7831a346b446cb9eb385f0c76310c"}, + {file = "texttable-1.6.7.tar.gz", hash = "sha256:290348fb67f7746931bcdfd55ac7584ecd4e5b0846ab164333f0794b121760f2"}, +] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomlkit" +version = "0.12.1" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"}, + {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"}, +] + +[[package]] +name = "tqdm" +version = "4.66.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, + {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "trio" +version = "0.22.2" +description = "A friendly Python library for async concurrency and I/O" +optional = false +python-versions = ">=3.7" +files = [ + {file = "trio-0.22.2-py3-none-any.whl", hash = "sha256:f43da357620e5872b3d940a2e3589aa251fd3f881b65a608d742e00809b1ec38"}, + {file = "trio-0.22.2.tar.gz", hash = "sha256:3887cf18c8bcc894433420305468388dac76932e9668afa1c49aa3806b6accb3"}, +] + +[package.dependencies] +attrs = ">=20.1.0" +cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""} +idna = "*" +outcome = "*" +sniffio = "*" +sortedcontainers = "*" + +[[package]] +name = "trio-websocket" +version = "0.10.3" +description = "WebSocket library for Trio" +optional = false +python-versions = ">=3.7" +files = [ + {file = "trio-websocket-0.10.3.tar.gz", hash = "sha256:1a748604ad906a7dcab9a43c6eb5681e37de4793ba0847ef0bc9486933ed027b"}, + {file = "trio_websocket-0.10.3-py3-none-any.whl", hash = "sha256:a9937d48e8132ebf833019efde2a52ca82d223a30a7ea3e8d60a7d28f75a4e3a"}, +] + +[package.dependencies] +exceptiongroup = "*" +trio = ">=0.11" +wsproto = ">=0.14" + +[[package]] +name = "types-freezegun" +version = "1.1.10" +description = "Typing stubs for freezegun" +optional = false +python-versions = "*" +files = [ + {file = "types-freezegun-1.1.10.tar.gz", hash = "sha256:cb3a2d2eee950eacbaac0673ab50499823365ceb8c655babb1544a41446409ec"}, + {file = "types_freezegun-1.1.10-py3-none-any.whl", hash = "sha256:fadebe72213e0674036153366205038e1f95c8ca96deb4ef9b71ddc15413543e"}, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.11" +description = "Typing stubs for PyYAML" +optional = false +python-versions = "*" +files = [ + {file = "types-PyYAML-6.0.12.11.tar.gz", hash = "sha256:7d340b19ca28cddfdba438ee638cd4084bde213e501a3978738543e27094775b"}, + {file = "types_PyYAML-6.0.12.11-py3-none-any.whl", hash = "sha256:a461508f3096d1d5810ec5ab95d7eeecb651f3a15b71959999988942063bf01d"}, +] + +[[package]] +name = "types-requests" +version = "2.31.0.2" +description = "Typing stubs for requests" +optional = false +python-versions = "*" +files = [ + {file = "types-requests-2.31.0.2.tar.gz", hash = "sha256:6aa3f7faf0ea52d728bb18c0a0d1522d9bfd8c72d26ff6f61bfc3d06a411cf40"}, + {file = "types_requests-2.31.0.2-py3-none-any.whl", hash = "sha256:56d181c85b5925cbc59f4489a57e72a8b2166f18273fd8ba7b6fe0c0b986f12a"}, +] + +[package.dependencies] +types-urllib3 = "*" + +[[package]] +name = "types-urllib3" +version = "1.26.25.14" +description = "Typing stubs for urllib3" +optional = false +python-versions = "*" +files = [ + {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, + {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "tzdata" +version = "2023.3" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, + {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, +] + +[[package]] +name = "urllib3" +version = "2.0.4" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, +] + +[package.dependencies] +pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uvicorn" +version = "0.23.2" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.8" +files = [ + {file = "uvicorn-0.23.2-py3-none-any.whl", hash = "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53"}, + {file = "uvicorn-0.23.2.tar.gz", hash = "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "vcrpy" +version = "5.1.0" +description = "Automatically mock your HTTP interactions to simplify and speed up testing" +optional = false +python-versions = ">=3.8" +files = [ + {file = "vcrpy-5.1.0-py2.py3-none-any.whl", hash = "sha256:605e7b7a63dcd940db1df3ab2697ca7faf0e835c0852882142bafb19649d599e"}, + {file = "vcrpy-5.1.0.tar.gz", hash = "sha256:bbf1532f2618a04f11bce2a99af3a9647a32c880957293ff91e0a5f187b6b3d2"}, +] + +[package.dependencies] +PyYAML = "*" +wrapt = "*" +yarl = "*" + +[[package]] +name = "vine" +version = "5.0.0" +description = "Promises, promises, promises." +optional = false +python-versions = ">=3.6" +files = [ + {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, + {file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"}, +] + +[[package]] +name = "wcwidth" +version = "0.2.6" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + +[[package]] +name = "webdriver-manager" +version = "3.8.6" +description = "Library provides the way to automatically manage drivers for different browsers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "webdriver_manager-3.8.6-py2.py3-none-any.whl", hash = "sha256:7d3aa8d67bd6c92a5d25f4abd75eea2c6dd24ea6617bff986f502280903a0e2b"}, + {file = "webdriver_manager-3.8.6.tar.gz", hash = "sha256:ee788d389b8f45222a8a62f6f39b579360a1f87be46dad6da89918354af3ce73"}, +] + +[package.dependencies] +packaging = "*" +python-dotenv = "*" +requests = "*" +tqdm = "*" + +[[package]] +name = "websocket-client" +version = "0.59.0" +description = "WebSocket client for Python with low level API options" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "websocket-client-0.59.0.tar.gz", hash = "sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c"}, + {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "werkzeug" +version = "2.3.7" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "werkzeug-2.3.7-py3-none-any.whl", hash = "sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528"}, + {file = "werkzeug-2.3.7.tar.gz", hash = "sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "wiremock" +version = "2.6.1" +description = "Wiremock Admin API Client" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "wiremock-2.6.1-py3-none-any.whl", hash = "sha256:417a803b0bba3ab6240410aedb4de15a32581fb29b1310b05289b4aa1a7c9ffd"}, + {file = "wiremock-2.6.1.tar.gz", hash = "sha256:89b64d763a68a1808274aa4daf802f7ce3f9bff2a18ac6bf8923c997a21d67c1"}, +] + +[package.dependencies] +importlib-resources = ">=5.12.0,<6.0.0" +requests = ">=2.20.0,<3.0.0" + +[package.extras] +testing = ["docker (>=6.1.0,<7.0.0)", "testcontainers (>=3.7.1,<4.0.0)"] + +[[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +] + +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + +[[package]] +name = "wtforms" +version = "3.0.1" +description = "Form validation and rendering for Python web development." +optional = false +python-versions = ">=3.7" +files = [ + {file = "WTForms-3.0.1-py3-none-any.whl", hash = "sha256:837f2f0e0ca79481b92884962b914eba4e72b7a2daaf1f939c890ed0124b834b"}, + {file = "WTForms-3.0.1.tar.gz", hash = "sha256:6b351bbb12dd58af57ffef05bc78425d08d1914e0fd68ee14143b7ade023c5bc"}, +] + +[package.dependencies] +MarkupSafe = "*" + +[package.extras] +email = ["email-validator"] + +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[[package]] +name = "zipp" +version = "3.16.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[[package]] +name = "zope-event" +version = "5.0" +description = "Very basic event publishing system" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26"}, + {file = "zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"}, +] + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx"] +test = ["zope.testrunner"] + +[[package]] +name = "zope-interface" +version = "6.0" +description = "Interfaces for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zope.interface-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f299c020c6679cb389814a3b81200fe55d428012c5e76da7e722491f5d205990"}, + {file = "zope.interface-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee4b43f35f5dc15e1fec55ccb53c130adb1d11e8ad8263d68b1284b66a04190d"}, + {file = "zope.interface-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a158846d0fca0a908c1afb281ddba88744d403f2550dc34405c3691769cdd85"}, + {file = "zope.interface-6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f72f23bab1848edb7472309e9898603141644faec9fd57a823ea6b4d1c4c8995"}, + {file = "zope.interface-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48f4d38cf4b462e75fac78b6f11ad47b06b1c568eb59896db5b6ec1094eb467f"}, + {file = "zope.interface-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:87b690bbee9876163210fd3f500ee59f5803e4a6607d1b1238833b8885ebd410"}, + {file = "zope.interface-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2363e5fd81afb650085c6686f2ee3706975c54f331b426800b53531191fdf28"}, + {file = "zope.interface-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af169ba897692e9cd984a81cb0f02e46dacdc07d6cf9fd5c91e81f8efaf93d52"}, + {file = "zope.interface-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa90bac61c9dc3e1a563e5babb3fd2c0c1c80567e815442ddbe561eadc803b30"}, + {file = "zope.interface-6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89086c9d3490a0f265a3c4b794037a84541ff5ffa28bb9c24cc9f66566968464"}, + {file = "zope.interface-6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:809fe3bf1a91393abc7e92d607976bbb8586512913a79f2bf7d7ec15bd8ea518"}, + {file = "zope.interface-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:0ec9653825f837fbddc4e4b603d90269b501486c11800d7c761eee7ce46d1bbb"}, + {file = "zope.interface-6.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:790c1d9d8f9c92819c31ea660cd43c3d5451df1df61e2e814a6f99cebb292788"}, + {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39b8711578dcfd45fc0140993403b8a81e879ec25d53189f3faa1f006087dca"}, + {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eba51599370c87088d8882ab74f637de0c4f04a6d08a312dce49368ba9ed5c2a"}, + {file = "zope.interface-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee934f023f875ec2cfd2b05a937bd817efcc6c4c3f55c5778cbf78e58362ddc"}, + {file = "zope.interface-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:042f2381118b093714081fd82c98e3b189b68db38ee7d35b63c327c470ef8373"}, + {file = "zope.interface-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dfbbbf0809a3606046a41f8561c3eada9db811be94138f42d9135a5c47e75f6f"}, + {file = "zope.interface-6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:424d23b97fa1542d7be882eae0c0fc3d6827784105264a8169a26ce16db260d8"}, + {file = "zope.interface-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e538f2d4a6ffb6edfb303ce70ae7e88629ac6e5581870e66c306d9ad7b564a58"}, + {file = "zope.interface-6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12175ca6b4db7621aedd7c30aa7cfa0a2d65ea3a0105393e05482d7a2d367446"}, + {file = "zope.interface-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3d7dfd897a588ec27e391edbe3dd320a03684457470415870254e714126b1f"}, + {file = "zope.interface-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b3f543ae9d3408549a9900720f18c0194ac0fe810cecda2a584fd4dca2eb3bb8"}, + {file = "zope.interface-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0583b75f2e70ec93f100931660328965bb9ff65ae54695fb3fa0a1255daa6f2"}, + {file = "zope.interface-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:23ac41d52fd15dd8be77e3257bc51bbb82469cf7f5e9a30b75e903e21439d16c"}, + {file = "zope.interface-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99856d6c98a326abbcc2363827e16bd6044f70f2ef42f453c0bd5440c4ce24e5"}, + {file = "zope.interface-6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1592f68ae11e557b9ff2bc96ac8fc30b187e77c45a3c9cd876e3368c53dc5ba8"}, + {file = "zope.interface-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4407b1435572e3e1610797c9203ad2753666c62883b921318c5403fb7139dec2"}, + {file = "zope.interface-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:5171eb073474a5038321409a630904fd61f12dd1856dd7e9d19cd6fe092cbbc5"}, + {file = "zope.interface-6.0.tar.gz", hash = "sha256:aab584725afd10c710b8f1e6e208dbee2d0ad009f57d674cb9d1b3964037275d"}, +] + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx", "repoze.sphinx.autointerface"] +test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] +testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "4d46b095bf77e79beb7a245df2fb7546e537f59e4cbe886a1a6be68c4e936e20" diff --git a/05-architecture/05-05-performance/pyproject.toml b/05-architecture/05-05-performance/pyproject.toml new file mode 100644 index 0000000..c89b87a --- /dev/null +++ b/05-architecture/05-05-performance/pyproject.toml @@ -0,0 +1,79 @@ +[tool.poetry] +name = "smarttesting-python" +version = "0.0.0" +description = "" +authors = [] + +[tool.poetry.dependencies] +python = "^3.11" +uvicorn = "*" +requests = "*" +factory-boy = "*" +injector = "*" +pydantic = "*" +fastapi = "*" +wiremock = "*" +sqlalchemy = "*" +docker = "*" +psycopg2-binary = "*" +celery = "*" +marshmallow-dataclass = "*" +marshmallow-enum = "*" +kombu = "*" +flask = "*" +flask-injector = "*" +flask-expects-json = "*" +pytest-docker-compose = "*" +pymongo = "*" +typing-inspect = "*" +selenium = "*" +pylint-forbidden-imports = "*" +pact-python = "*" +pytest-benchmark = "*" +locust = "*" +pybuilder = "*" +invoke = "*" +pytest-rerunfailures = "*" +polling = "*" +pytest-cov = "*" +mutmut = "*" +expects = "*" +responses = "*" +vcrpy = "*" +pytest-randomly = "*" +assertpy = "*" +flask-admin = "*" +prometheus-client = "*" +prometheus-flask-exporter = "*" +freezegun = "*" +types-freezegun = "*" +cached-property = "*" +importlib-metadata = "*" +pyyaml = "5.3.1" +selene = {version = "^2.0.0rc4", allow-prereleases = true} +gunicorn = "*" + +[tool.poetry.dev-dependencies] + + +[tool.poetry.group.dev.dependencies] +mypy = "*" +black = "*" +flake8 = "*" +pylint = "*" +isort = "*" +pytest = "*" +tavern = "2.2.0" +types-requests = "*" + +[tool.isort] +profile = "black" + +[tool.pytest.ini_options] +addopts = "-p no:pytest-randomly" +python_files = "test_*.py *_test.py *_tests.py" +markers = "uses_docker wiremock" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/05-architecture/05-05-performance/resources/images/locust_bledy.png b/05-architecture/05-05-performance/resources/images/locust_bledy.png new file mode 100644 index 0000000..03281d9 Binary files /dev/null and b/05-architecture/05-05-performance/resources/images/locust_bledy.png differ diff --git a/05-architecture/05-05-performance/resources/images/locust_moment_krytyczny.png b/05-architecture/05-05-performance/resources/images/locust_moment_krytyczny.png new file mode 100644 index 0000000..acc8924 Binary files /dev/null and b/05-architecture/05-05-performance/resources/images/locust_moment_krytyczny.png differ diff --git a/05-architecture/05-05-performance/resources/images/locust_ustawienia.png b/05-architecture/05-05-performance/resources/images/locust_ustawienia.png new file mode 100644 index 0000000..9bb3e2f Binary files /dev/null and b/05-architecture/05-05-performance/resources/images/locust_ustawienia.png differ diff --git a/05-architecture/05-05-performance/resources/images/locust_wykresy.png b/05-architecture/05-05-performance/resources/images/locust_wykresy.png new file mode 100644 index 0000000..1ebc7f5 Binary files /dev/null and b/05-architecture/05-05-performance/resources/images/locust_wykresy.png differ diff --git a/05-architecture/05-05-performance/smarttesting/__init__.py b/05-architecture/05-05-performance/smarttesting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-05-performance/smarttesting/customer/__init__.py b/05-architecture/05-05-performance/smarttesting/customer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-05-performance/smarttesting/customer/customer.py b/05-architecture/05-05-performance/smarttesting/customer/customer.py new file mode 100644 index 0000000..e3abc18 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/customer/customer.py @@ -0,0 +1,28 @@ +from dataclasses import dataclass +from uuid import UUID + +from smarttesting.customer.person import Person + + +@dataclass +class Customer: + """Klient. Klasa opakowująca osobę do zweryfikowania.""" + + _uuid: UUID + _person: Person + + @property + def uuid(self) -> UUID: + return self._uuid + + @property + def person(self) -> Person: + return self._person + + @property + def is_student(self) -> bool: + return self._person.is_student + + @property + def student(self): + return self._person.student diff --git a/05-architecture/05-05-performance/smarttesting/customer/person.py b/05-architecture/05-05-performance/smarttesting/customer/person.py new file mode 100644 index 0000000..7825d55 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/customer/person.py @@ -0,0 +1,64 @@ +import enum +from dataclasses import dataclass +from datetime import date + + +class Gender(enum.Enum): + MALE = enum.auto() + FEMALE = enum.auto() + + +class Status(enum.Enum): + STUDENT = enum.auto() + NOT_STUDENT = enum.auto() + + +@dataclass +class Person: + """Reprezentuje osobę do zweryfikowania.""" + + _name: str + _surname: str + _date_of_birth: date + _gender: Gender + _national_id_number: str + _status: Status = Status.NOT_STUDENT + + @property + def name(self) -> str: + return self._name + + @property + def surname(self) -> str: + return self._surname + + @property + def date_of_birth(self) -> date: + return self._date_of_birth + + @property + def gender(self) -> Gender: + return self._gender + + @property + def national_id_number(self) -> str: + return self._national_id_number + + @property + def is_student(self) -> bool: + return self._status == Status.STUDENT + + def student(self) -> None: + self._status = Status.STUDENT + + @property + def age(self): + today = date.today() + years_diff = today.year - self._date_of_birth.year + had_birthday_this_year = ( + today.replace(year=self._date_of_birth.year) < self._date_of_birth + ) + if had_birthday_this_year: + years_diff -= 1 + + return years_diff diff --git a/05-architecture/05-05-performance/smarttesting/message.py b/05-architecture/05-05-performance/smarttesting/message.py new file mode 100644 index 0000000..1a51c39 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/message.py @@ -0,0 +1,17 @@ +from typing import ClassVar, Dict, Type + + +class Message: + """Klasa bazowa dla wszystkich wiadomości. + + Potrzebna jest nam 'jedynie' do implementacji własnego kodeka do tasków Celery + by można było przekazywać instancje dataclass jako argumenty wywołania tasków.""" + + __messages_by_name: ClassVar[Dict[str, Type]] = {} + + def __init_subclass__(cls) -> None: + cls.__messages_by_name[cls.__name__] = cls + + @classmethod + def subclass_for_name(cls, name: str) -> Type: + return cls.__messages_by_name[name] diff --git a/05-architecture/05-05-performance/smarttesting/serialization.py b/05-architecture/05-05-performance/smarttesting/serialization.py new file mode 100644 index 0000000..82c3689 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/serialization.py @@ -0,0 +1,54 @@ +import functools +import json +from typing import Any, Hashable, Type, cast + +from marshmallow import Schema +from marshmallow_dataclass import class_schema + +from smarttesting.message import Message + +__all__ = [ + "dataclass_dump", + "dataclass_load", +] + + +def dataclass_dump(data: Any) -> str: + return json.dumps(data, cls=Encoder) + + +def dataclass_load(data: Any) -> Any: + return json.loads(data, object_hook=decoder) + + +class Encoder(json.JSONEncoder): + def default(self, o: Any) -> Any: + try: + schema = _get_schema_for_dataclass(cast(Hashable, type(o))) + except TypeError: + return json.JSONEncoder.default(self, o) + else: + dict_repr = schema.dump(o) + dict_repr["__dataclass_name__"] = type(o).__name__ + return dict_repr + + +def decoder(obj: Any) -> Any: + if "__dataclass_name__" in obj: + dataclass_name = obj.pop("__dataclass_name__") + dataclass = Message.subclass_for_name(dataclass_name) + schema = _get_schema_for_dataclass(cast(Hashable, dataclass)) + return schema.load(obj) + else: + return obj + + +@functools.lru_cache(maxsize=None) +def _get_schema_for_dataclass(dataclass_obj: Type) -> Schema: + """ + Funkcja budująca instancję schemę dla podanej klasy udekorowanej @dataclass. + + Schema jest bezstanowa, więc bezpieczne jest reużywanie obiektów. + """ + schema_cls = class_schema(dataclass_obj) + return schema_cls() diff --git a/05-architecture/05-05-performance/smarttesting/verifier/__init__.py b/05-architecture/05-05-performance/smarttesting/verifier/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/__init__.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/bik_verification_service.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/bik_verification_service.py new file mode 100644 index 0000000..cb7f5be --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/customer/bik_verification_service.py @@ -0,0 +1,36 @@ +import logging +from dataclasses import dataclass + +import requests +from requests.exceptions import RequestException + +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, + Status, +) + +logger = logging.getLogger(__name__) + + +@dataclass +class BIKVerificationService: + """Klient do komunikacji z Biurem Informacji Kredytowej.""" + + _bik_service_url: str + + def verify(self, customer: Customer) -> CustomerVerificationResult: + """ + Weryfikuje czy dana osoba jest oszustem poprzez wysłanie zapytania po HTTP + do BIK. Do wykonania zapytania po HTTP wykorzystujemy bibliotekę `requests`. + """ + try: + id_number = customer.person.national_id_number + response = requests.get(self._bik_service_url + id_number) + + if response.text == Status.VERIFICATION_PASSED.name: + return CustomerVerificationResult.create_passed(customer.uuid) + except RequestException: + logger.exception("HTTP request failed") + + return CustomerVerificationResult.create_failed(customer.uuid) diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/customer_verification.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/customer_verification.py new file mode 100644 index 0000000..af6ed67 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/customer/customer_verification.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass + +from smarttesting.customer.person import Person +from smarttesting.message import Message +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, +) + + +@dataclass(frozen=True) +class CustomerVerification(Message): + """Klasa wiadomości, którą wysyłamy poprzez brokera. + + Reprezentuje osobę i rezultat weryfikacji. + """ + + person: Person + result: CustomerVerificationResult diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/customer_verification_result.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/customer_verification_result.py new file mode 100644 index 0000000..3f3006f --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/customer/customer_verification_result.py @@ -0,0 +1,36 @@ +import enum +from dataclasses import dataclass +from uuid import UUID + + +class Status(enum.Enum): + VERIFICATION_PASSED = "VERIFICATION_PASSED" + VERIFICATION_FAILED = "VERIFICATION_FAILED" + + +@dataclass(frozen=True) +class CustomerVerificationResult: + """Rezultat weryfikacji klienta.""" + + _user_id: UUID + _status: Status + + @property + def user_id(self) -> UUID: + return self._user_id + + @property + def status(self) -> Status: + return self._status + + @property + def passed(self) -> bool: + return self._status == Status.VERIFICATION_PASSED + + @staticmethod + def create_passed(user_id: UUID) -> "CustomerVerificationResult": + return CustomerVerificationResult(user_id, Status.VERIFICATION_PASSED) + + @staticmethod + def create_failed(user_id: UUID) -> "CustomerVerificationResult": + return CustomerVerificationResult(user_id, Status.VERIFICATION_FAILED) diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/customer_verifier.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/customer_verifier.py new file mode 100644 index 0000000..9863d4a --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/customer/customer_verifier.py @@ -0,0 +1,80 @@ +from dataclasses import dataclass +from typing import Set + +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting.verifier.customer.customer_verification import CustomerVerification +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, + Status, +) +from smarttesting.verifier.customer.fraud_alert_task import FraudAlertTask +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto +from smarttesting.verifier.verification import Verification + + +@dataclass +class CustomerVerifier: + """Weryfikacja czy klient jest oszustem czy nie. + + Przechodzi po różnych implementacjach weryfikacji i jeśli, przy którejś okaże się, + że użytkownik jest oszustem, wówczas wysyłamy wiadomość do brokera, z informacją + o oszuście. + """ + + _bik_verification_service: BIKVerificationService + _verifications: Set[Verification] + _repository: VerificationRepository + _fraud_alert_task: FraudAlertTask + + def verify(self, customer: Customer) -> CustomerVerificationResult: + """ + Główna metoda biznesowa. Sprawdza, czy już nie doszło do weryfikacji klienta + i jeśli rezultat zostanie odnaleziony w bazie danych to go zwraca. W innym + przypadku zapisuje wynik weryfikacji w bazie danych. Weryfikacja wówczas + zachodzi poprzez odpytanie BIKu o stan naszego klienta. + """ + prior_result = self._repository.find_by_user_id(customer.uuid) + if prior_result: + return CustomerVerificationResult( + prior_result.uuid, Status(prior_result.status) + ) + else: + return self._verify_customer(customer) + + def _verify_customer(self, customer: Customer) -> CustomerVerificationResult: + result = self._perform_checks(customer) + self._save_verification_result(customer, result) + if not result.passed: + customer_verification = CustomerVerification(customer.person, result) + self._fraud_alert_task.delay(customer_verification=customer_verification) + return result + + def _perform_checks(self, customer: Customer) -> CustomerVerificationResult: + external_result = self._bik_verification_service.verify(customer) + + person = customer.person + verifications_passed = all( + verification.passes(person) for verification in self._verifications + ) + + if external_result.passed and verifications_passed: + return CustomerVerificationResult.create_passed(customer.uuid) + else: + return CustomerVerificationResult.create_failed(customer.uuid) + + def _save_verification_result( + self, customer: Customer, result: CustomerVerificationResult + ) -> None: + self._repository.save( + VerifiedPersonDto( + uuid=customer.uuid, + national_identification_number=customer.person.national_id_number, + status=result.status, + ) + ) diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/fraud_alert_task.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/fraud_alert_task.py new file mode 100644 index 0000000..010ab5c --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/customer/fraud_alert_task.py @@ -0,0 +1,17 @@ +from typing import Protocol + +from smarttesting.verifier.customer.customer_verification import CustomerVerification + + +class TaskResult(Protocol): + """Prosty protokół opokowujący AsyncResult z Celery.""" + + def get(self) -> None: + ... + + +class FraudAlertTask(Protocol): + """Prosty protokół opakowaujący taska celery z danym argumentem.""" + + def delay(self, *, customer_verification: CustomerVerification) -> TaskResult: + ... diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/fraud_detected_handler.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/fraud_detected_handler.py new file mode 100644 index 0000000..5552b68 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/customer/fraud_detected_handler.py @@ -0,0 +1,27 @@ +import logging + +from injector import Inject + +from smarttesting.verifier.customer.customer_verification import CustomerVerification +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto + +logger = logging.getLogger(__name__) + + +def fraud_detected_handler( + repo: Inject[VerificationRepository], *, customer_verification: CustomerVerification +) -> None: + """Implementacja zadania przechodzącego przez brokera RabbitMQ.""" + logger.info("Got customer verification: %s", customer_verification) + person = customer_verification.person + result = customer_verification.result + repo.save( + VerifiedPersonDto( + uuid=result.user_id, + national_identification_number=person.national_id_number, + status=result.status, + ) + ) diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/module.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/module.py new file mode 100644 index 0000000..90041d4 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/customer/module.py @@ -0,0 +1,35 @@ +from typing import Set + +import injector + +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting.verifier.customer.customer_verifier import ( + CustomerVerifier, + FraudAlertTask, +) +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.verification import Verification + + +class CustomerModule(injector.Module): + """Moduł injectora dla modułu klienta.""" + + @injector.provider + def bik_verification_service(self) -> BIKVerificationService: + return BIKVerificationService("http://localhost") + + @injector.provider + def customer_verifier( + self, + bik_verification_service: BIKVerificationService, + verifications: Set[Verification], + repo: VerificationRepository, + fraud_alert_task: FraudAlertTask, + ) -> CustomerVerifier: + return CustomerVerifier( + bik_verification_service, verifications, repo, fraud_alert_task + ) diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/verification/__init__.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/verification/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/verification/age.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/verification/age.py new file mode 100644 index 0000000..2bebb13 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/customer/verification/age.py @@ -0,0 +1,11 @@ +from smarttesting.customer.person import Person +from smarttesting.verifier.verification import Verification + + +class AgeVerification(Verification): + """Weryfikacja wieku osoby wnioskującej o udzielenie pożyczki.""" + + def passes(self, person: Person) -> bool: + if person.age < 0: + raise ValueError("Age cannot be negative!") + return 18 <= person.age <= 99 diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/verification/identification_number.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/verification/identification_number.py new file mode 100644 index 0000000..194cdc8 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/customer/verification/identification_number.py @@ -0,0 +1,45 @@ +from smarttesting.customer.person import Gender, Person +from smarttesting.verifier.verification import Verification + + +class IdentificationNumberVerification(Verification): + """Weryfikacja poprawności numeru PESEL. + + Zobacz: https://pl.wikipedia.org/wiki/PESEL#Cyfra_kontrolna_i_sprawdzanie_poprawno.C5.9Bci_numeru + """ + + def passes(self, person: Person) -> bool: + return ( + self._gender_matches_id_number(person) + and self._starts_with_date_of_birth(person) + and self._weight_is_correct(person) + ) + + def _gender_matches_id_number(self, person: Person) -> bool: + tenth_character = person.national_id_number[9:10] + if int(tenth_character) % 2 == 0: + return person.gender == Gender.FEMALE + else: + return person.gender == Gender.MALE + + def _starts_with_date_of_birth(self, person: Person) -> bool: + dob_formatted = person.date_of_birth.strftime("%y%m%d") + if dob_formatted[0] == "0": + month = person.date_of_birth.month + 20 + dob_formatted = dob_formatted[:2] + str(month) + dob_formatted[4:] + + return dob_formatted == person.national_id_number[:6] + + def _weight_is_correct(self, person: Person) -> bool: + if len(person.national_id_number) != 11: + return False + + weights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3] + weight_sum = sum( + int(person.national_id_number[index]) * weights[index] + for index in range(10) + ) + actual_sum = (10 - weight_sum % 10) % 10 + + check_sum = int(person.national_id_number[10]) + return actual_sum == check_sum diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/verification/module.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/verification/module.py new file mode 100644 index 0000000..c74ff5e --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/customer/verification/module.py @@ -0,0 +1,25 @@ +from typing import Set + +import injector + +from smarttesting.verifier.customer.verification.age import AgeVerification +from smarttesting.verifier.customer.verification.identification_number import ( + IdentificationNumberVerification, +) +from smarttesting.verifier.verification import Verification + + +class VerificationModule(injector.Module): + @injector.provider + def age(self) -> AgeVerification: + return AgeVerification() + + @injector.provider + def id_number(self) -> IdentificationNumberVerification: + return IdentificationNumberVerification() + + @injector.provider + def verifications( + self, age: AgeVerification, id_number: IdentificationNumberVerification + ) -> Set[Verification]: + return {age, id_number} diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/verification_repository.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/verification_repository.py new file mode 100644 index 0000000..17060de --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/customer/verification_repository.py @@ -0,0 +1,15 @@ +import abc +from typing import Optional +from uuid import UUID + +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto + + +class VerificationRepository(abc.ABC): + @abc.abstractmethod + def find_by_user_id(self, user_id: UUID) -> Optional[VerifiedPersonDto]: + pass + + @abc.abstractmethod + def save(self, verified_person: VerifiedPersonDto) -> None: + pass diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/verified_person.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/verified_person.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-05-performance/smarttesting/verifier/customer/verified_person_dto.py b/05-architecture/05-05-performance/smarttesting/verifier/customer/verified_person_dto.py new file mode 100644 index 0000000..936d466 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/customer/verified_person_dto.py @@ -0,0 +1,11 @@ +from dataclasses import dataclass +from uuid import UUID + +from smarttesting.verifier.customer.customer_verification_result import Status + + +@dataclass +class VerifiedPersonDto: + uuid: UUID + national_identification_number: str + status: Status diff --git a/05-architecture/05-05-performance/smarttesting/verifier/verification.py b/05-architecture/05-05-performance/smarttesting/verifier/verification.py new file mode 100644 index 0000000..fb44e92 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting/verifier/verification.py @@ -0,0 +1,9 @@ +import abc + +from smarttesting.customer.person import Person + + +class Verification(abc.ABC): + @abc.abstractmethod + def passes(self, person: Person) -> bool: + pass diff --git a/05-architecture/05-05-performance/smarttesting_api/__init__.py b/05-architecture/05-05-performance/smarttesting_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-05-performance/smarttesting_api/web_app.py b/05-architecture/05-05-performance/smarttesting_api/web_app.py new file mode 100644 index 0000000..988fa4a --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting_api/web_app.py @@ -0,0 +1,64 @@ +"""Prosta, demonstracyjna aplikacja flaskowa.""" +import os +from http import HTTPStatus + +import marshmallow +import marshmallow_dataclass +from flask import Flask, Response, jsonify, request +from flask_expects_json import expects_json +from flask_injector import FlaskInjector +from marshmallow import ValidationError +from marshmallow.fields import Field +from sqlalchemy.orm import Session + +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.customer_verifier import CustomerVerifier +from smarttesting_main.smart_testing_application import assemble + +DEV_MODE = False +if os.environ.get("APP_ENV") == "DEV": + os.environ["FLASK_ENV"] = "development" + DEV_MODE = True + + +app = Flask(__name__) + + +@app.after_request # type: ignore +def close_tx(response: Response, session: Session) -> Response: + session.commit() + session.close() + return response + + +class PrivateFieldsCapableSchema(marshmallow.Schema): + def on_bind_field(self, field_name: str, field_obj: Field) -> None: + # Dataclasses (w przeciwieństwie do attrs) nie aliasują prywatnych pól + # w __init__, więc żeby API nie wymagało podawania pól w formacie "_uuid", + # aliasujemy je usuwając podkreślnik + field_obj.data_key = field_name.lstrip("_") + + +CustomerSchema = marshmallow_dataclass.class_schema( + Customer, base_schema=PrivateFieldsCapableSchema +) + + +@app.route("/fraudCheck", methods=["POST"]) +@expects_json() +def fraud_check(verifier: CustomerVerifier): + try: + customer = CustomerSchema().load(request.json) # type: ignore + except ValidationError as validation_error: + return jsonify(validation_error.messages), HTTPStatus.BAD_REQUEST + + result = verifier.verify(customer=customer) + if result.passed: + return jsonify({"message": "Weryfikacja udana"}) + else: + return jsonify({"message": "Bagiety już jadą"}), HTTPStatus.UNAUTHORIZED + + +APP_ENV = "DEV" if DEV_MODE else "PROD" +app_injector = assemble(env=APP_ENV) # type: ignore +FlaskInjector(app=app, injector=app_injector) diff --git a/05-architecture/05-05-performance/smarttesting_main/__init__.py b/05-architecture/05-05-performance/smarttesting_main/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-05-performance/smarttesting_main/celery_app.py b/05-architecture/05-05-performance/smarttesting_main/celery_app.py new file mode 100644 index 0000000..97892d9 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting_main/celery_app.py @@ -0,0 +1,10 @@ +import os + +from celery import Celery + +from smarttesting_main.smart_testing_application import assemble + +env = os.environ.get("APP_ENV", "DEV") +app_injector = assemble(env=env) # type: ignore + +app = app_injector.get(Celery) diff --git a/05-architecture/05-05-performance/smarttesting_main/celery_module.py b/05-architecture/05-05-performance/smarttesting_main/celery_module.py new file mode 100644 index 0000000..dfb42c3 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting_main/celery_module.py @@ -0,0 +1,43 @@ +from typing import List, NewType, cast + +import injector +from celery import Celery, Task +from kombu.serialization import register + +from smarttesting import serialization +from smarttesting.verifier.customer.fraud_alert_task import FraudAlertTask +from smarttesting.verifier.customer.fraud_detected_handler import fraud_detected_handler +from smarttesting_main.task import task_with_injectables + +CeleryConfig = NewType("CeleryConfig", object) + + +class CeleryModule(injector.Module): + def __init__(self) -> None: + register( + "dataclasses_serialization", + serialization.dataclass_dump, + serialization.dataclass_load, + content_type="application/json", + content_encoding="utf-8", + ) + + @injector.singleton + @injector.provider + def celery(self, container: injector.Injector, config: CeleryConfig) -> Celery: + app = Celery(config_source=config) + app.__injector__ = container + return app + + @injector.singleton + @injector.provider + def fraud_alert_task(self, celery: Celery) -> FraudAlertTask: + # To robimy zamiast dekorowania funkcji zadania @app.task + registered_celery_task = celery.task(typing=False)(fraud_detected_handler) + task_with_injected_dependencies = task_with_injectables(registered_celery_task) + return cast(FraudAlertTask, task_with_injected_dependencies) + + @injector.multiprovider + def tasks(self, fraud_alert_task: FraudAlertTask) -> List[Task]: + # Potrzebne do rejestracji zadań przez Celery + return [fraud_alert_task] # type: ignore diff --git a/05-architecture/05-05-performance/smarttesting_main/dev_modules.py b/05-architecture/05-05-performance/smarttesting_main/dev_modules.py new file mode 100644 index 0000000..b679894 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting_main/dev_modules.py @@ -0,0 +1,24 @@ +import injector + +from smarttesting.customer.customer import Customer +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting.verifier.customer.customer_verification_result import ( + CustomerVerificationResult, +) + + +class DevModule(injector.Module): + """Moduł injectora nadpisujący niektóre klasy na potrzeby lokalnego środowiska.""" + + @injector.provider + def stubbed_bik_verification_service(self) -> BIKVerificationService: + class BIKVerificationServiceStub(BIKVerificationService): + def verify(self, customer: Customer) -> CustomerVerificationResult: + if customer.person.surname == "Fraudeusz": + return CustomerVerificationResult.create_failed(customer.uuid) + else: + return CustomerVerificationResult.create_passed(customer.uuid) + + return BIKVerificationServiceStub(_bik_service_url="") diff --git a/05-architecture/05-05-performance/smarttesting_main/infrastructure/__init__.py b/05-architecture/05-05-performance/smarttesting_main/infrastructure/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-05-performance/smarttesting_main/infrastructure/module.py b/05-architecture/05-05-performance/smarttesting_main/infrastructure/module.py new file mode 100644 index 0000000..355ce1f --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting_main/infrastructure/module.py @@ -0,0 +1,15 @@ +import injector +from sqlalchemy.orm import Session + +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting_main.infrastructure.verification_repo import ( + SqlAlchemyVerificationRepository, +) + + +class InfrastructureModule(injector.Module): + @injector.provider + def repo(self, session: Session) -> VerificationRepository: + return SqlAlchemyVerificationRepository(session) diff --git a/05-architecture/05-05-performance/smarttesting_main/infrastructure/verification_repo.py b/05-architecture/05-05-performance/smarttesting_main/infrastructure/verification_repo.py new file mode 100644 index 0000000..6ce9186 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting_main/infrastructure/verification_repo.py @@ -0,0 +1,40 @@ +from dataclasses import dataclass +from typing import Optional +from uuid import UUID + +from sqlalchemy.orm import Session + +from smarttesting.verifier.customer.customer_verification_result import Status +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) +from smarttesting.verifier.customer.verified_person_dto import VerifiedPersonDto +from smarttesting_main.infrastructure.verified_person import VerifiedPerson + + +@dataclass +class SqlAlchemyVerificationRepository(VerificationRepository): + _session: Session + + def find_by_user_id(self, user_id: UUID) -> Optional[VerifiedPersonDto]: + model = ( + self._session.query(VerifiedPerson) + .filter(VerifiedPerson.uuid == str(user_id)) + .first() + ) + if model: + return VerifiedPersonDto( + uuid=UUID(model.uuid), + national_identification_number=model.national_identification_number, + status=Status(model.status), + ) + return None + + def save(self, verified_person: VerifiedPersonDto) -> None: + model = VerifiedPerson( + uuid=str(verified_person.uuid), + national_identification_number=verified_person.national_identification_number, + status=verified_person.status.value, + ) + self._session.add(model) + self._session.flush() diff --git a/05-architecture/05-05-performance/smarttesting_main/infrastructure/verified_person.py b/05-architecture/05-05-performance/smarttesting_main/infrastructure/verified_person.py new file mode 100644 index 0000000..961cc06 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting_main/infrastructure/verified_person.py @@ -0,0 +1,21 @@ +from typing import Any + +from sqlalchemy import Column, Integer, String +from sqlalchemy.ext.declarative import declarative_base + +Base: Any = declarative_base() + + +class VerifiedPerson(Base): + """ + Model bazodanowy. Wykorzystujemy ORM (mapowanie obiektowo relacyjne) + i obiekt tej klasy mapuje się na tabelę "verified". Każde pole klasy to osobna + kolumna w bazie danych. + """ + + __tablename__ = "verified" + + id = Column(Integer(), primary_key=True) + uuid: str = Column(String(36)) + national_identification_number: str = Column(String(255)) + status: str = Column(String(255)) diff --git a/05-architecture/05-05-performance/smarttesting_main/smart_testing_application.py b/05-architecture/05-05-performance/smarttesting_main/smart_testing_application.py new file mode 100644 index 0000000..6c3c336 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting_main/smart_testing_application.py @@ -0,0 +1,92 @@ +import os +from dataclasses import dataclass +from typing import List, Literal, cast + +import injector +from celery import Task +from sqlalchemy import create_engine +from sqlalchemy.orm import Session, scoped_session, sessionmaker + +from smarttesting.verifier.customer.module import CustomerModule +from smarttesting.verifier.customer.verification.module import VerificationModule +from smarttesting_main.celery_module import CeleryConfig, CeleryModule +from smarttesting_main.dev_modules import DevModule +from smarttesting_main.infrastructure.module import InfrastructureModule +from smarttesting_main.infrastructure.verified_person import Base + +Env = Literal["PROD", "DEV"] + + +def assemble(env: Env = "PROD") -> injector.Injector: + """Zainicjowanie kontenera IoC.""" + extra_modules: List[injector.Module] = [] + + db_dsn = os.environ.get("DB_URL", "sqlite:///dev_database.db") + broker_url = os.environ.get("BROKER_URL", "memory://") + if env == "PROD": + extra_modules += [ProdConfigModule(broker_url)] + elif env == "DEV": + extra_modules += [DevConfigModule(broker_url), DevModule()] + + modules = [ + VerificationModule(), + CustomerModule(), + CeleryModule(), + DbModule(db_dsn), + InfrastructureModule(), + ] + extra_modules + + container = injector.Injector(modules=modules, auto_bind=False) + + # Zarejestruj taski w Celery wywołując ich wstrzyknięcie + container.get(List[Task]) + + return container + + +@dataclass +class ProdConfigModule(injector.Module): + _broker_url: str + + @injector.singleton + @injector.provider + def celery_config(self) -> CeleryConfig: + CeleryConfigProd.broker_url = self._broker_url + return cast(CeleryConfig, CeleryConfigProd) + + +@dataclass +class DevConfigModule(injector.Module): + _broker_url: str + + @injector.singleton + @injector.provider + def celery_config(self) -> CeleryConfig: + CeleryConfigDev.broker_url = self._broker_url + return cast(CeleryConfig, CeleryConfigDev) + + +class CeleryConfigProd: + accept_content = {"json", "dataclasses_serialization"} + task_serializer = "dataclasses_serialization" + result_backend = "rpc://" + result_persistent = False + broker_url = "" # będzie nadpisane + + +class CeleryConfigDev(CeleryConfigProd): + worker_concurrency = 1 + task_always_eager = True + + +class DbModule(injector.Module): + def __init__(self, db_dsn: str) -> None: + self._db_dsn = db_dsn + self._engine = create_engine(self._db_dsn) + self._scoped_session_factory = scoped_session(sessionmaker(bind=self._engine)) + # Stwórz schemat bazy danych. Normalnie odbywa się to przez migracje + Base.metadata.create_all(self._engine) + + @injector.provider + def session(self) -> Session: + return self._scoped_session_factory() diff --git a/05-architecture/05-05-performance/smarttesting_main/task.py b/05-architecture/05-05-performance/smarttesting_main/task.py new file mode 100644 index 0000000..48fa2e6 --- /dev/null +++ b/05-architecture/05-05-performance/smarttesting_main/task.py @@ -0,0 +1,57 @@ +import functools +import inspect + +from celery import Task +from injector import Injector, get_bindings +from sqlalchemy.orm import Session + + +def task_with_injectables(task: Task) -> Task: + """Dekorator na taski, który zapewni wstrzykiwanie zależności i transakcję. + + Od funkcji-taska wymagane jest, by wszystkie zależności do wstrzyknięte były + argumentami pozycyjnymi zaś wszystkie argumenty niewstrzykiwane były zadeklarowane + jako keyword-only. + + Jest to podyktowane uproszczeniami w tej integracji Celery z Injectorem. Przykład: + ``` + @task_with_injectables + @app.task(typing=False) + def add(x: Inject[int], y: Inject[float], *, z: int) -> None: + print(x + y + z) + ``` + + """ + # Safety-checks zanim przejdziemy dalej + + # Sprawdzamy, czy flaga typing jest ustawiona na False. Inaczej Celery protestuje, + # że wyzwalamy taska bez przekazania wszystkich argumentów (także tych, które potem + # będą wstrzyknięte) + assert ( + task.typing is False + ), "Wymagane jest wyłączenie sprawdzania argumentów przy schedulowaniu taska" + # Upewnijmy się, że niewstrzykiwane argumenty są opisane jako keyword-only + args_spec = inspect.getfullargspec(task.run) + bindings = get_bindings(task.run) + assert set(bindings) == set( + args_spec.args + ), "Wstrzykiwane argumenty muszą być pozycyjne" + assert args_spec.varargs is None, "*args nie jest wspierane" + assert args_spec.varkw is None, "**kwargs nie jest wspierane" + + actual_run = task.run + + @functools.wraps(actual_run) + def wrapped_run(*args, **kwargs): + injector: Injector = task.app.__injector__ + session = injector.get(Session) + try: + result = injector.call_with_injection(actual_run, args=args, kwargs=kwargs) + session.commit() + return result + finally: + session.close() + + task.run = wrapped_run + + return task diff --git a/05-architecture/05-05-performance/tests/__init__.py b/05-architecture/05-05-performance/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/05-architecture/05-05-performance/tests/microbenchmarks_tests.py b/05-architecture/05-05-performance/tests/microbenchmarks_tests.py new file mode 100644 index 0000000..afb2b2d --- /dev/null +++ b/05-architecture/05-05-performance/tests/microbenchmarks_tests.py @@ -0,0 +1,54 @@ +"""Wykorzystujemy bibliotekę pytest-benchmark.""" +import uuid +from datetime import date +from unittest.mock import Mock + +import pytest +from pytest_benchmark.fixture import BenchmarkFixture + +from smarttesting.customer.customer import Customer +from smarttesting.customer.person import Gender, Person +from smarttesting.verifier.customer.bik_verification_service import ( + BIKVerificationService, +) +from smarttesting.verifier.customer.customer_verifier import CustomerVerifier +from smarttesting.verifier.customer.fraud_alert_task import FraudAlertTask +from smarttesting.verifier.customer.verification_repository import ( + VerificationRepository, +) + + +@pytest.fixture() +def verifier() -> CustomerVerifier: + return CustomerVerifier( + _bik_verification_service=Mock(spec_set=BIKVerificationService), + _verifications=set(), + _repository=Mock( + spec_set=VerificationRepository, find_by_user_id=Mock(return_value=None) + ), + _fraud_alert_task=Mock(spec_set=FraudAlertTask), + ) + + +@pytest.fixture() +def customer() -> Customer: + return Customer( + _uuid=uuid.uuid4(), + _person=Person( + _name="Fraud", + _surname="Fraudowski", + _date_of_birth=date.today(), + _gender=Gender.MALE, + _national_id_number="1234567890", + ), + ) + + +def test_processing_fraud( # pylint: disable=redefined-outer-name + verifier: CustomerVerifier, customer: Customer, benchmark: BenchmarkFixture +) -> None: + """Test micro-benchmarkowy. + + Sprawdza jak szybki jest algorytm weryfikujący czy klient jest oszustem. + """ + benchmark(verifier.verify, customer) diff --git a/05-architecture/README.adoc b/05-architecture/README.adoc new file mode 100644 index 0000000..7ae6cf6 --- /dev/null +++ b/05-architecture/README.adoc @@ -0,0 +1,3 @@ += Testowanie architektury + +W module 05-02 mamy też klasę testową do modułu 05-01. Chcąc zaoszczędzić tworzenia kolejnego modułu, dorzuciłem ją do bardziej rozbudowanego.