From 53b926ce744692b2b62ea75e1d33983200523eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Buczyn=CC=81ski?= Date: Mon, 25 Sep 2023 17:35:27 +0200 Subject: [PATCH] Week 5 --- .../05-02-packages/05-02-01-core/.pylintrc | 8 + .../05-02-01-core/smarttesting/__init__.py | 0 .../smarttesting/models/__init__.py | 3 + .../smarttesting/models/basket.py | 24 + .../smarttesting/models/person.py | 24 + .../05-02-01-core/smarttesting/tasks.py | 6 + .../05-02-01-core/smarttesting/taxes.py | 9 + .../smarttesting_api/__init__.py | 0 .../05-02-01-core/smarttesting_api/app.py | 10 + .../05-02-01-core/tests/__init__.py | 0 .../tests/lesson1/_01_bad_class_tests.py | 47 + .../05-02-01-core/tests/lesson1/__init__.py | 0 .../tests/lesson2/_02_threadlocals_tests.py | 54 + .../tests/lesson2/_03_architecture_tests.py | 36 + .../05-02-01-core/tests/lesson2/__init__.py | 0 05-architecture/05-02-packages/README.adoc | 30 + .../05-03-cdc/01_generate_stubs_with_scc.sh | 29 + .../05-03-cdc/02_run_stubrunner.sh | 27 + .../05-03-cdc/05-03-01-producer/.pylintrc | 13 + .../contracts/should_return_fraud.yml | 15 + .../contracts/should_return_non_fraud.yml | 15 + .../smarttesting/__init__.py | 0 .../smarttesting/customer/__init__.py | 0 .../smarttesting/customer/customer.py | 28 + .../smarttesting/customer/person.py | 64 + .../05-03-01-producer/smarttesting/message.py | 17 + .../smarttesting/serialization.py | 53 + .../smarttesting/verifier/__init__.py | 0 .../verifier/customer/__init__.py | 0 .../customer/bik_verification_service.py | 35 + .../customer/customer_verification.py | 18 + .../customer/customer_verification_result.py | 36 + .../verifier/customer/customer_verifier.py | 80 + .../verifier/customer/fraud_alert_task.py | 17 + .../customer/fraud_detected_handler.py | 26 + .../smarttesting/verifier/customer/module.py | 34 + .../customer/verification/__init__.py | 0 .../verifier/customer/verification/age.py | 11 + .../verification/identification_number.py | 45 + .../verifier/customer/verification/module.py | 24 + .../customer/verification_repository.py | 15 + .../verifier/customer/verified_person.py | 0 .../verifier/customer/verified_person_dto.py | 11 + .../smarttesting/verifier/verification.py | 9 + .../smarttesting_api/__init__.py | 0 .../smarttesting_api/web_app.py | 63 + .../smarttesting_main/__init__.py | 0 .../smarttesting_main/celery_app.py | 9 + .../smarttesting_main/celery_module.py | 42 + .../smarttesting_main/dev_modules.py | 23 + .../infrastructure/__init__.py | 0 .../infrastructure/module.py | 14 + .../infrastructure/verification_repo.py | 39 + .../infrastructure/verified_person.py | 21 + .../smart_testing_application.py | 91 + .../smarttesting_main/task.py | 57 + .../bootJarMainClassName | 0 .../contracts/ContractVerifierTest.class | Bin 0 -> 2315 bytes .../contracts/ContractTestsBase$Config.class | Bin 0 -> 655 bytes .../test/contracts/ContractTestsBase.class | Bin 0 -> 9620 bytes .../ContractVerifierCamelHelper.class | Bin 0 -> 1748 bytes .../contracts/MessagingAutoConfig$1.class | Bin 0 -> 10285 bytes .../test/contracts/MessagingAutoConfig.class | Bin 0 -> 2452 bytes .../java/contracts/ContractVerifierTest.java | 53 + .../libs/smarttesting-0.0.1.RELEASE-stubs.jar | Bin 0 -> 3503 bytes .../contracts.ContractVerifierTest.html | 352 ++ .../tests/contractTest/css/base-style.css | 179 + .../reports/tests/contractTest/css/style.css | 84 + .../reports/tests/contractTest/index.html | 133 + .../reports/tests/contractTest/js/report.js | 194 + .../contractTest/packages/contracts.html | 103 + .../reports/tests/test/css/base-style.css | 179 + .../reports/tests/test/css/style.css | 84 + .../reports/tests/test/index.html | 92 + .../reports/tests/test/js/report.js | 194 + .../resources/test/META-INF/spring.factories | 3 + .../test/application-messagingtype.yml | 1 + .../contracts/should_return_fraud.yml | 15 + .../contracts/should_return_non_fraud.yml | 15 + .../mappings/should_return_fraud.json | 30 + .../mappings/should_return_non_fraud.json | 30 + .../TEST-contracts.ContractVerifierTest.xml | 250 + .../contractTest/binary/output.bin | Bin 0 -> 40758 bytes .../contractTest/binary/output.bin.idx | Bin 0 -> 102 bytes .../contractTest/binary/results.bin | Bin 0 -> 221 bytes .../test-results/test/binary/output.bin | 0 .../test-results/test/binary/output.bin.idx | Bin 0 -> 1 bytes .../test-results/test/binary/results.bin | 0 .../test/jar_extract_7295776964697644061_tmp | Bin 0 -> 5824 bytes .../tmp/verifierStubsJar/MANIFEST.MF | 2 + .../05-03-01-producer/tests/__init__.py | 0 .../tests/architecture_tests.py | 31 + .../05-03-02-consumer/consumer/__init__.py | 49 + .../consumer/consumer_pact_tests.py | 93 + .../consumer_spring_cloud_contract_tests.py | 64 + .../consumer/some_consumer-fraudverify.json | 67 + .../some_consumer-fraudverify.json | 67 + 05-architecture/05-03-cdc/README.adoc | 45 + 05-architecture/05-04-chaos/README.adoc | 45 + .../05-04-chaos/smarttesting/__init__.py | 0 .../smarttesting/customer/__init__.py | 0 .../smarttesting/customer/customer.py | 28 + .../smarttesting/customer/person.py | 64 + .../05-04-chaos/smarttesting/message.py | 17 + .../05-04-chaos/smarttesting/serialization.py | 53 + .../smarttesting/verifier/__init__.py | 0 .../verifier/customer/__init__.py | 0 .../customer/bik_verification_service.py | 35 + .../customer/customer_verification.py | 18 + .../customer/customer_verification_result.py | 36 + .../verifier/customer/customer_verifier.py | 81 + .../verifier/customer/fraud_alert_task.py | 17 + .../customer/fraud_detected_handler.py | 26 + .../smarttesting/verifier/customer/module.py | 34 + .../customer/verification/__init__.py | 0 .../verifier/customer/verification/age.py | 11 + .../verification/identification_number.py | 45 + .../verifier/customer/verification/module.py | 24 + .../customer/verification_repository.py | 15 + .../verifier/customer/verified_person.py | 0 .../verifier/customer/verified_person_dto.py | 11 + .../smarttesting/verifier/verification.py | 9 + .../05-04-chaos/smarttesting_api/__init__.py | 0 .../05-04-chaos/smarttesting_api/web_app.py | 63 + .../05-04-chaos/smarttesting_main/__init__.py | 0 .../smarttesting_main/celery_app.py | 9 + .../smarttesting_main/celery_module.py | 42 + .../smarttesting_main/dev_modules.py | 23 + .../infrastructure/__init__.py | 0 .../infrastructure/module.py | 14 + .../infrastructure/verification_repo.py | 39 + .../infrastructure/verified_person.py | 21 + .../smart_testing_application.py | 91 + .../05-04-chaos/smarttesting_main/task.py | 57 + 05-architecture/05-04-chaos/tests/__init__.py | 0 .../05-04-chaos/tests/chaos_tests.py | 97 + 05-architecture/05-05-performance/Dockerfile | 24 + 05-architecture/05-05-performance/README.adoc | 77 + .../05-05-performance/docker-compose.yml | 30 + .../05-05-performance/locustfile.py | 38 + 05-architecture/05-05-performance/poetry.lock | 4031 +++++++++++++++++ .../05-05-performance/pyproject.toml | 79 + .../resources/images/locust_bledy.png | Bin 0 -> 26124 bytes .../images/locust_moment_krytyczny.png | Bin 0 -> 34281 bytes .../resources/images/locust_ustawienia.png | Bin 0 -> 38201 bytes .../resources/images/locust_wykresy.png | Bin 0 -> 143537 bytes .../smarttesting/__init__.py | 0 .../smarttesting/customer/__init__.py | 0 .../smarttesting/customer/customer.py | 28 + .../smarttesting/customer/person.py | 64 + .../05-05-performance/smarttesting/message.py | 17 + .../smarttesting/serialization.py | 54 + .../smarttesting/verifier/__init__.py | 0 .../verifier/customer/__init__.py | 0 .../customer/bik_verification_service.py | 36 + .../customer/customer_verification.py | 18 + .../customer/customer_verification_result.py | 36 + .../verifier/customer/customer_verifier.py | 80 + .../verifier/customer/fraud_alert_task.py | 17 + .../customer/fraud_detected_handler.py | 27 + .../smarttesting/verifier/customer/module.py | 35 + .../customer/verification/__init__.py | 0 .../verifier/customer/verification/age.py | 11 + .../verification/identification_number.py | 45 + .../verifier/customer/verification/module.py | 25 + .../customer/verification_repository.py | 15 + .../verifier/customer/verified_person.py | 0 .../verifier/customer/verified_person_dto.py | 11 + .../smarttesting/verifier/verification.py | 9 + .../smarttesting_api/__init__.py | 0 .../smarttesting_api/web_app.py | 64 + .../smarttesting_main/__init__.py | 0 .../smarttesting_main/celery_app.py | 10 + .../smarttesting_main/celery_module.py | 43 + .../smarttesting_main/dev_modules.py | 24 + .../infrastructure/__init__.py | 0 .../infrastructure/module.py | 15 + .../infrastructure/verification_repo.py | 40 + .../infrastructure/verified_person.py | 21 + .../smart_testing_application.py | 92 + .../smarttesting_main/task.py | 57 + .../05-05-performance/tests/__init__.py | 0 .../tests/microbenchmarks_tests.py | 54 + 05-architecture/README.adoc | 3 + 184 files changed, 9896 insertions(+) create mode 100644 05-architecture/05-02-packages/05-02-01-core/.pylintrc create mode 100644 05-architecture/05-02-packages/05-02-01-core/smarttesting/__init__.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/smarttesting/models/__init__.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/smarttesting/models/basket.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/smarttesting/models/person.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/smarttesting/tasks.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/smarttesting/taxes.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/smarttesting_api/__init__.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/smarttesting_api/app.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/tests/__init__.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/tests/lesson1/_01_bad_class_tests.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/tests/lesson1/__init__.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/tests/lesson2/_02_threadlocals_tests.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/tests/lesson2/_03_architecture_tests.py create mode 100644 05-architecture/05-02-packages/05-02-01-core/tests/lesson2/__init__.py create mode 100644 05-architecture/05-02-packages/README.adoc create mode 100755 05-architecture/05-03-cdc/01_generate_stubs_with_scc.sh create mode 100755 05-architecture/05-03-cdc/02_run_stubrunner.sh create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/.pylintrc create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/contracts/should_return_fraud.yml create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/contracts/should_return_non_fraud.yml create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/__init__.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/customer/__init__.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/customer/customer.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/customer/person.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/message.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/serialization.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/__init__.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/__init__.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/bik_verification_service.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/customer_verification.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/customer_verification_result.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/customer_verifier.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/fraud_alert_task.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/fraud_detected_handler.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/module.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/__init__.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/age.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/identification_number.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification/module.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verification_repository.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verified_person.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/customer/verified_person_dto.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting/verifier/verification.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting_api/__init__.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting_api/web_app.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/__init__.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/celery_app.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/celery_module.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/dev_modules.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/__init__.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/module.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/verification_repo.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/infrastructure/verified_person.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/smart_testing_application.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/smarttesting_main/task.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/bootJarMainClassName create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/contractTest/contracts/ContractVerifierTest.class create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/ContractTestsBase$Config.class create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/ContractTestsBase.class create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/ContractVerifierCamelHelper.class create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/MessagingAutoConfig$1.class create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/classes/java/test/contracts/MessagingAutoConfig.class create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/generated-test-sources/contractTest/java/contracts/ContractVerifierTest.java create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/libs/smarttesting-0.0.1.RELEASE-stubs.jar create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/classes/contracts.ContractVerifierTest.html create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/css/base-style.css create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/css/style.css create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/index.html create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/js/report.js create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/contractTest/packages/contracts.html create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/css/base-style.css create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/css/style.css create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/index.html create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/reports/tests/test/js/report.js create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/resources/test/META-INF/spring.factories create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/resources/test/application-messagingtype.yml create mode 100644 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 create mode 100644 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 create mode 100644 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 create mode 100644 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 create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/TEST-contracts.ContractVerifierTest.xml create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/binary/output.bin create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/binary/output.bin.idx create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/contractTest/binary/results.bin create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/test/binary/output.bin create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/test/binary/output.bin.idx create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/test-results/test/binary/results.bin create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/tmp/test/jar_extract_7295776964697644061_tmp create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/spring-cloud-contract-output/tmp/verifierStubsJar/MANIFEST.MF create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/tests/__init__.py create mode 100644 05-architecture/05-03-cdc/05-03-01-producer/tests/architecture_tests.py create mode 100644 05-architecture/05-03-cdc/05-03-02-consumer/consumer/__init__.py create mode 100644 05-architecture/05-03-cdc/05-03-02-consumer/consumer/consumer_pact_tests.py create mode 100644 05-architecture/05-03-cdc/05-03-02-consumer/consumer/consumer_spring_cloud_contract_tests.py create mode 100644 05-architecture/05-03-cdc/05-03-02-consumer/consumer/some_consumer-fraudverify.json create mode 100644 05-architecture/05-03-cdc/05-03-02-consumer/some_consumer-fraudverify.json create mode 100644 05-architecture/05-03-cdc/README.adoc create mode 100644 05-architecture/05-04-chaos/README.adoc create mode 100644 05-architecture/05-04-chaos/smarttesting/__init__.py create mode 100644 05-architecture/05-04-chaos/smarttesting/customer/__init__.py create mode 100644 05-architecture/05-04-chaos/smarttesting/customer/customer.py create mode 100644 05-architecture/05-04-chaos/smarttesting/customer/person.py create mode 100644 05-architecture/05-04-chaos/smarttesting/message.py create mode 100644 05-architecture/05-04-chaos/smarttesting/serialization.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/__init__.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/__init__.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/bik_verification_service.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verification.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verification_result.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/customer_verifier.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/fraud_alert_task.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/fraud_detected_handler.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/module.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/__init__.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/age.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/identification_number.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/verification/module.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/verification_repository.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/verified_person.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/customer/verified_person_dto.py create mode 100644 05-architecture/05-04-chaos/smarttesting/verifier/verification.py create mode 100644 05-architecture/05-04-chaos/smarttesting_api/__init__.py create mode 100644 05-architecture/05-04-chaos/smarttesting_api/web_app.py create mode 100644 05-architecture/05-04-chaos/smarttesting_main/__init__.py create mode 100644 05-architecture/05-04-chaos/smarttesting_main/celery_app.py create mode 100644 05-architecture/05-04-chaos/smarttesting_main/celery_module.py create mode 100644 05-architecture/05-04-chaos/smarttesting_main/dev_modules.py create mode 100644 05-architecture/05-04-chaos/smarttesting_main/infrastructure/__init__.py create mode 100644 05-architecture/05-04-chaos/smarttesting_main/infrastructure/module.py create mode 100644 05-architecture/05-04-chaos/smarttesting_main/infrastructure/verification_repo.py create mode 100644 05-architecture/05-04-chaos/smarttesting_main/infrastructure/verified_person.py create mode 100644 05-architecture/05-04-chaos/smarttesting_main/smart_testing_application.py create mode 100644 05-architecture/05-04-chaos/smarttesting_main/task.py create mode 100644 05-architecture/05-04-chaos/tests/__init__.py create mode 100644 05-architecture/05-04-chaos/tests/chaos_tests.py create mode 100644 05-architecture/05-05-performance/Dockerfile create mode 100644 05-architecture/05-05-performance/README.adoc create mode 100644 05-architecture/05-05-performance/docker-compose.yml create mode 100644 05-architecture/05-05-performance/locustfile.py create mode 100644 05-architecture/05-05-performance/poetry.lock create mode 100644 05-architecture/05-05-performance/pyproject.toml create mode 100644 05-architecture/05-05-performance/resources/images/locust_bledy.png create mode 100644 05-architecture/05-05-performance/resources/images/locust_moment_krytyczny.png create mode 100644 05-architecture/05-05-performance/resources/images/locust_ustawienia.png create mode 100644 05-architecture/05-05-performance/resources/images/locust_wykresy.png create mode 100644 05-architecture/05-05-performance/smarttesting/__init__.py create mode 100644 05-architecture/05-05-performance/smarttesting/customer/__init__.py create mode 100644 05-architecture/05-05-performance/smarttesting/customer/customer.py create mode 100644 05-architecture/05-05-performance/smarttesting/customer/person.py create mode 100644 05-architecture/05-05-performance/smarttesting/message.py create mode 100644 05-architecture/05-05-performance/smarttesting/serialization.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/__init__.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/__init__.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/bik_verification_service.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/customer_verification.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/customer_verification_result.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/customer_verifier.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/fraud_alert_task.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/fraud_detected_handler.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/module.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/verification/__init__.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/verification/age.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/verification/identification_number.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/verification/module.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/verification_repository.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/verified_person.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/customer/verified_person_dto.py create mode 100644 05-architecture/05-05-performance/smarttesting/verifier/verification.py create mode 100644 05-architecture/05-05-performance/smarttesting_api/__init__.py create mode 100644 05-architecture/05-05-performance/smarttesting_api/web_app.py create mode 100644 05-architecture/05-05-performance/smarttesting_main/__init__.py create mode 100644 05-architecture/05-05-performance/smarttesting_main/celery_app.py create mode 100644 05-architecture/05-05-performance/smarttesting_main/celery_module.py create mode 100644 05-architecture/05-05-performance/smarttesting_main/dev_modules.py create mode 100644 05-architecture/05-05-performance/smarttesting_main/infrastructure/__init__.py create mode 100644 05-architecture/05-05-performance/smarttesting_main/infrastructure/module.py create mode 100644 05-architecture/05-05-performance/smarttesting_main/infrastructure/verification_repo.py create mode 100644 05-architecture/05-05-performance/smarttesting_main/infrastructure/verified_person.py create mode 100644 05-architecture/05-05-performance/smarttesting_main/smart_testing_application.py create mode 100644 05-architecture/05-05-performance/smarttesting_main/task.py create mode 100644 05-architecture/05-05-performance/tests/__init__.py create mode 100644 05-architecture/05-05-performance/tests/microbenchmarks_tests.py create mode 100644 05-architecture/README.adoc 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 0000000000000000000000000000000000000000..ab4fc20c21587645045af69c51b0ee35d480594f GIT binary patch literal 2315 zcmbtVTXWk)6#h1LBG(bLbxTQT8iKjhPUA?nY)4K?shcJx#c4WqCOq)qSxakMi6gnX zq;xv{5j^oLc;EqOn1Sgd!;fM(l5#0dD4F2#?5@t9bM`ymo^$rkzkdB4KpBtI$l$Xy zZX%aLK80Bcx5hDtLJITYtb~J_LNT0660~v1D5p_DHH`)6Dbyq^N?2kTyKUK)e}^HF z%Wp9xmmO0uTx?jjcogimg}ceyeUfAvj=}p|+_l2*17XtdSsueo!*Fch<%aJm%Ll71 z;aVMw%!TLEF;jcIZ<*W|EwASUeY52XKXC0<$K`>^Ai3gcK$;9%!*UdtmfZ6KSD1=7 z5C(ZNxNkYO@+8)4z9}U4DY6(iwnsp-!wwF&$Trp?{q)MWh8Tn%JqfoNCZ7bhZ|#aL z%d;rFx@|jtbRb1H)o|RdvKY&BvO!>(*~P40GxP;rXa!C0EELMRRxI#N)hHNRyUf*UwWgLz*}3dMxa2dt_#$ib zT|stDUv#)_aoVGc_=nJW=Iw;?=sulJt8KY{k4#ExNiC>F`qO4t*rsqvy1KGnZ>*5S zju_?rmSwi=D7R?0SklUs>VjTVvoBxDSjK%BD_CV1SEAfl?g?W@#shpwc{Y?9CvH~8 z8XijcN=5_g5+2Fez&gVZM`ToucDYh2b_(U1$%zb6Dbxfn70SG5YI?0!(TgS_Jwe7p zZipS)IA-F0(6;*ABiS$u#hOkmXa!ZL%_A1x#(}P?nyMG|QcWWg7^dG|P7K#iDumhS z-Q!^eTscmQqcmsuV2C`b2EQKH7{h4S+7pyfm0W(fe9lyV#UbbaZA6`?CpcvYLqFFz zM%?sW%kD0{A&FCa>6??H^I=W49rHQEgHujDr^7H>hFh}iqUrhQ#njSW$&QdE7Y}x8+Gbt zWU(CTg^etH9Vp%6P3o%YSaY+-DG(*MmXEq@EZio?u85j!z3oM&Yt#z6!i_x;&e;&u z*|ZrhSl-IhfcG~Ye1yyNbcVkKs8Vp1#`ow;p_$Pg=gNisum$H8Q2X88CF&@Y`8I{r8f`b~nKBj|a8H%HLt309GyYyT7aCltU4 GKK&bx{DW-( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..513fe0c30e449d2f8ac59331bc7a416311215fc2 GIT binary patch literal 655 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c7781b3dbc22682f0407ac6003e294d74a78b2ec GIT binary patch literal 1748 zcmcIkTTc@~6#k}^E-mFMBCa=7kd`X@pl>B4TBF1iB*8TLWTxBcc3|0Urqe3_3ST5* zBt~OQ{87d;+bz+6Xb64Soij7%obNlg`SJ7M5WoXC8KiO3!mVMXaNGRmGDu+F!kr9; zuwWr)A~8&`9Lg3Zg`Ws4dJ*Zo?hyT zD-_fCn$V4~w!uy6wNR9h^rrLzuG@;@te;bPPGwnp7{#TWyn89U+`Tv@1hL?TeOX;Du@`QdA)^-`Da{1$}LWw1XRl#e~=uPx=+FH__@+LLr z+zB1lb#$bZuV9v5*EBuBzyN(z1zNKtZP4qRpf$Bv*av$T-H${X=JXQ=afnfprn_1g z!#M3o9}}1a!v&JFw02LXNV-V!Afszz1TwpXq)9hP2JkiQjBC5_oz(U|BJry50Rs&5 zPcrOdaF1>WU`UKcWWSKbR|5R@8xi}6hz%Q82=;1Z#ZoTtw_tySb literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7eacd26a30a83de75eed36e8d8caddb3085215d2 GIT binary patch literal 10285 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 + + + + +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 0000000000000000000000000000000000000000..e3969e5ddcf9cb8812a224d416b9e3bb0ea608e4 GIT binary patch literal 40758 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 N45btnLqToP{{aaIuYv#o literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c9e2dada27dbdb25947dd5254c9f13d47b672683 GIT binary patch literal 102 ncmZQ%WM)7GCT0K8024%ksS1S7QbCjd4>cl76QW?gA-VzpNdGd= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ed8a58b62f159ffd758f49be841d431ff54cc5d4 GIT binary patch literal 221 zcmZQ(WSpOzpI1_pm|Rk<=L}_qr50tTWu_K|q!yP<#U{wWz}PiQTDOdeX?|H^PG(AC zNoss?Mt*5dN_0cnP{Y!IUi7Bes}#$i-mKHNAWlrZ=~P5iwCGei6{@Rxs;d!v zJLB6Kb;fx(WYl!c3YESZQ=&Rvs?3 z!A41rIdayOw`F=#IxR|+$RQ#1t*E9pwvq&ZrIMMys|htA^QJ`V|8z^GNf|f@xi5Fuv$s zOw!$;WounZ>I(9zis=VSfthFBYj}l$j|~XuM;*uXhHRr!G0FW`^|8B5pc~{6mknde z@QqC|4^+XlxK+Pg^#$x-(=yF1kr54yW=p{{4WF6#T-@&1#aXl9hs#mbx9l9(+x`Z^ z(8|VD+cmhj##fCH1`B4!G^W&|zB;Iv8YcD<`8-Q_*TV6ECCX}kH46304g2$f{GOO> zG}BF`E7I6Vj>5{gEk{Dg4K$o&b0cuRo2Ld^NevY2uz5;$%T=;_U}UM#ERL2ydfcsg z1@n+4o1%?F%QY_BWD4lmg3~%4!6qFK;bDRG^NA5`x(BM3&1@6co}V!@!QyCUqSi8F zmCCkRGM!*^$yk+)JDu^l6lTuq_%r^Z<5j#SuU;1r=XJb+H#NMa<88d7vl_A#^~M6T zE7S8!0~Ft%_?lsq%c>Rai?7;Y%eXaGAqx_kMVu;{zS@`t?(L2XszC}YP*_`9=4o4` z(079&fwc=1hG1BZl9=UR^7wXRIn%B{XWW&@GM9noqoc=9=2_!}emvSG7TOA>1DPYy zRWxs{~4YQ@@B`bAOXj<5Q%-Ek5_Z)2GsWzgawflbRk8W~Y`AN~2vJ;VvDbp@+wH zXy!4?yIv~qLp!!%JAYXSw)7y6gYno%IJCytm9(O{g zo(J)Jg}5isQ$w8L_-@*wZu|jz6j>ScV}O>W7kW6?3+aW#o`ld9-u5J)1)l=tfyeo8 zOY#6dvR55r|K|x=u 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 0000000000000000000000000000000000000000..03281d9cbb62b4be96e0e9efecc5451003b85f3d GIT binary patch literal 26124 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..acc89245865dc5bd4cc0050aaef26d975752fe21 GIT binary patch literal 34281 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

-+-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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9bb3e2f93ae5ddd5df44e995970d812a3fa94cde GIT binary patch literal 38201 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1ebc7f5a158c0b0209e028a2958126d8f4c98901 GIT binary patch literal 143537 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%0AN%3Jh9W!j_M$ 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 H`S$++H+^;* literal 0 HcmV?d00001 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.