From 0d25f175ebc8d431b08084aaed1f2944aa6d044e Mon Sep 17 00:00:00 2001 From: Jumana B Date: Thu, 18 Jan 2024 11:53:30 -0500 Subject: [PATCH 01/33] Add API Validation (#2081) * validate apli keys * Add api key validation - We check the API Prefix - We check that the service_id is in the api key - Else return an error * Test changes * fix errors * fix errors * Update tests/app/authentication/test_authentication.py Co-authored-by: Andrew --------- Co-authored-by: Andrew --- app/api_key/rest.py | 3 +-- app/authentication/auth.py | 11 --------- app/dao/api_key_dao.py | 23 +++++++++++++++--- tests/app/api_key/test_rest.py | 5 +++- .../app/authentication/test_authentication.py | 14 +++++++---- tests/app/dao/test_api_key_dao.py | 24 ++++++++++++++----- .../notifications/test_post_notifications.py | 12 +++++++--- 7 files changed, 61 insertions(+), 31 deletions(-) diff --git a/app/api_key/rest.py b/app/api_key/rest.py index 8ad96ec1e8..1aa6b28fd9 100644 --- a/app/api_key/rest.py +++ b/app/api_key/rest.py @@ -121,8 +121,7 @@ def revoke_api_keys(): # Step 1 try: - # take last 36 chars of string so that it works even if the full key is provided. - api_key_token = api_key_data["token"][-36:] + api_key_token = api_key_data["token"] api_key = get_api_key_by_secret(api_key_token) except Exception: current_app.logger.error( diff --git a/app/authentication/auth.py b/app/authentication/auth.py index 144c89079f..8bef40a005 100644 --- a/app/authentication/auth.py +++ b/app/authentication/auth.py @@ -152,19 +152,8 @@ def requires_auth(): def _auth_by_api_key(auth_token): - # TODO: uncomment this when the grace period for the token prefix is over - # orig_token = auth_token - try: - # take last 36 chars of string so that it works even if the full key is provided. - auth_token = auth_token[-36:] api_key = get_api_key_by_secret(auth_token) - - # TODO: uncomment this when the grace period for the token prefix is over - # check for token prefix - # if current_app.config["API_KEY_PREFIX"] not in orig_token: - # raise AuthError("Invalid token: you must re-generate your API key to continue using GC Notify", 403, service_id=api_key.service.id, api_key_id=api_key.id) - except NoResultFound: raise AuthError("Invalid token: API key not found", 403) _auth_with_api_key(api_key, api_key.service) diff --git a/app/dao/api_key_dao.py b/app/dao/api_key_dao.py index 679467f4f4..d669ee61af 100644 --- a/app/dao/api_key_dao.py +++ b/app/dao/api_key_dao.py @@ -76,13 +76,30 @@ def update_compromised_api_key_info(service_id, api_key_id, compromised_info): db.session.add(api_key) -def get_api_key_by_secret(secret): - signed_with_all_keys = signer_api_key.sign_with_all_keys(str(secret)) +def get_api_key_by_secret(secret, service_id=None): + # Check the first part of the secret is the gc prefix + if current_app.config["API_KEY_PREFIX"] != secret[: len(current_app.config["API_KEY_PREFIX"])]: + raise NoResultFound() + + # Check if the remaining part of the secret is a the valid api key + token = secret[-36:] + signed_with_all_keys = signer_api_key.sign_with_all_keys(str(token)) for signed_secret in signed_with_all_keys: try: - return db.on_reader().query(ApiKey).filter_by(_secret=signed_secret).options(joinedload("service")).one() + api_key = db.on_reader().query(ApiKey).filter_by(_secret=signed_secret).options(joinedload("service")).one() except NoResultFound: pass + + # Check the middle portion of the secret is the valid service id + if api_key.service_id: + if len(secret) >= 79: + service_id_from_token = str(secret[-73:-37]) + if str(api_key.service_id) != service_id_from_token: + raise NoResultFound() + else: + raise NoResultFound() + if api_key: + return api_key raise NoResultFound() diff --git a/tests/app/api_key/test_rest.py b/tests/app/api_key/test_rest.py index 0def4b7884..9d78074688 100644 --- a/tests/app/api_key/test_rest.py +++ b/tests/app/api_key/test_rest.py @@ -90,6 +90,9 @@ def test_revoke_api_keys_with_valid_auth_revokes_and_notifies_user(self, client, api_key_1 = create_api_key(service, key_type=KEY_TYPE_NORMAL, key_name="Key 1") unsigned_secret = get_unsigned_secret(api_key_1.id) + # Create token expected from the frontend + unsigned_secret = f"gcntfy-keyname-{service.id}-{unsigned_secret}" + sre_auth_header = create_sre_authorization_header() response = client.post( url_for("sre_tools.revoke_api_keys"), @@ -98,7 +101,7 @@ def test_revoke_api_keys_with_valid_auth_revokes_and_notifies_user(self, client, ) # Get api key from DB - api_key_1 = get_api_key_by_secret(api_key_1.secret) + api_key_1 = get_api_key_by_secret(unsigned_secret) assert response.status_code == 201 assert api_key_1.expiry_date is not None assert api_key_1.compromised_key_info["type"] == "cds-tester" diff --git a/tests/app/authentication/test_authentication.py b/tests/app/authentication/test_authentication.py index 16299507b9..4ed075497c 100644 --- a/tests/app/authentication/test_authentication.py +++ b/tests/app/authentication/test_authentication.py @@ -135,18 +135,20 @@ def test_admin_auth_should_not_allow_api_key_scheme(client, sample_api_key): @pytest.mark.parametrize("scheme", ["ApiKey-v1", "apikey-v1", "APIKEY-V1"]) def test_should_allow_auth_with_api_key_scheme(client, sample_api_key, scheme): api_key_secret = get_unsigned_secret(sample_api_key.id) - - response = client.get("/notifications", headers={"Authorization": f"{scheme} {api_key_secret}"}) + unsigned_secret = f"gcntfy-keyname-{sample_api_key.service_id}-{api_key_secret}" + response = client.get("/notifications", headers={"Authorization": f"{scheme} {unsigned_secret}"}) assert response.status_code == 200 -def test_should_allow_auth_with_api_key_scheme_36_chars_or_longer(client, sample_api_key): +def test_should_NOT_allow_auth_with_api_key_scheme_with_incorrect_format(client, sample_api_key): api_key_secret = "fhsdkjhfdsfhsd" + get_unsigned_secret(sample_api_key.id) response = client.get("/notifications", headers={"Authorization": f"ApiKey-v1 {api_key_secret}"}) - assert response.status_code == 200 + assert response.status_code == 403 + error_message = json.loads(response.get_data()) + assert error_message["message"] == {"token": ["Invalid token: API key not found"]} def test_should_not_allow_invalid_api_key(client, sample_api_key): @@ -162,7 +164,9 @@ def test_should_not_allow_expired_api_key(client, sample_api_key): expire_api_key(service_id=sample_api_key.service_id, api_key_id=sample_api_key.id) - response = client.get("/notifications", headers={"Authorization": f"ApiKey-v1 {api_key_secret}"}) + unsigned_secret = f"gcntfy-keyname-{sample_api_key.service_id}-{api_key_secret}" + + response = client.get("/notifications", headers={"Authorization": f"ApiKey-v1 {unsigned_secret}"}) assert response.status_code == 403 error_message = json.loads(response.get_data()) diff --git a/tests/app/dao/test_api_key_dao.py b/tests/app/dao/test_api_key_dao.py index 9535ce0343..6a71490f08 100644 --- a/tests/app/dao/test_api_key_dao.py +++ b/tests/app/dao/test_api_key_dao.py @@ -103,12 +103,24 @@ def test_get_unsigned_secret_returns_key(sample_api_key): assert unsigned_api_key == sample_api_key.secret -def test_get_api_key_by_secret(sample_api_key): - unsigned_secret = get_unsigned_secret(sample_api_key.id) - assert get_api_key_by_secret(unsigned_secret).id == sample_api_key.id - - with pytest.raises(NoResultFound): - get_api_key_by_secret("nope") +class TestGetAPIKeyBySecret: + def test_get_api_key_by_secret(self, sample_api_key): + secret = get_unsigned_secret(sample_api_key.id) + # Create token expected from the frontend + unsigned_secret = f"gcntfy-keyname-{sample_api_key.service_id}-{secret}" + assert get_api_key_by_secret(unsigned_secret).id == sample_api_key.id + + with pytest.raises(NoResultFound): + get_api_key_by_secret("nope") + + # Test getting secret without the keyname prefix + with pytest.raises(NoResultFound): + get_api_key_by_secret(str(sample_api_key.id)) + + # Test the service_name isnt part of the secret + with pytest.raises(NoResultFound): + # import pdb; pdb.set_trace() + get_api_key_by_secret(f"gcntfy-keyname-hello-{secret}") def test_should_not_allow_duplicate_key_names_per_service(sample_api_key, fake_uuid): diff --git a/tests/app/v2/notifications/test_post_notifications.py b/tests/app/v2/notifications/test_post_notifications.py index 84b868168d..2e3d1d36b2 100644 --- a/tests/app/v2/notifications/test_post_notifications.py +++ b/tests/app/v2/notifications/test_post_notifications.py @@ -1515,6 +1515,8 @@ def __send_sms(): key_type=key_type, ) save_model_api_key(api_key) + api_key_secret = get_unsigned_secret(api_key.id) + unsigned_secret = f"gcntfy-keyname-{api_key.service_id}-{api_key_secret}" with set_config_values(notify_api, {"REDIS_ENABLED": True}): response = client.post( @@ -1522,7 +1524,7 @@ def __send_sms(): data=json.dumps(data), headers=[ ("Content-Type", "application/json"), - ("Authorization", f"ApiKey-v1 {get_unsigned_secret(api_key.id)}"), + ("Authorization", f"ApiKey-v1 {unsigned_secret}"), ], ) return response @@ -1563,6 +1565,8 @@ def __send_sms(): key_type=key_type, ) save_model_api_key(api_key) + api_key_secret = get_unsigned_secret(api_key.id) + unsigned_secret = f"gcntfy-keyname-{api_key.service_id}-{api_key_secret}" with set_config_values(notify_api, {"REDIS_ENABLED": True}): response = client.post( @@ -1570,7 +1574,7 @@ def __send_sms(): data=json.dumps(data), headers=[ ("Content-Type", "application/json"), - ("Authorization", f"ApiKey-v1 {get_unsigned_secret(api_key.id)}"), + ("Authorization", f"ApiKey-v1 {unsigned_secret}"), ], ) return response @@ -1607,6 +1611,8 @@ def __send_sms(): key_type=key_type, ) save_model_api_key(api_key) + api_key_secret = get_unsigned_secret(api_key.id) + unsigned_secret = f"gcntfy-keyname-{api_key.service_id}-{api_key_secret}" with set_config_values(notify_api, {"REDIS_ENABLED": True}): response = client.post( @@ -1614,7 +1620,7 @@ def __send_sms(): data=json.dumps(data), headers=[ ("Content-Type", "application/json"), - ("Authorization", f"ApiKey-v1 {get_unsigned_secret(api_key.id)}"), + ("Authorization", f"ApiKey-v1 {unsigned_secret}"), ], ) return response From 6c982fe8273457ad35c5386a7e5c08d4a92eba72 Mon Sep 17 00:00:00 2001 From: Jumana B Date: Thu, 18 Jan 2024 12:28:28 -0500 Subject: [PATCH 02/33] Fix smoke test (#2082) --- tests_smoke/smoke/test_api_bulk.py | 2 +- tests_smoke/smoke/test_api_one_off.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests_smoke/smoke/test_api_bulk.py b/tests_smoke/smoke/test_api_bulk.py index 1817a2b552..e31455589d 100644 --- a/tests_smoke/smoke/test_api_bulk.py +++ b/tests_smoke/smoke/test_api_bulk.py @@ -25,7 +25,7 @@ def test_api_bulk(notification_type: Notification_type): "template_id": template_id, "csv": rows_to_csv([[header, "var"], *job_line(to, 2)]), }, - headers={"Authorization": f"ApiKey-v1 {Config.API_KEY[-36:]}"}, + headers={"Authorization": f"ApiKey-v1 {Config.API_KEY}"}, ) if response.status_code != 201: pretty_print(response.json()) diff --git a/tests_smoke/smoke/test_api_one_off.py b/tests_smoke/smoke/test_api_one_off.py index 1c19ec1018..643192bb64 100644 --- a/tests_smoke/smoke/test_api_one_off.py +++ b/tests_smoke/smoke/test_api_one_off.py @@ -57,7 +57,7 @@ def test_api_one_off(notification_type: Notification_type, attachment_type: Atta response = requests.post( f"{Config.API_HOST_NAME}/v2/notifications/{notification_type.value}", json=data, - headers={"Authorization": f"ApiKey-v1 {Config.API_KEY[-36:]}"}, + headers={"Authorization": f"ApiKey-v1 {Config.API_KEY}"}, ) if response.status_code != 201: pretty_print(response.json()) From d4a58db15a126c6d77b286901f0ae24522795f4b Mon Sep 17 00:00:00 2001 From: Jumana B Date: Thu, 18 Jan 2024 12:57:05 -0500 Subject: [PATCH 03/33] Fix api key (#2083) --- tests_smoke/smoke/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests_smoke/smoke/common.py b/tests_smoke/smoke/common.py index 37293fc77c..8c189baf19 100644 --- a/tests_smoke/smoke/common.py +++ b/tests_smoke/smoke/common.py @@ -79,7 +79,7 @@ def single_succeeded(uri: str, use_jwt: bool) -> bool: token = create_jwt_token(Config.ADMIN_CLIENT_SECRET, client_id=Config.ADMIN_CLIENT_USER_NAME) headers = {"Authorization": f"Bearer {token}"} else: - headers = {"Authorization": f"ApiKey-v1 {Config.API_KEY[-36:]}"} + headers = {"Authorization": f"ApiKey-v1 {Config.API_KEY}"} response = requests.get( uri, From 96f0269d6d0cd76917666369908b585578f14484 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:37:29 -0500 Subject: [PATCH 04/33] fix(deps): update notifications-utils digest to 69b6eee (#2086) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 185 ++----------------------------------------------- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 180 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0711b8deed..7deb6dd41b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.9.1" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -101,7 +100,6 @@ speedups = ["Brotli", "aiodns", "brotlicffi"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -116,7 +114,6 @@ frozenlist = ">=1.1.0" name = "alembic" version = "1.12.1" description = "A database migration tool for SQLAlchemy." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -136,7 +133,6 @@ tz = ["python-dateutil"] name = "amqp" version = "5.2.0" description = "Low-level AMQP client for Python (fork of amqplib)." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -151,7 +147,6 @@ vine = ">=5.0.0,<6.0.0" name = "annotated-types" version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -163,7 +158,6 @@ files = [ name = "apig-wsgi" version = "2.18.0" description = "Wrap a WSGI application in an AWS Lambda handler function for running on API Gateway or an ALB." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -175,7 +169,6 @@ files = [ name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -187,7 +180,6 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -206,7 +198,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "aws-embedded-metrics" version = "1.0.8" description = "AWS Embedded Metrics Package" -category = "main" optional = false python-versions = "*" files = [ @@ -221,7 +212,6 @@ aiohttp = "*" name = "awscli" version = "1.32.19" description = "Universal Command Line Environment for AWS." -category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -241,7 +231,6 @@ s3transfer = ">=0.10.0,<0.11.0" name = "awscli-cwlogs" version = "1.4.6" description = "AWSCLI CloudWatch Logs plugin" -category = "main" optional = false python-versions = "*" files = [ @@ -258,7 +247,6 @@ six = ">=1.1.0" name = "bcrypt" version = "4.1.1" description = "Modern password hashing for your software and your servers" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -289,7 +277,6 @@ typecheck = ["mypy"] name = "billiard" version = "4.2.0" description = "Python multiprocessing fork with improvements and bugfixes" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -301,7 +288,6 @@ files = [ name = "black" version = "23.7.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -347,7 +333,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "bleach" version = "6.0.0" description = "An easy safelist-based HTML-sanitizing tool." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -366,7 +351,6 @@ css = ["tinycss2 (>=1.1.0,<1.2)"] name = "blinker" version = "1.7.0" description = "Fast, simple object-to-object and broadcast signaling" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -378,7 +362,6 @@ files = [ name = "boto" version = "2.49.0" description = "Amazon Web Services Library" -category = "main" optional = false python-versions = "*" files = [ @@ -390,7 +373,6 @@ files = [ name = "boto3" version = "1.34.19" description = "The AWS SDK for Python" -category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -410,7 +392,6 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] name = "botocore" version = "1.34.19" description = "Low-level, data-driven core of boto 3." -category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -430,7 +411,6 @@ crt = ["awscrt (==0.19.19)"] name = "brotli" version = "1.1.0" description = "Python bindings for the Brotli compression library" -category = "dev" optional = false python-versions = "*" files = [ @@ -523,7 +503,6 @@ files = [ name = "cachelib" version = "0.10.2" description = "A collection of cache libraries in the same API interface." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -535,7 +514,6 @@ files = [ name = "cachetools" version = "4.2.4" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = "~=3.5" files = [ @@ -547,7 +525,6 @@ files = [ name = "celery" version = "5.3.1" description = "Distributed Task Queue." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -609,7 +586,6 @@ zstd = ["zstandard (==0.21.0)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -621,7 +597,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -698,7 +673,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -798,7 +772,6 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -813,7 +786,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "click-datetime" version = "0.2" description = "Datetime type support for click." -category = "main" optional = false python-versions = "*" files = [ @@ -831,7 +803,6 @@ dev = ["wheel"] name = "click-didyoumean" version = "0.3.0" description = "Enables git-like *did-you-mean* feature in click" -category = "main" optional = false python-versions = ">=3.6.2,<4.0.0" files = [ @@ -846,7 +817,6 @@ click = ">=7" name = "click-plugins" version = "1.1.1" description = "An extension module for click to enable registering CLI commands via setuptools entry-points." -category = "main" optional = false python-versions = "*" files = [ @@ -864,7 +834,6 @@ dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] name = "click-repl" version = "0.3.0" description = "REPL plugin for Click" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -883,7 +852,6 @@ testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -895,7 +863,6 @@ files = [ 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." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -911,7 +878,6 @@ yaml = ["PyYAML"] name = "coverage" version = "5.5" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" files = [ @@ -979,7 +945,6 @@ toml = ["toml"] name = "coveralls" version = "3.3.1" description = "Show coverage stats online via coveralls.io" -category = "dev" optional = false python-versions = ">= 3.5" files = [ @@ -988,7 +953,7 @@ files = [ ] [package.dependencies] -coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" +coverage = ">=4.1,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<7.0" docopt = ">=0.6.1" requests = ">=1.0.0" @@ -999,7 +964,6 @@ yaml = ["PyYAML (>=3.10)"] name = "cryptography" version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1045,7 +1009,6 @@ test-randomorder = ["pytest-randomly"] name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" -category = "main" optional = false python-versions = "*" files = [ @@ -1056,7 +1019,6 @@ files = [ name = "docutils" version = "0.16" description = "Docutils -- Python Documentation Utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1068,7 +1030,6 @@ files = [ name = "environs" version = "9.5.0" description = "simplified environment variable parsing" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1090,7 +1051,6 @@ tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1105,7 +1065,6 @@ test = ["pytest (>=6)"] name = "execnet" version = "2.0.2" description = "execnet: rapid multi-Python deployment" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1120,7 +1079,6 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] name = "fido2" version = "0.9.3" description = "Python based FIDO 2.0 library" -category = "main" optional = false python-versions = ">=2.7.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -1138,7 +1096,6 @@ pcsc = ["pyscard"] name = "filelock" version = "3.13.1" description = "A platform independent file lock." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1155,7 +1112,6 @@ typing = ["typing-extensions (>=4.8)"] name = "flake8" version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -1172,7 +1128,6 @@ pyflakes = ">=3.1.0,<3.2.0" name = "flask" version = "2.3.3" description = "A simple framework for building complex web applications." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1195,7 +1150,6 @@ dotenv = ["python-dotenv"] name = "flask-basicauth" version = "0.2.0" description = "HTTP basic access authentication for Flask." -category = "dev" optional = false python-versions = "*" files = [ @@ -1209,7 +1163,6 @@ Flask = "*" name = "flask-bcrypt" version = "1.0.1" description = "Brcrypt hashing for Flask." -category = "main" optional = false python-versions = "*" files = [ @@ -1225,7 +1178,6 @@ Flask = "*" name = "flask-cors" version = "4.0.0" description = "A Flask extension adding a decorator for CORS support" -category = "dev" optional = false python-versions = "*" files = [ @@ -1240,7 +1192,6 @@ Flask = ">=0.9" name = "flask-marshmallow" version = "0.14.0" description = "Flask + marshmallow for beautiful APIs" -category = "main" optional = false python-versions = "*" files = [ @@ -1264,7 +1215,6 @@ tests = ["flask-sqlalchemy", "marshmallow-sqlalchemy (>=0.13.0)", "marshmallow-s name = "flask-migrate" version = "2.7.0" description = "SQLAlchemy database migrations for Flask applications using Alembic" -category = "main" optional = false python-versions = "*" files = [ @@ -1281,7 +1231,6 @@ Flask-SQLAlchemy = ">=1.0" name = "flask-redis" version = "0.4.0" description = "A nice way to use Redis in your Flask app" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1301,7 +1250,6 @@ tests = ["coverage", "pytest", "pytest-mock"] name = "Flask-SQLAlchemy" version = "2.3.2.dev20231128" description = "Adds SQLAlchemy support to your Flask application" -category = "main" optional = false python-versions = "*" files = [] @@ -1321,7 +1269,6 @@ resolved_reference = "500e732dd1b975a56ab06a46bd1a20a21e682262" name = "freezegun" version = "1.2.2" description = "Let your Python tests travel through time" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1336,7 +1283,6 @@ python-dateutil = ">=2.7" name = "frozenlist" version = "1.4.0" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1407,7 +1353,6 @@ files = [ name = "future" version = "0.18.3" description = "Clean single-source support for Python 3 and 2" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1418,7 +1363,6 @@ files = [ name = "gevent" version = "23.9.0" description = "Coroutine-based network library" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1470,7 +1414,6 @@ test = ["cffi (>=1.12.2)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idn name = "geventhttpclient" version = "2.0.11" description = "http client library for gevent" -category = "dev" optional = false python-versions = "*" files = [ @@ -1594,7 +1537,6 @@ six = "*" name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -1672,7 +1614,6 @@ test = ["objgraph", "psutil"] name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1693,7 +1634,6 @@ tornado = ["tornado (>=0.2)"] name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1705,7 +1645,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1717,7 +1656,6 @@ files = [ name = "iso8601" version = "2.0.0" description = "Simple module to parse ISO 8601 dates" -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -1729,7 +1667,6 @@ files = [ name = "isodate" version = "0.6.1" description = "An ISO 8601 date/time/duration parser and formatter" -category = "main" optional = false python-versions = "*" files = [ @@ -1744,7 +1681,6 @@ six = "*" name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1762,7 +1698,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1774,7 +1709,6 @@ files = [ name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1792,7 +1726,6 @@ i18n = ["Babel (>=2.7)"] name = "jinja2-cli" version = "0.8.2" description = "A CLI interface to Jinja2" -category = "dev" optional = false python-versions = "*" files = [ @@ -1814,7 +1747,6 @@ yaml = ["jinja2", "pyyaml"] name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1826,7 +1758,6 @@ files = [ name = "jsonschema" version = "3.2.0" description = "An implementation of JSON Schema validation for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -1848,7 +1779,6 @@ format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-va name = "kombu" version = "5.3.4" description = "Messaging library for Python." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1884,7 +1814,6 @@ zookeeper = ["kazoo (>=2.8.0)"] name = "locust" version = "2.16.1" description = "Developer friendly load testing framework" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1912,7 +1841,6 @@ Werkzeug = ">=2.0.0" name = "lxml" version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ @@ -2020,7 +1948,6 @@ source = ["Cython (>=0.29.35)"] name = "mako" version = "1.3.0" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2040,7 +1967,6 @@ testing = ["pytest"] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2110,7 +2036,6 @@ files = [ name = "marshmallow" version = "3.20.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2131,7 +2056,6 @@ tests = ["pytest", "pytz", "simplejson"] name = "marshmallow-sqlalchemy" version = "0.29.0" description = "SQLAlchemy integration with the marshmallow (de)serialization library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2154,7 +2078,6 @@ tests = ["pytest", "pytest-lazy-fixture (>=0.6.2)"] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2166,7 +2089,6 @@ files = [ name = "mistune" version = "0.8.4" description = "The fastest markdown parser in pure Python" -category = "main" optional = false python-versions = "*" files = [ @@ -2178,7 +2100,6 @@ files = [ name = "more-itertools" version = "8.14.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2190,7 +2111,6 @@ files = [ name = "moto" version = "4.1.11" description = "" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2237,7 +2157,6 @@ xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] name = "msgpack" version = "1.0.7" description = "MessagePack serializer" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2303,7 +2222,6 @@ files = [ name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2387,7 +2305,6 @@ files = [ name = "mypy" version = "1.5.0" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2429,7 +2346,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2441,7 +2357,6 @@ files = [ name = "nanoid" version = "2.0.0" description = "A tiny, secure, URL-friendly, unique string ID generator for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -2453,7 +2368,6 @@ files = [ name = "networkx" version = "2.8.8" description = "Python package for creating and manipulating graphs and networks" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2472,7 +2386,6 @@ test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] name = "newrelic" version = "6.10.0.165" description = "New Relic Python Agent" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -2498,7 +2411,6 @@ infinite-tracing = ["grpcio (<2)", "protobuf (<4)"] name = "notifications-python-client" version = "6.4.1" description = "Python API client for GOV.UK Notify." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2514,7 +2426,6 @@ requests = ">=2.0.0" name = "notifications-utils" version = "52.0.18" description = "Shared python code for Notification - Provides logging utils etc." -category = "main" optional = false python-versions = "~3.10" files = [] @@ -2548,14 +2459,13 @@ werkzeug = "2.3.7" [package.source] type = "git" url = "https://github.com/cds-snc/notifier-utils.git" -reference = "52.0.18" +reference = "69b6eee0c715fa4c3fe9ac48e4ccd6e3d4c1e239" resolved_reference = "69b6eee0c715fa4c3fe9ac48e4ccd6e3d4c1e239" [[package]] name = "ordered-set" version = "4.1.0" description = "An OrderedSet is a custom MutableSet that remembers its order, so that every" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2570,7 +2480,6 @@ dev = ["black", "mypy", "pytest"] name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2582,7 +2491,6 @@ files = [ name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2594,7 +2502,6 @@ files = [ name = "pendulum" version = "2.1.2" description = "Python datetimes made easy" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2629,7 +2536,6 @@ pytzdata = ">=2020.1" name = "phonenumbers" version = "8.13.24" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." -category = "main" optional = false python-versions = "*" files = [ @@ -2641,7 +2547,6 @@ files = [ name = "platformdirs" version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2657,7 +2562,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2673,7 +2577,6 @@ testing = ["pytest", "pytest-benchmark"] name = "prompt-toolkit" version = "3.0.41" description = "Library for building powerful interactive command lines in Python" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -2688,7 +2591,6 @@ wcwidth = "*" name = "psutil" version = "5.9.6" description = "Cross-platform lib for process and system monitoring in Python." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -2717,7 +2619,6 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] name = "psycopg2-binary" version = "2.9.7" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2787,7 +2688,6 @@ files = [ name = "pwnedpasswords" version = "2.0.0" description = "A Python wrapper for Troy Hunt's Pwned Passwords API." -category = "main" optional = false python-versions = "*" files = [ @@ -2801,7 +2701,6 @@ future = "*" name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2813,7 +2712,6 @@ files = [ name = "py-w3c" version = "0.3.1" description = "W3C services for python." -category = "main" optional = false python-versions = "*" files = [ @@ -2824,7 +2722,6 @@ files = [ name = "pyasn1" version = "0.5.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -2836,7 +2733,6 @@ files = [ name = "pycodestyle" version = "2.11.1" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2848,7 +2744,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2860,7 +2755,6 @@ files = [ name = "pycurl" version = "7.45.2" description = "PycURL -- A Python Interface To The cURL library" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2871,7 +2765,6 @@ files = [ name = "pydantic" version = "2.5.2" description = "Data validation using Python type hints" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2891,7 +2784,6 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.14.5" description = "" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3009,7 +2901,6 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pyflakes" version = "3.1.0" description = "passive checker of Python programs" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3021,7 +2912,6 @@ files = [ name = "pyjwt" version = "2.8.0" description = "JSON Web Token implementation in Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3039,7 +2929,6 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] name = "pypdf2" version = "1.28.6" description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" -category = "main" optional = false python-versions = ">=2.7" files = [ @@ -3051,7 +2940,6 @@ files = [ name = "pyrsistent" version = "0.20.0" description = "Persistent/Functional/Immutable data structures" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3093,7 +2981,6 @@ files = [ name = "pytest" version = "7.4.0" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3116,7 +3003,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-cov" version = "3.0.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3135,7 +3021,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-env" version = "0.8.2" description = "py.test plugin that allows you to add environment variables." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3153,7 +3038,6 @@ test = ["coverage (>=7.2.7)", "pytest-mock (>=3.10)"] name = "pytest-forked" version = "1.6.0" description = "run tests in isolated forked subprocesses" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3169,7 +3053,6 @@ pytest = ">=3.10" name = "pytest-mock" version = "3.11.1" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3187,7 +3070,6 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "pytest-mock-resources" version = "2.9.1" description = "A pytest plugin for easily instantiating reproducible mock resources." -category = "dev" optional = false python-versions = ">=3.7,<4" files = [ @@ -3218,7 +3100,6 @@ redshift = ["boto3", "filelock", "moto", "python-on-whales (>=0.22.0)", "sqlpars name = "pytest-xdist" version = "2.5.0" description = "pytest xdist plugin for distributed testing and loop-on-failing modes" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3240,7 +3121,6 @@ testing = ["filelock"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -3255,7 +3135,6 @@ six = ">=1.5" name = "python-dotenv" version = "1.0.0" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3270,7 +3149,6 @@ cli = ["click (>=5.0)"] name = "python-json-logger" version = "2.0.7" description = "A python library adding a json log formatter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3282,7 +3160,6 @@ files = [ name = "python-magic" version = "0.4.27" description = "File type identification using libmagic" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -3294,7 +3171,6 @@ files = [ name = "python-on-whales" version = "0.67.0" description = "A Docker client for Python, designed to be fun and intuitive!" -category = "dev" optional = false python-versions = "<4,>=3.8" files = [ @@ -3303,7 +3179,7 @@ files = [ ] [package.dependencies] -pydantic = ">=1.9,<2.0.0 || >=2.1.0,<3" +pydantic = ">=1.9,<2.0.dev0 || >=2.1.dev0,<3" requests = "*" tqdm = "*" typer = ">=0.4.1" @@ -3316,7 +3192,6 @@ test = ["pytest"] name = "pytz" version = "2021.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -3328,7 +3203,6 @@ files = [ name = "pytzdata" version = "2020.1" description = "The Olson timezone database for Python." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3340,7 +3214,6 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" -category = "dev" optional = false python-versions = "*" files = [ @@ -3364,7 +3237,6 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3386,6 +3258,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -3424,7 +3297,6 @@ files = [ name = "pyzmq" version = "25.1.1" description = "Python bindings for 0MQ" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3530,7 +3402,6 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} name = "redis" version = "5.0.1" description = "Python client for Redis database and key-value store" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3549,7 +3420,6 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3571,7 +3441,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-file" version = "1.5.1" description = "File transport adapter for Requests" -category = "main" optional = false python-versions = "*" files = [ @@ -3587,7 +3456,6 @@ six = "*" name = "requests-mock" version = "1.11.0" description = "Mock out responses from the requests package" -category = "dev" optional = false python-versions = "*" files = [ @@ -3607,7 +3475,6 @@ test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "tes name = "requests-toolbelt" version = "1.0.0" description = "A utility belt for advanced users of python-requests" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3622,7 +3489,6 @@ requests = ">=2.0.1,<3.0.0" name = "responses" version = "0.24.1" description = "A utility library for mocking out the `requests` Python library." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3642,7 +3508,6 @@ tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asy name = "rfc3987" version = "1.3.8" description = "Parsing and validation of URIs (RFC 3986) and IRIs (RFC 3987)" -category = "dev" optional = false python-versions = "*" files = [ @@ -3654,7 +3519,6 @@ files = [ name = "roundrobin" version = "0.0.4" description = "Collection of roundrobin utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -3665,7 +3529,6 @@ files = [ name = "rsa" version = "4.7.2" description = "Pure-Python RSA implementation" -category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -3680,7 +3543,6 @@ pyasn1 = ">=0.1.3" name = "s3transfer" version = "0.10.0" description = "An Amazon S3 Transfer Manager" -category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -3698,7 +3560,6 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] name = "setuptools" version = "69.0.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3715,7 +3576,6 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "simple-salesforce" version = "1.12.5" description = "A basic Salesforce.com REST API client." -category = "main" optional = false python-versions = "*" files = [ @@ -3735,7 +3595,6 @@ zeep = "*" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3747,7 +3606,6 @@ files = [ name = "smartypants" version = "2.0.1" description = "Python with the SmartyPants" -category = "main" optional = false python-versions = "*" files = [ @@ -3758,7 +3616,6 @@ files = [ name = "sqlalchemy" version = "1.4.49" description = "Database Abstraction Library" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -3840,7 +3697,6 @@ sqlcipher = ["sqlcipher3-binary"] name = "sqlalchemy-stubs" version = "0.4" description = "SQLAlchemy stubs and mypy plugin" -category = "dev" optional = false python-versions = "*" files = [ @@ -3856,7 +3712,6 @@ typing-extensions = ">=3.7.4" name = "sqlalchemy2-stubs" version = "0.0.2a35" description = "Typing Stubs for SQLAlchemy 1.4" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3871,7 +3726,6 @@ typing-extensions = ">=3.7.4" name = "statsd" version = "3.3.0" description = "A simple statsd client." -category = "main" optional = false python-versions = "*" files = [ @@ -3883,7 +3737,6 @@ files = [ name = "strict-rfc3339" version = "0.7" description = "Strict, simple, lightweight RFC3339 functions" -category = "dev" optional = false python-versions = "*" files = [ @@ -3894,7 +3747,6 @@ files = [ name = "tldextract" version = "3.4.4" description = "Accurately separates a URL's subdomain, domain, and public suffix, using the Public Suffix List (PSL). By default, this includes the public ICANN TLDs and their exceptions. You can optionally support the Public Suffix List's private domains as well." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3912,7 +3764,6 @@ requests-file = ">=1.4" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3924,7 +3775,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3936,7 +3786,6 @@ files = [ name = "tqdm" version = "4.66.1" description = "Fast, Extensible Progress Meter" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3957,7 +3806,6 @@ telegram = ["requests"] name = "typer" version = "0.9.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3979,7 +3827,6 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. name = "types-boto" version = "2.49.18.9" description = "Typing stubs for boto" -category = "dev" optional = false python-versions = "*" files = [ @@ -3991,7 +3838,6 @@ files = [ name = "types-mock" version = "4.0.15.2" description = "Typing stubs for mock" -category = "dev" optional = false python-versions = "*" files = [ @@ -4003,7 +3849,6 @@ files = [ name = "types-pyopenssl" version = "23.3.0.0" description = "Typing stubs for pyOpenSSL" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4018,7 +3863,6 @@ cryptography = ">=35.0.0" name = "types-python-dateutil" version = "2.8.19.14" description = "Typing stubs for python-dateutil" -category = "dev" optional = false python-versions = "*" files = [ @@ -4030,7 +3874,6 @@ files = [ name = "types-pytz" version = "2022.7.1.2" description = "Typing stubs for pytz" -category = "dev" optional = false python-versions = "*" files = [ @@ -4042,7 +3885,6 @@ files = [ name = "types-redis" version = "4.6.0.5" description = "Typing stubs for redis" -category = "dev" optional = false python-versions = "*" files = [ @@ -4058,7 +3900,6 @@ types-pyOpenSSL = "*" name = "types-requests" version = "2.31.0.2" description = "Typing stubs for requests" -category = "dev" optional = false python-versions = "*" files = [ @@ -4073,7 +3914,6 @@ types-urllib3 = "*" name = "types-urllib3" version = "1.26.25.14" description = "Typing stubs for urllib3" -category = "dev" optional = false python-versions = "*" files = [ @@ -4085,7 +3925,6 @@ files = [ name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4097,7 +3936,6 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -4109,7 +3947,6 @@ files = [ name = "unidecode" version = "1.3.6" description = "ASCII transliterations of Unicode text" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -4121,7 +3958,6 @@ files = [ name = "urllib3" version = "1.26.18" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -4138,7 +3974,6 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "vine" version = "5.1.0" description = "Python promises." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -4150,7 +3985,6 @@ files = [ name = "wcwidth" version = "0.2.12" description = "Measures the displayed width of unicode strings in a terminal" -category = "main" optional = false python-versions = "*" files = [ @@ -4162,7 +3996,6 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" -category = "main" optional = false python-versions = "*" files = [ @@ -4174,7 +4007,6 @@ files = [ name = "werkzeug" version = "2.3.7" description = "The comprehensive WSGI web application library." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4192,7 +4024,6 @@ watchdog = ["watchdog (>=2.3)"] name = "xmltodict" version = "0.13.0" description = "Makes working with XML feel like you are working with JSON" -category = "dev" optional = false python-versions = ">=3.4" files = [ @@ -4204,7 +4035,6 @@ files = [ name = "yarl" version = "1.9.3" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4308,7 +4138,6 @@ multidict = ">=4.0" name = "zeep" version = "4.2.1" description = "A Python SOAP client" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4336,7 +4165,6 @@ xmlsec = ["xmlsec (>=0.6.1)"] name = "zope-event" version = "5.0" description = "Very basic event publishing system" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4355,7 +4183,6 @@ test = ["zope.testrunner"] name = "zope-interface" version = "6.1" description = "Interfaces for Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4408,4 +4235,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "~3.10.9" -content-hash = "98564f0ca361b4467614a26152b33a3d5c491dca89cbdc5ad20e5da80fed7744" +content-hash = "2a0871640898c6922505047d0efee3d2b911a1b1ee18688cdc82fdfa7932c46b" diff --git a/pyproject.toml b/pyproject.toml index 0055065ab8..7b754e33ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ Werkzeug = "2.3.7" MarkupSafe = "2.1.3" # REVIEW: v2 is using sha512 instead of sha1 by default (in v1) itsdangerous = "2.1.2" -notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", rev = "52.0.18" } +notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", rev = "69b6eee0c715fa4c3fe9ac48e4ccd6e3d4c1e239" } # rsa = "4.9 # awscli 1.22.38 depends on rsa<4.8 typing-extensions = "4.7.1" greenlet = "2.0.2" From 2de81548561984bbe1f8d2be78d2fc2a7541664e Mon Sep 17 00:00:00 2001 From: Jumana B Date: Mon, 22 Jan 2024 10:53:12 -0500 Subject: [PATCH 05/33] Revert "Add API Validation" (#2088) --- app/api_key/rest.py | 3 ++- app/authentication/auth.py | 11 +++++++++ app/dao/api_key_dao.py | 23 +++--------------- tests/app/api_key/test_rest.py | 5 +--- .../app/authentication/test_authentication.py | 14 ++++------- tests/app/dao/test_api_key_dao.py | 24 +++++-------------- .../notifications/test_post_notifications.py | 12 +++------- 7 files changed, 31 insertions(+), 61 deletions(-) diff --git a/app/api_key/rest.py b/app/api_key/rest.py index 1aa6b28fd9..8ad96ec1e8 100644 --- a/app/api_key/rest.py +++ b/app/api_key/rest.py @@ -121,7 +121,8 @@ def revoke_api_keys(): # Step 1 try: - api_key_token = api_key_data["token"] + # take last 36 chars of string so that it works even if the full key is provided. + api_key_token = api_key_data["token"][-36:] api_key = get_api_key_by_secret(api_key_token) except Exception: current_app.logger.error( diff --git a/app/authentication/auth.py b/app/authentication/auth.py index 8bef40a005..144c89079f 100644 --- a/app/authentication/auth.py +++ b/app/authentication/auth.py @@ -152,8 +152,19 @@ def requires_auth(): def _auth_by_api_key(auth_token): + # TODO: uncomment this when the grace period for the token prefix is over + # orig_token = auth_token + try: + # take last 36 chars of string so that it works even if the full key is provided. + auth_token = auth_token[-36:] api_key = get_api_key_by_secret(auth_token) + + # TODO: uncomment this when the grace period for the token prefix is over + # check for token prefix + # if current_app.config["API_KEY_PREFIX"] not in orig_token: + # raise AuthError("Invalid token: you must re-generate your API key to continue using GC Notify", 403, service_id=api_key.service.id, api_key_id=api_key.id) + except NoResultFound: raise AuthError("Invalid token: API key not found", 403) _auth_with_api_key(api_key, api_key.service) diff --git a/app/dao/api_key_dao.py b/app/dao/api_key_dao.py index d669ee61af..679467f4f4 100644 --- a/app/dao/api_key_dao.py +++ b/app/dao/api_key_dao.py @@ -76,30 +76,13 @@ def update_compromised_api_key_info(service_id, api_key_id, compromised_info): db.session.add(api_key) -def get_api_key_by_secret(secret, service_id=None): - # Check the first part of the secret is the gc prefix - if current_app.config["API_KEY_PREFIX"] != secret[: len(current_app.config["API_KEY_PREFIX"])]: - raise NoResultFound() - - # Check if the remaining part of the secret is a the valid api key - token = secret[-36:] - signed_with_all_keys = signer_api_key.sign_with_all_keys(str(token)) +def get_api_key_by_secret(secret): + signed_with_all_keys = signer_api_key.sign_with_all_keys(str(secret)) for signed_secret in signed_with_all_keys: try: - api_key = db.on_reader().query(ApiKey).filter_by(_secret=signed_secret).options(joinedload("service")).one() + return db.on_reader().query(ApiKey).filter_by(_secret=signed_secret).options(joinedload("service")).one() except NoResultFound: pass - - # Check the middle portion of the secret is the valid service id - if api_key.service_id: - if len(secret) >= 79: - service_id_from_token = str(secret[-73:-37]) - if str(api_key.service_id) != service_id_from_token: - raise NoResultFound() - else: - raise NoResultFound() - if api_key: - return api_key raise NoResultFound() diff --git a/tests/app/api_key/test_rest.py b/tests/app/api_key/test_rest.py index 9d78074688..0def4b7884 100644 --- a/tests/app/api_key/test_rest.py +++ b/tests/app/api_key/test_rest.py @@ -90,9 +90,6 @@ def test_revoke_api_keys_with_valid_auth_revokes_and_notifies_user(self, client, api_key_1 = create_api_key(service, key_type=KEY_TYPE_NORMAL, key_name="Key 1") unsigned_secret = get_unsigned_secret(api_key_1.id) - # Create token expected from the frontend - unsigned_secret = f"gcntfy-keyname-{service.id}-{unsigned_secret}" - sre_auth_header = create_sre_authorization_header() response = client.post( url_for("sre_tools.revoke_api_keys"), @@ -101,7 +98,7 @@ def test_revoke_api_keys_with_valid_auth_revokes_and_notifies_user(self, client, ) # Get api key from DB - api_key_1 = get_api_key_by_secret(unsigned_secret) + api_key_1 = get_api_key_by_secret(api_key_1.secret) assert response.status_code == 201 assert api_key_1.expiry_date is not None assert api_key_1.compromised_key_info["type"] == "cds-tester" diff --git a/tests/app/authentication/test_authentication.py b/tests/app/authentication/test_authentication.py index 4ed075497c..16299507b9 100644 --- a/tests/app/authentication/test_authentication.py +++ b/tests/app/authentication/test_authentication.py @@ -135,20 +135,18 @@ def test_admin_auth_should_not_allow_api_key_scheme(client, sample_api_key): @pytest.mark.parametrize("scheme", ["ApiKey-v1", "apikey-v1", "APIKEY-V1"]) def test_should_allow_auth_with_api_key_scheme(client, sample_api_key, scheme): api_key_secret = get_unsigned_secret(sample_api_key.id) - unsigned_secret = f"gcntfy-keyname-{sample_api_key.service_id}-{api_key_secret}" - response = client.get("/notifications", headers={"Authorization": f"{scheme} {unsigned_secret}"}) + + response = client.get("/notifications", headers={"Authorization": f"{scheme} {api_key_secret}"}) assert response.status_code == 200 -def test_should_NOT_allow_auth_with_api_key_scheme_with_incorrect_format(client, sample_api_key): +def test_should_allow_auth_with_api_key_scheme_36_chars_or_longer(client, sample_api_key): api_key_secret = "fhsdkjhfdsfhsd" + get_unsigned_secret(sample_api_key.id) response = client.get("/notifications", headers={"Authorization": f"ApiKey-v1 {api_key_secret}"}) - assert response.status_code == 403 - error_message = json.loads(response.get_data()) - assert error_message["message"] == {"token": ["Invalid token: API key not found"]} + assert response.status_code == 200 def test_should_not_allow_invalid_api_key(client, sample_api_key): @@ -164,9 +162,7 @@ def test_should_not_allow_expired_api_key(client, sample_api_key): expire_api_key(service_id=sample_api_key.service_id, api_key_id=sample_api_key.id) - unsigned_secret = f"gcntfy-keyname-{sample_api_key.service_id}-{api_key_secret}" - - response = client.get("/notifications", headers={"Authorization": f"ApiKey-v1 {unsigned_secret}"}) + response = client.get("/notifications", headers={"Authorization": f"ApiKey-v1 {api_key_secret}"}) assert response.status_code == 403 error_message = json.loads(response.get_data()) diff --git a/tests/app/dao/test_api_key_dao.py b/tests/app/dao/test_api_key_dao.py index 6a71490f08..9535ce0343 100644 --- a/tests/app/dao/test_api_key_dao.py +++ b/tests/app/dao/test_api_key_dao.py @@ -103,24 +103,12 @@ def test_get_unsigned_secret_returns_key(sample_api_key): assert unsigned_api_key == sample_api_key.secret -class TestGetAPIKeyBySecret: - def test_get_api_key_by_secret(self, sample_api_key): - secret = get_unsigned_secret(sample_api_key.id) - # Create token expected from the frontend - unsigned_secret = f"gcntfy-keyname-{sample_api_key.service_id}-{secret}" - assert get_api_key_by_secret(unsigned_secret).id == sample_api_key.id - - with pytest.raises(NoResultFound): - get_api_key_by_secret("nope") - - # Test getting secret without the keyname prefix - with pytest.raises(NoResultFound): - get_api_key_by_secret(str(sample_api_key.id)) - - # Test the service_name isnt part of the secret - with pytest.raises(NoResultFound): - # import pdb; pdb.set_trace() - get_api_key_by_secret(f"gcntfy-keyname-hello-{secret}") +def test_get_api_key_by_secret(sample_api_key): + unsigned_secret = get_unsigned_secret(sample_api_key.id) + assert get_api_key_by_secret(unsigned_secret).id == sample_api_key.id + + with pytest.raises(NoResultFound): + get_api_key_by_secret("nope") def test_should_not_allow_duplicate_key_names_per_service(sample_api_key, fake_uuid): diff --git a/tests/app/v2/notifications/test_post_notifications.py b/tests/app/v2/notifications/test_post_notifications.py index 2e3d1d36b2..84b868168d 100644 --- a/tests/app/v2/notifications/test_post_notifications.py +++ b/tests/app/v2/notifications/test_post_notifications.py @@ -1515,8 +1515,6 @@ def __send_sms(): key_type=key_type, ) save_model_api_key(api_key) - api_key_secret = get_unsigned_secret(api_key.id) - unsigned_secret = f"gcntfy-keyname-{api_key.service_id}-{api_key_secret}" with set_config_values(notify_api, {"REDIS_ENABLED": True}): response = client.post( @@ -1524,7 +1522,7 @@ def __send_sms(): data=json.dumps(data), headers=[ ("Content-Type", "application/json"), - ("Authorization", f"ApiKey-v1 {unsigned_secret}"), + ("Authorization", f"ApiKey-v1 {get_unsigned_secret(api_key.id)}"), ], ) return response @@ -1565,8 +1563,6 @@ def __send_sms(): key_type=key_type, ) save_model_api_key(api_key) - api_key_secret = get_unsigned_secret(api_key.id) - unsigned_secret = f"gcntfy-keyname-{api_key.service_id}-{api_key_secret}" with set_config_values(notify_api, {"REDIS_ENABLED": True}): response = client.post( @@ -1574,7 +1570,7 @@ def __send_sms(): data=json.dumps(data), headers=[ ("Content-Type", "application/json"), - ("Authorization", f"ApiKey-v1 {unsigned_secret}"), + ("Authorization", f"ApiKey-v1 {get_unsigned_secret(api_key.id)}"), ], ) return response @@ -1611,8 +1607,6 @@ def __send_sms(): key_type=key_type, ) save_model_api_key(api_key) - api_key_secret = get_unsigned_secret(api_key.id) - unsigned_secret = f"gcntfy-keyname-{api_key.service_id}-{api_key_secret}" with set_config_values(notify_api, {"REDIS_ENABLED": True}): response = client.post( @@ -1620,7 +1614,7 @@ def __send_sms(): data=json.dumps(data), headers=[ ("Content-Type", "application/json"), - ("Authorization", f"ApiKey-v1 {unsigned_secret}"), + ("Authorization", f"ApiKey-v1 {get_unsigned_secret(api_key.id)}"), ], ) return response From 074cbfd7c9d7b924e3acf7893c48130a2acc9bab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 16:03:16 +0000 Subject: [PATCH 06/33] chore(deps): update all non-major github action dependencies (#2087) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jumana B --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/performance.yml | 2 +- .github/workflows/test.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e9b4547ef3..5b821d9279 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,15 +27,15 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Initialize CodeQL - uses: github/codeql-action/init@1500a131381b66de0c52ac28abb13cd79f4b7ecc # v2.22.12 + uses: github/codeql-action/init@8b7fcbfac2aae0e6c24d9f9ebd5830b1290b18e4 # v2.23.0 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@1500a131381b66de0c52ac28abb13cd79f4b7ecc # v2.22.12 + uses: github/codeql-action/autobuild@8b7fcbfac2aae0e6c24d9f9ebd5830b1290b18e4 # v2.23.0 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1500a131381b66de0c52ac28abb13cd79f4b7ecc # v2.22.12 + uses: github/codeql-action/analyze@8b7fcbfac2aae0e6c24d9f9ebd5830b1290b18e4 # v2.23.0 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index b689c1b236..8c52bdd979 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -16,7 +16,7 @@ jobs: python-version: '3.10' - name: Upgrade pip run: python -m pip install --upgrade pip - - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + - uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c # v3.3.3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7170ed1dce..96e08b4935 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -25,7 +25,7 @@ jobs: python-version: '3.10' - name: Upgrade pip run: python -m pip install --upgrade pip - - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2 + - uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c # v3.3.3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} From 66d72b56c0aece052528066dbfabae2f6f66aa89 Mon Sep 17 00:00:00 2001 From: William B <7444334+whabanks@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:12:41 -0500 Subject: [PATCH 07/33] Add a retry mechanism when fetching email attachements (#2079) * Add a retry mechanism when fetching email attachements * Remove unnecessary exception handling specificity * Revert bad commit * Remove test code * Remove unused import --- app/delivery/send_to_providers.py | 31 +++--- tests/app/delivery/test_send_to_providers.py | 110 +++++++++++++++---- 2 files changed, 107 insertions(+), 34 deletions(-) diff --git a/app/delivery/send_to_providers.py b/app/delivery/send_to_providers.py index bc6bbb67bc..33590c7667 100644 --- a/app/delivery/send_to_providers.py +++ b/app/delivery/send_to_providers.py @@ -1,7 +1,6 @@ import base64 import os import re -import urllib.request from datetime import datetime from typing import Dict from uuid import UUID @@ -17,6 +16,8 @@ SMSMessageTemplate, ) from unidecode import unidecode +from urllib3 import PoolManager +from urllib3.util import Retry from app import bounce_rate_client, clients, document_download_client, statsd_client from app.celery.research_mode_tasks import send_email_response, send_sms_response @@ -231,27 +232,29 @@ def send_email_to_provider(notification: Notification): check_file_url(personalisation_data[key]["document"], notification.id) sending_method = personalisation_data[key]["document"].get("sending_method") direct_file_url = personalisation_data[key]["document"]["direct_file_url"] + filename = personalisation_data[key]["document"].get("filename") + mime_type = personalisation_data[key]["document"].get("mime_type") document_id = personalisation_data[key]["document"]["id"] scan_verdict_response = document_download_client.check_scan_verdict(service.id, document_id, sending_method) check_for_malware_errors(scan_verdict_response.status_code, notification) current_app.logger.info(f"scan_verdict for document_id {document_id} is {scan_verdict_response.json()}") if sending_method == "attach": try: - req = urllib.request.Request(direct_file_url) - with urllib.request.urlopen(req) as response: - buffer = response.read() - filename = personalisation_data[key]["document"].get("filename") - mime_type = personalisation_data[key]["document"].get("mime_type") - attachments.append( - { - "name": filename, - "data": buffer, - "mime_type": mime_type, - } - ) + retries = Retry(total=5) + http = PoolManager(retries=retries) + + response = http.request("GET", url=direct_file_url) + attachments.append( + { + "name": filename, + "data": response.data, + "mime_type": mime_type, + } + ) except Exception as e: current_app.logger.error(f"Could not download and attach {direct_file_url}\nException: {e}") - del personalisation_data[key] + del personalisation_data[key] + else: personalisation_data[key] = personalisation_data[key]["document"]["url"] diff --git a/tests/app/delivery/test_send_to_providers.py b/tests/app/delivery/test_send_to_providers.py index 57cd6fba41..b6c43b299a 100644 --- a/tests/app/delivery/test_send_to_providers.py +++ b/tests/app/delivery/test_send_to_providers.py @@ -1,8 +1,9 @@ import uuid from collections import namedtuple from datetime import datetime +from http.client import HTTPMessage from unittest import TestCase -from unittest.mock import ANY, MagicMock, call +from unittest.mock import ANY, MagicMock, Mock, call import pytest from flask import current_app @@ -884,6 +885,82 @@ def test_send_email_to_provider_should_format_email_address(sample_email_notific ) +def test_file_attachment_retry(mocker, notify_db, notify_db_session): + template = create_sample_email_template(notify_db, notify_db_session, content="Here is your ((file))") + + class mock_response: + status_code = 200 + + def json(): + return {"av-status": "clean"} + + mocker.patch("app.delivery.send_to_providers.document_download_client.check_scan_verdict", return_value=mock_response) + + personalisation = { + "file": document_download_response( + { + "direct_file_url": "http://foo.bar/direct_file_url", + "url": "http://foo.bar/url", + "mime_type": "application/pdf", + } + ) + } + personalisation["file"]["document"]["sending_method"] = "attach" + personalisation["file"]["document"]["filename"] = "file.txt" + + db_notification = save_notification(create_notification(template=template, personalisation=personalisation)) + + mocker.patch("app.delivery.send_to_providers.statsd_client") + mocker.patch("app.aws_ses_client.send_email", return_value="reference") + + getconn_mock = mocker.patch("urllib3.connectionpool.HTTPConnectionPool._new_conn") + getconn_mock.return_value.getresponse.side_effect = [ + Mock(status=500, msg=HTTPMessage()), + Mock(status=429, msg=HTTPMessage()), + Mock(status=400, msg=HTTPMessage()), + Mock(status=404, msg=HTTPMessage()), + Mock(status=200, msg=HTTPMessage()), + ] + + mock_logger = mocker.patch("app.delivery.send_to_providers.current_app.logger.error") + send_to_providers.send_email_to_provider(db_notification) + assert mock_logger.call_count == 0 + + +def test_file_attachment_max_retries(mocker, notify_db, notify_db_session): + template = create_sample_email_template(notify_db, notify_db_session, content="Here is your ((file))") + + class mock_response: + status_code = 200 + + def json(): + return {"av-status": "clean"} + + mocker.patch("app.delivery.send_to_providers.document_download_client.check_scan_verdict", return_value=mock_response) + + personalisation = { + "file": document_download_response( + { + "direct_file_url": "http://foo.bar/direct_file_url", + "url": "http://foo.bar/url", + "mime_type": "application/pdf", + } + ) + } + personalisation["file"]["document"]["sending_method"] = "attach" + personalisation["file"]["document"]["filename"] = "file.txt" + + db_notification = save_notification(create_notification(template=template, personalisation=personalisation)) + + mocker.patch("app.delivery.send_to_providers.statsd_client") + mocker.patch("app.aws_ses_client.send_email", return_value="reference") + + mock_logger = mocker.patch("app.delivery.send_to_providers.current_app.logger.error") + send_to_providers.send_email_to_provider(db_notification) + assert mock_logger.call_count == 1 + assert "Max retries exceeded" in mock_logger.call_args[0][0] + + @pytest.mark.parametrize( "filename_attribute_present, filename, expected_filename", [ @@ -930,28 +1007,26 @@ def json(): statsd_mock = mocker.patch("app.delivery.send_to_providers.statsd_client") send_mock = mocker.patch("app.aws_ses_client.send_email", return_value="reference") - request_mock = mocker.patch( - "app.delivery.send_to_providers.urllib.request.Request", - return_value="request_mock", + mocker.patch("app.delivery.send_to_providers.Retry") + + response_return_mock = MagicMock() + response_return_mock.status = 200 + response_return_mock.data = "Hello there!" + + response_mock = mocker.patch( + "app.delivery.send_to_providers.PoolManager.request", + return_value=response_return_mock, ) - # See https://stackoverflow.com/a/34929900 - cm = MagicMock() - cm.read.return_value = "request_content" - cm.__enter__.return_value = cm - cm.getcode = lambda: 200 - urlopen_mock = mocker.patch("app.delivery.send_to_providers.urllib.request.urlopen") - urlopen_mock.return_value = cm send_to_providers.send_email_to_provider(db_notification) attachments = [] if filename_attribute_present: - request_mock.assert_called_once_with("http://foo.bar/direct_file_url") - urlopen_mock.assert_called_once_with("request_mock") + response_mock.assert_called_with("GET", url="http://foo.bar/direct_file_url") attachments = [ { - "data": "request_content", "name": expected_filename, + "data": "Hello there!", "mime_type": "application/pdf", } ] @@ -1001,12 +1076,7 @@ def test_notification_with_bad_file_attachment_url(mocker, notify_db, notify_db_ db_notification = save_notification(create_notification(template=template, personalisation=personalisation)) - # See https://stackoverflow.com/a/34929900 - cm = MagicMock() - cm.read.return_value = "request_content" - cm.__enter__.return_value = cm - urlopen_mock = mocker.patch("app.delivery.send_to_providers.urllib.request.urlopen") - urlopen_mock.return_value = cm + mocker.patch("app.delivery.send_to_providers.Retry") with pytest.raises(InvalidUrlException): send_to_providers.send_email_to_provider(db_notification) From bc5d81e893e742a9cb3864059513ccc7fec2ccf8 Mon Sep 17 00:00:00 2001 From: William B <7444334+whabanks@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:26:19 -0500 Subject: [PATCH 08/33] Update Celery config to use UTC time (#2080) * Add a retry mechanism when fetching email attachements * Remove unnecessary exception handling specificity * Revert bad commit * Enable/use UTC not local time in celery and beat conf * Fix bad commit... --- app/celery/celery.py | 1 + app/config.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/celery/celery.py b/app/celery/celery.py index b1758f73c0..be5a5593aa 100644 --- a/app/celery/celery.py +++ b/app/celery/celery.py @@ -48,6 +48,7 @@ def init_app(self, app): "beat_schedule": app.config["CELERYBEAT_SCHEDULE"], "imports": app.config["CELERY_IMPORTS"], "task_serializer": app.config["CELERY_TASK_SERIALIZER"], + "enable_utc": app.config["CELERY_ENABLE_UTC"], "timezone": app.config["CELERY_TIMEZONE"], "broker_transport_options": app.config["BROKER_TRANSPORT_OPTIONS"], "task_queues": app.config["CELERY_QUEUES"], diff --git a/app/config.py b/app/config.py index 10ec7f5a6f..d760fbb1d4 100644 --- a/app/config.py +++ b/app/config.py @@ -359,7 +359,7 @@ class Config(object): "queue_name_prefix": NOTIFICATION_QUEUE_PREFIX, } CELERY_ENABLE_UTC = True - CELERY_TIMEZONE = os.getenv("TIMEZONE", "America/Toronto") + CELERY_TIMEZONE = os.getenv("TIMEZONE", "UTC") CELERY_ACCEPT_CONTENT = ["json"] CELERY_TASK_SERIALIZER = "json" CELERY_IMPORTS = ( From ee78d288b355d9c34bceef241c2b274560c1db99 Mon Sep 17 00:00:00 2001 From: Jumana B Date: Tue, 23 Jan 2024 09:05:51 -0500 Subject: [PATCH 09/33] Added a last_used column for the api_key (#2091) * Added a last_used column for the api_key * added migration file --------- Co-authored-by: William B <7444334+whabanks@users.noreply.github.com> --- app/models.py | 1 + .../0443_add_apikey_last_used_column.py | 22 +++++++++++++++++++ tests/app/dao/test_api_key_dao.py | 1 + 3 files changed, 24 insertions(+) create mode 100644 migrations/versions/0443_add_apikey_last_used_column.py diff --git a/app/models.py b/app/models.py index fd640e1473..704ccf798a 100644 --- a/app/models.py +++ b/app/models.py @@ -918,6 +918,7 @@ class ApiKey(BaseModel, Versioned): created_by = db.relationship("User") created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey("users.id"), index=True, nullable=False) compromised_key_info = db.Column(JSONB(none_as_null=True), nullable=True, default={}) + last_used_timestamp = db.Column(db.DateTime, index=False, unique=False, nullable=True, default=None) __table_args__ = ( Index( diff --git a/migrations/versions/0443_add_apikey_last_used_column.py b/migrations/versions/0443_add_apikey_last_used_column.py new file mode 100644 index 0000000000..23d532d983 --- /dev/null +++ b/migrations/versions/0443_add_apikey_last_used_column.py @@ -0,0 +1,22 @@ +""" +Revision ID: 0443_add_apikey_last_used_column +Revises: 0442_add_heartbeat_templates +Create Date: 2022-09-21 00:00:00 +""" +from datetime import datetime + +import sqlalchemy as sa +from alembic import op + +revision = "0443_add_apikey_last_used_column" +down_revision = "0442_add_heartbeat_templates" + + +def upgrade(): + op.add_column("api_keys", sa.Column("last_used_timestamp", sa.DateTime(), nullable=True)) + op.add_column("api_keys_history", sa.Column("last_used_timestamp", sa.DateTime(), nullable=True)) + + +def downgrade(): + op.drop_column("api_keys", "last_used_timestamp") + op.drop_column("api_keys_history", "last_used_timestamp") diff --git a/tests/app/dao/test_api_key_dao.py b/tests/app/dao/test_api_key_dao.py index 9535ce0343..7642325ded 100644 --- a/tests/app/dao/test_api_key_dao.py +++ b/tests/app/dao/test_api_key_dao.py @@ -35,6 +35,7 @@ def test_save_api_key_should_create_new_api_key_and_history(sample_service): assert len(all_api_keys) == 1 assert all_api_keys[0] == api_key assert api_key.version == 1 + assert api_key.last_used_timestamp is None all_history = api_key.get_history_model().query.all() assert len(all_history) == 1 From 0f36abb9865ed3e9989d0d480a026b1b047d0311 Mon Sep 17 00:00:00 2001 From: Steve Astels Date: Tue, 23 Jan 2024 14:39:28 -0500 Subject: [PATCH 10/33] Script to add rows to notification_history table (#2068) --- scripts/enlarge_db/README.md | 22 +++++++++++++ scripts/enlarge_db/enlarge_db.py | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 scripts/enlarge_db/README.md create mode 100644 scripts/enlarge_db/enlarge_db.py diff --git a/scripts/enlarge_db/README.md b/scripts/enlarge_db/README.md new file mode 100644 index 0000000000..b36090390b --- /dev/null +++ b/scripts/enlarge_db/README.md @@ -0,0 +1,22 @@ +# Enlarge DB + +## Purpose + +The purpose of this script is add rows to the notification_history table. This is useful in estimating how long database-related infrastructure operations will take when performed on a database the same size as that in production. + +## How to use + +The script should be run in the same environment as api. Locally this can be in the api repo devcontainer, while in AWS the api kubernetes pod would be preferred. + +To add 2000 rows to the table with a client_reference of "test2000" run + +``` +cd scripts/enlarge_db +python enlarge_db.py -n 2000 -r test2000 +``` + +The new notifications are added in batches to improve performance, with a default batch size of 10000. You may use a different batch with the `-c` parameter, for example + +``` +python enlarge_db.py -n 2000 -c 101 -r test2000x101 +``` diff --git a/scripts/enlarge_db/enlarge_db.py b/scripts/enlarge_db/enlarge_db.py new file mode 100644 index 0000000000..649e6f3032 --- /dev/null +++ b/scripts/enlarge_db/enlarge_db.py @@ -0,0 +1,55 @@ + +import argparse +import sys +from datetime import datetime +from typing import List + +from flask import Flask + +sys.path.append("../..") +from app import create_app, create_uuid, db # noqa: E402 +from app.config import Config # noqa: E402 +from app.models import NotificationHistory # noqa: E402 + +DEFAULT_CHUNK_SIZE = 10000 + + +def create_notifications(n: int, ref: str) -> List[NotificationHistory]: + notifications = [ + NotificationHistory( + id=create_uuid(), + created_at=datetime.utcnow(), + template_id=Config.NEW_USER_EMAIL_VERIFICATION_TEMPLATE_ID, + template_version=1, + service_id=Config.NOTIFY_SERVICE_ID, + notification_type="email", + key_type='normal', + client_reference=ref, + ) + for _ in range(n) + ] + return notifications + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-n", "--notifications", default=1, type=int, help="number of notifications to add to the notification_history table (default 1)") + parser.add_argument("-r", "--reference", default="manually created", type=str, help="client reference to use for the notifications (default 'manually created')") + parser.add_argument("-c", "--chunksize", default=DEFAULT_CHUNK_SIZE, type=int, help=f"chunk size for bulk_save_objects (default {DEFAULT_CHUNK_SIZE})") + args = parser.parse_args() + + app = Flask("enlarge_db") + create_app(app) + + for notifications_done in range(0, args.notifications, args.chunksize): + notifications = create_notifications(min(args.chunksize, args.notifications - notifications_done), args.reference) + print(f"Adding {len(notifications)} notifications to notification_history") + with app.app_context(): + try: + db.session.bulk_save_objects(notifications) + db.session.commit() + except Exception as e: + print(f"Error adding notifications: {e}") + db.session.rollback() + sys.exit(1) + print(f"Done {notifications_done+len(notifications)} / {args.notifications}") From 463becbb9c87482c06d6bc2605ebee034e78357f Mon Sep 17 00:00:00 2001 From: Jumana B Date: Tue, 23 Jan 2024 15:43:23 -0500 Subject: [PATCH 11/33] Task/read from last used (#2092) * Read from last_used from api_key table * Add tests to Api key last used --- app/dao/api_key_dao.py | 7 +++++ app/dao/fact_notification_status_dao.py | 29 +++++++++++++------ app/notifications/process_notifications.py | 12 ++++++++ tests/app/dao/test_api_key_dao.py | 12 ++++++++ .../dao/test_fact_notification_status_dao.py | 8 +++++ tests/app/db.py | 3 +- .../test_process_notification.py | 7 +++++ 7 files changed, 68 insertions(+), 10 deletions(-) diff --git a/app/dao/api_key_dao.py b/app/dao/api_key_dao.py index 679467f4f4..f6a707bf76 100644 --- a/app/dao/api_key_dao.py +++ b/app/dao/api_key_dao.py @@ -68,6 +68,13 @@ def expire_api_key(service_id, api_key_id): db.session.add(api_key) +@transactional +def update_last_used_api_key(api_key_id, last_used=None) -> None: + api_key = ApiKey.query.filter_by(id=api_key_id).one() + api_key.last_used_timestamp = last_used if last_used else datetime.utcnow() + db.session.add(api_key) + + @transactional @version_class(ApiKey) def update_compromised_api_key_info(service_id, api_key_id, compromised_info): diff --git a/app/dao/fact_notification_status_dao.py b/app/dao/fact_notification_status_dao.py index bf886fbfd6..e67903428e 100644 --- a/app/dao/fact_notification_status_dao.py +++ b/app/dao/fact_notification_status_dao.py @@ -336,24 +336,35 @@ def get_total_notifications_sent_for_api_key(api_key_id): def get_last_send_for_api_key(api_key_id): """ + SELECT last_used_timestamp as last_notification_created + FROM api_keys + WHERE id = 'api_key_id'; + + If last_used_timestamp is null, then check notifications table/ or notification_history. SELECT max(created_at) as last_notification_created FROM notifications WHERE api_key_id = 'api_key_id' GROUP BY api_key_id; """ - notification_table = ( - db.session.query(func.max(Notification.created_at).label("last_notification_created")) - .filter(Notification.api_key_id == api_key_id) - .all() + # Fetch last_used_timestamp from api_keys table + api_key_table = ( + db.session.query(ApiKey.last_used_timestamp.label("last_notification_created")).filter(ApiKey.id == api_key_id).all() ) - if not notification_table[0][0]: + if not api_key_table[0][0]: notification_table = ( - db.session.query(func.max(NotificationHistory.created_at).label("last_notification_created")) - .filter(NotificationHistory.api_key_id == api_key_id) + db.session.query(func.max(Notification.created_at).label("last_notification_created")) + .filter(Notification.api_key_id == api_key_id) .all() ) - notification_table = [] if notification_table[0][0] is None else notification_table - return notification_table + if not notification_table[0][0]: + notification_table = ( + db.session.query(func.max(NotificationHistory.created_at).label("last_notification_created")) + .filter(NotificationHistory.api_key_id == api_key_id) + .all() + ) + notification_table = [] if notification_table[0][0] is None else notification_table + return notification_table + return api_key_table def get_api_key_ranked_by_notifications_created(n_days_back): diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py index 56a2dc98d3..d3dc3304c8 100644 --- a/app/notifications/process_notifications.py +++ b/app/notifications/process_notifications.py @@ -15,6 +15,7 @@ from app.celery import provider_tasks from app.celery.letters_pdf_tasks import create_letters_pdf from app.config import QueueNames +from app.dao.api_key_dao import update_last_used_api_key from app.dao.notifications_dao import ( bulk_insert_notifications, dao_create_notification, @@ -133,6 +134,8 @@ def persist_notification( if redis_store.get(redis.daily_limit_cache_key(service.id)): redis_store.incr(redis.daily_limit_cache_key(service.id)) current_app.logger.info("{} {} created at {}".format(notification_type, notification_id, notification_created_at)) + if api_key_id: + update_last_used_api_key(api_key_id, notification_created_at) return notification @@ -298,6 +301,7 @@ def persist_notifications(notifications: List[VerifiedNotification]) -> List[Not """ lofnotifications = [] + api_key_last_used = None for notification in notifications: notification_created_at = notification.get("created_at") or datetime.utcnow() @@ -357,7 +361,15 @@ def persist_notifications(notifications: List[VerifiedNotification]) -> List[Not notification.get("notification_created_at"), # type: ignore ) ) + # If the bulk message is sent using an api key, we want to keep track of the last time the api key was used + # We will only update the api key once + api_key_id = notification.get("api_key_id") + if api_key_id: + api_key_last_used = datetime.utcnow() + if api_key_last_used: + update_last_used_api_key(api_key_id, api_key_last_used) bulk_insert_notifications(lofnotifications) + return lofnotifications diff --git a/tests/app/dao/test_api_key_dao.py b/tests/app/dao/test_api_key_dao.py index 7642325ded..5e23002171 100644 --- a/tests/app/dao/test_api_key_dao.py +++ b/tests/app/dao/test_api_key_dao.py @@ -14,6 +14,7 @@ resign_api_keys, save_model_api_key, update_compromised_api_key_info, + update_last_used_api_key, ) from app.models import KEY_TYPE_NORMAL, ApiKey from tests.app.db import create_api_key @@ -61,6 +62,17 @@ def test_expire_api_key_should_update_the_api_key_and_create_history_record(noti sorted_all_history[1].version = 2 +def test_last_used_should_update_the_api_key_and_not_create_history_record(notify_api, sample_api_key): + last_used = datetime.utcnow() + update_last_used_api_key(api_key_id=sample_api_key.id, last_used=last_used) + all_api_keys = get_model_api_keys(service_id=sample_api_key.service_id) + assert len(all_api_keys) == 1 + assert all_api_keys[0].last_used_timestamp == last_used + + all_history = sample_api_key.get_history_model().query.all() + assert len(all_history) == 1 + + def test_update_compromised_api_key_info_and_create_history_record(notify_api, sample_api_key): update_compromised_api_key_info( service_id=sample_api_key.service_id, api_key_id=sample_api_key.id, compromised_info={"key": "value"} diff --git a/tests/app/dao/test_fact_notification_status_dao.py b/tests/app/dao/test_fact_notification_status_dao.py index 6e6087d2cc..663cf859f8 100644 --- a/tests/app/dao/test_fact_notification_status_dao.py +++ b/tests/app/dao/test_fact_notification_status_dao.py @@ -356,6 +356,14 @@ def test_get_total_notifications_sent_for_api_key(notify_db_session): assert dict(api_key_stats_3) == dict([(EMAIL_TYPE, total_sends), (SMS_TYPE, total_sends)]) +def test_get_last_send_for_api_key_check_last_used(notify_db_session): + service = create_service(service_name="First Service") + api_key = create_api_key(service, last_used=datetime.utcnow()) + + last_send = get_last_send_for_api_key(str(api_key.id))[0][0] + assert last_send == api_key.last_used_timestamp + + def test_get_last_send_for_api_key(notify_db_session): service = create_service(service_name="First Service") api_key = create_api_key(service) diff --git a/tests/app/db.py b/tests/app/db.py index 9e79f5042c..1dacec37bc 100644 --- a/tests/app/db.py +++ b/tests/app/db.py @@ -551,7 +551,7 @@ def create_letter_rate( return rate -def create_api_key(service, key_type=KEY_TYPE_NORMAL, key_name=None): +def create_api_key(service, key_type=KEY_TYPE_NORMAL, key_name=None, last_used=None): id_ = uuid.uuid4() name = key_name if key_name else "{} api key {}".format(key_type, id_) @@ -563,6 +563,7 @@ def create_api_key(service, key_type=KEY_TYPE_NORMAL, key_name=None): key_type=key_type, id=id_, secret=uuid.uuid4(), + last_used_timestamp=last_used, ) db.session.add(api_key) db.session.commit() diff --git a/tests/app/notifications/test_process_notification.py b/tests/app/notifications/test_process_notification.py index bb194d3c28..eaa0c00da0 100644 --- a/tests/app/notifications/test_process_notification.py +++ b/tests/app/notifications/test_process_notification.py @@ -19,6 +19,7 @@ LETTER_TYPE, NORMAL, PRIORITY, + ApiKey, Notification, NotificationHistory, ScheduledNotification, @@ -433,6 +434,10 @@ def test_persist_notifications_list(self, sample_job, sample_api_key, notify_db_ assert persisted_notification[1].to == "foo2@bar.com" assert persisted_notification[0].service == sample_job.service + # Test that the api key last_used_timestamp got updated + api_key = ApiKey.query.get(sample_api_key.id) + assert api_key.last_used_timestamp is not None + def test_persist_notifications_reply_to_text_is_original_value_if_sender_is_changed_later( self, sample_template, sample_api_key, mocker ): @@ -913,6 +918,8 @@ def test_transform_email_notification_stores_normalised_email( assert persisted_notification.to == recipient assert persisted_notification.normalised_to == expected_recipient_normalised + api_key = ApiKey.query.get(sample_api_key.id) + assert api_key.last_used_timestamp is not None class TestDBSaveAndSendNotification: From 1af9f276ed889a0ed5bb4e99c7e3cec2d3b8735e Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 24 Jan 2024 14:50:55 -0400 Subject: [PATCH 12/33] Feat/update cypress tests (#2084) * chore(cypress config): add a production config in case we need to test against prod * chore: include SRE methods in 'all' test suite * fix(cypress config read): read the credentials in differently due to config refactor --------- Co-authored-by: Jumana B --- tests_cypress/config.js | 33 ++++++++++++++++++++++- tests_cypress/cypress/Notify/NotifyAPI.js | 14 +++++----- tests_cypress/cypress/e2e/api/all.cy.js | 1 + 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/tests_cypress/config.js b/tests_cypress/config.js index 5cd94f0e18..5ea2ae52d8 100644 --- a/tests_cypress/config.js +++ b/tests_cypress/config.js @@ -1,3 +1,33 @@ +let PRODUCTION = { + CONFIG_NAME: "PRODUCTION", + Hostnames: { + API: 'https://api.notification.canada.ca', + Admin: 'https://notification.canada.ca', + DDAPI: 'https://api.document.notification.canada.ca', + }, + Services: { + Notify: 'd6aa2c68-a2d9-4437-ab19-3ae8eb202553', + Cypress: '5c8a0501-2aa8-433a-ba51-cefb8063ab93' + }, + Templates: { + 'FILE_ATTACH_TEMPLATE_ID': 'ee6e4f6e-df3c-49b5-82de-eca5122ce965', + 'SIMPLE_EMAIL_TEMPLATE_ID': 'bcc5ff84-4f20-4714-ac8e-7c5bd91c49a6', + 'VARIABLES_EMAIL_TEMPLATE_ID': '0f10fca7-a5e8-4c03-bc96-610072f236eb', + 'SMOKE_TEST_EMAIL': 'be04f866-2302-4b76-8efd-2dec1e853c7d', + 'SMOKE_TEST_EMAIL_BULK': '6978ecf0-8049-47ca-b5af-f010796e8805', + 'SMOKE_TEST_EMAIL_ATTACH': 'ee6e4f6e-df3c-49b5-82de-eca5122ce965', + 'SMOKE_TEST_EMAIL_LINK': '8bac2ff9-32e6-4e19-bf80-4218ce4789fd', + 'SMOKE_TEST_SMS': 'f718f471-d940-44f6-9841-93f61da9b4f7' + }, + Users: { + Team: ['andrew.leith+2@cds-snc.ca'], + NonTeam: ['person@example.com'], + Simulated: ['simulate-delivered-2@notification.canada.ca', 'simulate-delivered-3@notification.canada.ca', 'success@simulator.amazonses.com'], + SimulatedPhone: ['+16132532222', '+16132532223', '+16132532224'] + }, + +} + let STAGING = { CONFIG_NAME: "STAGING", Hostnames: { @@ -69,9 +99,10 @@ let LOCAL = { const config = { STAGING, LOCAL, + PRODUCTION }; // choose which config to use here -const ConfigToUse = config.STAGING; +const ConfigToUse = config.LOCAL; module.exports = ConfigToUse; diff --git a/tests_cypress/cypress/Notify/NotifyAPI.js b/tests_cypress/cypress/Notify/NotifyAPI.js index 02ec5afbe5..400dd1b96b 100644 --- a/tests_cypress/cypress/Notify/NotifyAPI.js +++ b/tests_cypress/cypress/Notify/NotifyAPI.js @@ -26,8 +26,7 @@ const Utilities = { }; const Admin = { SendOneOff: ({to, template_id}) => { - - var token = Utilities.CreateJWT(Cypress.env('ADMIN_USERNAME'), Cypress.env('ADMIN_SECRET')); + var token = Utilities.CreateJWT(Cypress.env('ADMIN_USERNAME'), Cypress.env(config.CONFIG_NAME).ADMIN_SECRET); return cy.request({ url: `/service/${config.Services.Cypress}/send-notification`, method: 'POST', @@ -37,7 +36,7 @@ const Admin = { body: { 'to': to, 'template_id': template_id, - 'created_by': Cypress.env('NOTIFY_USER_ID'), + 'created_by': Cypress.env(config.CONFIG_NAME).NOTIFY_USER_ID, } }); } @@ -117,7 +116,7 @@ const API = { }); }, CreateAPIKey: ({ service_id, key_type, name }) => { - var token = Utilities.CreateJWT(Cypress.env('ADMIN_USERNAME'), Cypress.env('ADMIN_SECRET')); + var token = Utilities.CreateJWT(Cypress.env('ADMIN_USERNAME'), Cypress.env(config.CONFIG_NAME).ADMIN_SECRET); return cy.request({ url: `/service/${service_id}/api-key`, method: 'POST', @@ -127,12 +126,13 @@ const API = { body: { key_type: key_type, name: name, - created_by: Cypress.env('NOTIFY_USER_ID'), + created_by: Cypress.env(config.CONFIG_NAME).NOTIFY_USER_ID, } }); }, RevokeAPIKey: ({ token, type, url, source, failOnStatusCode = true }) => { - var jwt_token = Utilities.CreateJWT(Cypress.env('SRE_USERNAME'), Cypress.env('SRE_SECRET')); + + var jwt_token = Utilities.CreateJWT(Cypress.env('SRE_USERNAME'), Cypress.env(config.CONFIG_NAME).SRE_SECRET); cy.request({ url: `/sre-tools/api-key-revoke`, method: 'POST', @@ -148,7 +148,7 @@ const API = { }); }, RevokeAPIKeyWithAdminAuth: ({ token, type, url, source, failOnStatusCode = true }) => { - var jwt_token = Utilities.CreateJWT(Cypress.env('ADMIN_USERNAME'), Cypress.env('ADMIN_SECRET')); + var jwt_token = Utilities.CreateJWT(Cypress.env('ADMIN_USERNAME'),Cypress.env(config.CONFIG_NAME).ADMIN_SECRET); return cy.request({ url: `/sre-tools/api-key-revoke`, method: 'POST', diff --git a/tests_cypress/cypress/e2e/api/all.cy.js b/tests_cypress/cypress/e2e/api/all.cy.js index 3a52c1c0d7..48c595e245 100644 --- a/tests_cypress/cypress/e2e/api/all.cy.js +++ b/tests_cypress/cypress/e2e/api/all.cy.js @@ -1,2 +1,3 @@ import './email_notifications.cy'; import './file_attach.cy'; +import './sre_tools.cy'; \ No newline at end of file From a5957ab16abaea049b91e7943f06d002a2c28804 Mon Sep 17 00:00:00 2001 From: Jumana B Date: Wed, 24 Jan 2024 16:39:48 -0500 Subject: [PATCH 13/33] Clean up celery config for beat schedule (#2093) --- app/config.py | 53 ++++++++++----------------------------------------- 1 file changed, 10 insertions(+), 43 deletions(-) diff --git a/app/config.py b/app/config.py index d760fbb1d4..3dbecb98bc 100644 --- a/app/config.py +++ b/app/config.py @@ -438,87 +438,54 @@ class Config(object): # app/celery/nightly_tasks.py "timeout-sending-notifications": { "task": "timeout-sending-notifications", - "schedule": crontab(hour=0, minute=5), + "schedule": crontab(hour=5, minute=5), # 00:05 EST in UTC "options": {"queue": QueueNames.PERIODIC}, }, "create-nightly-billing": { "task": "create-nightly-billing", - "schedule": crontab(hour=0, minute=15), + "schedule": crontab(hour=5, minute=15), # 00:15 EST in UTC "options": {"queue": QueueNames.REPORTING}, }, "create-nightly-notification-status": { "task": "create-nightly-notification-status", - "schedule": crontab(hour=0, minute=30), # after 'timeout-sending-notifications' + "schedule": crontab(hour=5, minute=30), # 00:30 EST in UTC, after 'timeout-sending-notifications' "options": {"queue": QueueNames.REPORTING}, }, "delete-sms-notifications": { "task": "delete-sms-notifications", - "schedule": crontab(hour=4, minute=15), # after 'create-nightly-notification-status' + "schedule": crontab(hour=9, minute=15), # 4:15 EST in UTC, after 'create-nightly-notification-status' "options": {"queue": QueueNames.PERIODIC}, }, "delete-email-notifications": { "task": "delete-email-notifications", - "schedule": crontab(hour=4, minute=30), # after 'create-nightly-notification-status' + "schedule": crontab(hour=9, minute=30), # 4:30 EST in UTC, after 'create-nightly-notification-status' "options": {"queue": QueueNames.PERIODIC}, }, "delete-letter-notifications": { "task": "delete-letter-notifications", - "schedule": crontab(hour=4, minute=45), # after 'create-nightly-notification-status' + "schedule": crontab(hour=9, minute=45), # 4:45 EST in UTC, after 'create-nightly-notification-status' "options": {"queue": QueueNames.PERIODIC}, }, "delete-inbound-sms": { "task": "delete-inbound-sms", - "schedule": crontab(hour=1, minute=40), + "schedule": crontab(hour=6, minute=40), # 1:40 EST in UTC "options": {"queue": QueueNames.PERIODIC}, }, "send-daily-performance-platform-stats": { "task": "send-daily-performance-platform-stats", - "schedule": crontab(hour=2, minute=0), + "schedule": crontab(hour=7, minute=0), # 2:00 EST in UTC "options": {"queue": QueueNames.PERIODIC}, }, "remove_transformed_dvla_files": { "task": "remove_transformed_dvla_files", - "schedule": crontab(hour=3, minute=40), + "schedule": crontab(hour=8, minute=40), # 3:40 EST in UTC "options": {"queue": QueueNames.PERIODIC}, }, "remove_sms_email_jobs": { "task": "remove_sms_email_jobs", - "schedule": crontab(hour=4, minute=0), + "schedule": crontab(hour=9, minute=0), # 4:00 EST in UTC "options": {"queue": QueueNames.PERIODIC}, }, - # 'remove_letter_jobs': { - # 'task': 'remove_letter_jobs', - # 'schedule': crontab(hour=4, minute=20), - # since we mark jobs as archived - # 'options': {'queue': QueueNames.PERIODIC}, - # }, - # 'check-templated-letter-state': { - # 'task': 'check-templated-letter-state', - # 'schedule': crontab(day_of_week='mon-fri', hour=9, minute=0), - # 'options': {'queue': QueueNames.PERIODIC} - # }, - # 'check-precompiled-letter-state': { - # 'task': 'check-precompiled-letter-state', - # 'schedule': crontab(day_of_week='mon-fri', hour='9,15', minute=0), - # 'options': {'queue': QueueNames.PERIODIC} - # }, - # 'raise-alert-if-letter-notifications-still-sending': { - # 'task': 'raise-alert-if-letter-notifications-still-sending', - # 'schedule': crontab(hour=16, minute=30), - # 'options': {'queue': QueueNames.PERIODIC} - # }, - # The collate-letter-pdf does assume it is called in an hour that BST does not make a - # difference to the truncate date which translates to the filename to process - # 'collate-letter-pdfs-for-day': { - # 'task': 'collate-letter-pdfs-for-day', - # 'schedule': crontab(hour=17, minute=50), - # 'options': {'queue': QueueNames.PERIODIC} - # }, - # 'raise-alert-if-no-letter-ack-file': { - # 'task': 'raise-alert-if-no-letter-ack-file', - # 'schedule': crontab(hour=23, minute=00), - # 'options': {'queue': QueueNames.PERIODIC} - # }, } CELERY_QUEUES: List[Any] = [] CELERY_DELIVER_SMS_RATE_LIMIT = os.getenv("CELERY_DELIVER_SMS_RATE_LIMIT", "1/s") From fb2657ef53175fa080165e086ced76301bb1ef86 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 12:19:58 -0500 Subject: [PATCH 14/33] chore(deps): update github/codeql-action action to v2.23.1 (#2095) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5b821d9279..71ca53041f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,15 +27,15 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Initialize CodeQL - uses: github/codeql-action/init@8b7fcbfac2aae0e6c24d9f9ebd5830b1290b18e4 # v2.23.0 + uses: github/codeql-action/init@4759df8df70c5ebe7042c3029bbace20eee13edd # v2.23.1 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@8b7fcbfac2aae0e6c24d9f9ebd5830b1290b18e4 # v2.23.0 + uses: github/codeql-action/autobuild@4759df8df70c5ebe7042c3029bbace20eee13edd # v2.23.1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@8b7fcbfac2aae0e6c24d9f9ebd5830b1290b18e4 # v2.23.0 + uses: github/codeql-action/analyze@4759df8df70c5ebe7042c3029bbace20eee13edd # v2.23.1 with: category: "/language:${{ matrix.language }}" From 77d370098fc6e9ecf4dbc31d7a0d1dac5ce0a4c9 Mon Sep 17 00:00:00 2001 From: Jimmy Royer Date: Mon, 29 Jan 2024 15:21:52 -0500 Subject: [PATCH 15/33] Prevent naming variations of .env files to be submited to git (#2094) * Prevent naming variations of .env files to be submited to git * Finetuning + removing real old encrypated secret file --- .env.enc.aws | Bin 1236 -> 0 bytes .gitignore | 7 +++---- 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 .env.enc.aws diff --git a/.env.enc.aws b/.env.enc.aws deleted file mode 100644 index 5c496d464082d132336a3f2cc320bb282bd9251a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1236 zcmV;_1S|Ui0s;Vdy@L>*5;WS%KM6&n*3mO7lOtUufak;_Wi{>Jz(3L{0cO`?Z?Akg zx%H@+M*p3j;Q#;xnlOR{mIetbhDe6@4FLxRpn?R4FoFbw0s#Opf&_d92`Yw2hW8Bt z2LUi11_@w>NC9O71OYBE5d;iTYvV!+ri@;{SKY1x0T6(K1Wh9HlOg2jX)VeASwdg4J%Z02zbJ&@)>Sfx_-fdfz&c)1i4AX8HP zCpP|aXM7;aGL#isnC<;(AjvDcNLF^g=cIoEbxTIzsRK>LG!F`U$Za5;|DN&k0ky?$ z6zN9A7}Kav9oLwh7&%oahU4h7Ue?M0Bz=Ro3E_Q+2#Ihi38TOSE7z{3lbkcseMVNU zE7Epe5l(kCEj9bUikp2+`w7~245g%73HFXrV$ESWxU{tQi?7w+kf+%~L-4}&2ch-Z zA##jb;yCku5lML#H0t8K)E3m`{&<*&$1wAfJW6_M#j5Lctaa`$XSsdVo+Cuf%Y2HR z{^U2M{&hv7_NUL(VxW;=dB?$*+q}J>@wv9$zb71$Ps)2|eoa20qD%-MUpqdgW*tfZ zb@%T6M50Vo(MLlL2QYD6vwt~6+0j49)TdbOd6D4%t2$cVq7NAFX_-e1bRKl za1)D|qGA4X+g!otDKi~k7w~s!OHUzJdN_&;IZeiiAQX+}yGu0T_^y_fRkQ~D_4=pZ z6*NkT@{2RE8DWO6-yd)k$@J>c2Z0UaCL~sNUa<8B{;Zwwthjb4VbU(^ z&*Rj0g|18&p-~ERA4442YJH}6>^L2=`yrAGr_m1@eH#B!4V2tN`A;03aK27aBasr3 zpZ||OzZ4ZSb;gZq_lBmqzNE22150@+$G0Q)XXZ)$D2K!eDRiIv4vluI7dc`JuHuqr zIR^QPz-^JvF=_o@f1VxCNHetfS}b2T6}xb0sQnx>{n?fQ>=hqdRJH=cnSGK_aKbT} zeTTX>&jX8gLnh`lb^L>)yN6>3^Muj?t{W5-#yA_^jL>@)$(`SK zi&H_9FYxiUSA>*zCJosxvno+k+ycW#^UhwW;4DRT$dCGmVP^J7CCcTIc4`PqPpyEP zD=Lyq!uSeO;1X{}`Jg$}G^1hYSItnk+lW!_5=vmB{PQ|G8l>tr`b<_7i?44e1nSzKgq3ufwDw>DThXY!OtFW)Yu~Rtnn|@ud`o#m>IP2nt6<0x7 y*;=9#o({QhXB3lal%eP1{{l=7Z1|)XSaOo$F&~WAUUqnvojDPVEbH=ILL4RvZcJVP diff --git a/.gitignore b/.gitignore index 1951ac3868..08663fb12a 100644 --- a/.gitignore +++ b/.gitignore @@ -72,10 +72,9 @@ target/ # Mac *.DS_Store environment.sh -.envrc -.env -.env_* -.env.lambda +.env* +!.env.devcontainer +!.env.example celerybeat-schedule celerybeat-schedule.* From 3d27ba57d182dcc6483c77d348be5c971193030e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 08:48:49 -0500 Subject: [PATCH 16/33] fix(deps): update all patch dependencies (#1974) * fix(deps): update all patch dependencies * Bump utils version and update lock file * Fix tests --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: wbanks --- poetry.lock | 778 ++++++++++++------- pyproject.toml | 30 +- tests/app/delivery/test_send_to_providers.py | 38 +- tests_cypress/package-lock.json | 515 ++++++------ 4 files changed, 795 insertions(+), 566 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7deb6dd41b..e064e39557 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "aiohttp" version = "3.9.1" description = "Async http client/server framework (asyncio)" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -100,6 +101,7 @@ speedups = ["Brotli", "aiodns", "brotlicffi"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -114,6 +116,7 @@ frozenlist = ">=1.1.0" name = "alembic" version = "1.12.1" description = "A database migration tool for SQLAlchemy." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -133,6 +136,7 @@ tz = ["python-dateutil"] name = "amqp" version = "5.2.0" description = "Low-level AMQP client for Python (fork of amqplib)." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -147,6 +151,7 @@ vine = ">=5.0.0,<6.0.0" name = "annotated-types" version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -158,6 +163,7 @@ files = [ name = "apig-wsgi" version = "2.18.0" description = "Wrap a WSGI application in an AWS Lambda handler function for running on API Gateway or an ALB." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -169,6 +175,7 @@ files = [ name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -180,6 +187,7 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -198,6 +206,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "aws-embedded-metrics" version = "1.0.8" description = "AWS Embedded Metrics Package" +category = "main" optional = false python-versions = "*" files = [ @@ -210,17 +219,18 @@ aiohttp = "*" [[package]] name = "awscli" -version = "1.32.19" +version = "1.32.25" description = "Universal Command Line Environment for AWS." +category = "main" optional = false python-versions = ">= 3.8" files = [ - {file = "awscli-1.32.19-py3-none-any.whl", hash = "sha256:1ce62c1db13cd20c876ebf497b6b4c3bc66697d49d21680b7ea88c6704809d90"}, - {file = "awscli-1.32.19.tar.gz", hash = "sha256:c4255f546cfa3bea233efe78972d2ef173654f615dae6848b44ea0b52ffd94dd"}, + {file = "awscli-1.32.25-py3-none-any.whl", hash = "sha256:eea617961175e8bd1bd3aeda706948c89dc353fc934e81c156fe1ea484ca7a31"}, + {file = "awscli-1.32.25.tar.gz", hash = "sha256:091bbdb852b984d81fb5d8bf00100edd9e40750c77e1542f7ce3ac952a01df6d"}, ] [package.dependencies] -botocore = "1.34.19" +botocore = "1.34.25" colorama = ">=0.2.5,<0.4.5" docutils = ">=0.10,<0.17" PyYAML = ">=3.10,<6.1" @@ -231,6 +241,7 @@ s3transfer = ">=0.10.0,<0.11.0" name = "awscli-cwlogs" version = "1.4.6" description = "AWSCLI CloudWatch Logs plugin" +category = "main" optional = false python-versions = "*" files = [ @@ -247,6 +258,7 @@ six = ">=1.1.0" name = "bcrypt" version = "4.1.1" description = "Modern password hashing for your software and your servers" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -277,6 +289,7 @@ typecheck = ["mypy"] name = "billiard" version = "4.2.0" description = "Python multiprocessing fork with improvements and bugfixes" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -288,6 +301,7 @@ files = [ name = "black" version = "23.7.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -333,6 +347,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "bleach" version = "6.0.0" description = "An easy safelist-based HTML-sanitizing tool." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -351,6 +366,7 @@ css = ["tinycss2 (>=1.1.0,<1.2)"] name = "blinker" version = "1.7.0" description = "Fast, simple object-to-object and broadcast signaling" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -362,6 +378,7 @@ files = [ name = "boto" version = "2.49.0" description = "Amazon Web Services Library" +category = "main" optional = false python-versions = "*" files = [ @@ -371,17 +388,18 @@ files = [ [[package]] name = "boto3" -version = "1.34.19" +version = "1.34.25" description = "The AWS SDK for Python" +category = "main" optional = false python-versions = ">= 3.8" files = [ - {file = "boto3-1.34.19-py3-none-any.whl", hash = "sha256:4c76ef92af7dbdcea21b196a2699671e82e8814d4cfe570c48eda477dd1aeb19"}, - {file = "boto3-1.34.19.tar.gz", hash = "sha256:95d2c2bde86a0934d4c461020c50fc1344b444f167654e215f1de549bc77fc0f"}, + {file = "boto3-1.34.25-py3-none-any.whl", hash = "sha256:87532469188f1eeef4dca67dffbd3f0cc1d51cef7d5e5b5dc95d3b8125f8446e"}, + {file = "boto3-1.34.25.tar.gz", hash = "sha256:1b415e0553679ea05b9e2aed3eb271431011a67a165e3e0aefa323e13b8b7e92"}, ] [package.dependencies] -botocore = ">=1.34.19,<1.35.0" +botocore = ">=1.34.25,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -390,13 +408,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.19" +version = "1.34.25" description = "Low-level, data-driven core of boto 3." +category = "main" optional = false python-versions = ">= 3.8" files = [ - {file = "botocore-1.34.19-py3-none-any.whl", hash = "sha256:a4a39c7092960f5da2439efc5f6220730dab634aaff4c1444bbd1dfa43bc28cc"}, - {file = "botocore-1.34.19.tar.gz", hash = "sha256:64352b2f05de5c6ab025c1d5232880c22775356dcc5a53d798a6f65db847e826"}, + {file = "botocore-1.34.25-py3-none-any.whl", hash = "sha256:35dfab5bdb4620f73ac7c557c4e0d012429706d8760b100f099feea34b5505f8"}, + {file = "botocore-1.34.25.tar.gz", hash = "sha256:a39070bb760bd9545b0eef52a8bcb2d03918206e67a5a786ea4bd6f4bd949edd"}, ] [package.dependencies] @@ -411,6 +430,7 @@ crt = ["awscrt (==0.19.19)"] name = "brotli" version = "1.1.0" description = "Python bindings for the Brotli compression library" +category = "dev" optional = false python-versions = "*" files = [ @@ -503,6 +523,7 @@ files = [ name = "cachelib" version = "0.10.2" description = "A collection of cache libraries in the same API interface." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -514,6 +535,7 @@ files = [ name = "cachetools" version = "4.2.4" description = "Extensible memoizing collections and decorators" +category = "main" optional = false python-versions = "~=3.5" files = [ @@ -523,35 +545,36 @@ files = [ [[package]] name = "celery" -version = "5.3.1" +version = "5.3.6" description = "Distributed Task Queue." +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "celery-5.3.1-py3-none-any.whl", hash = "sha256:27f8f3f3b58de6e0ab4f174791383bbd7445aff0471a43e99cfd77727940753f"}, - {file = "celery-5.3.1.tar.gz", hash = "sha256:f84d1c21a1520c116c2b7d26593926581191435a03aa74b77c941b93ca1c6210"}, + {file = "celery-5.3.6-py3-none-any.whl", hash = "sha256:9da4ea0118d232ce97dff5ed4974587fb1c0ff5c10042eb15278487cdd27d1af"}, + {file = "celery-5.3.6.tar.gz", hash = "sha256:870cc71d737c0200c397290d730344cc991d13a057534353d124c9380267aab9"}, ] [package.dependencies] -billiard = ">=4.1.0,<5.0" +billiard = ">=4.2.0,<5.0" boto3 = {version = ">=1.26.143", optional = true, markers = "extra == \"sqs\""} click = ">=8.1.2,<9.0" click-didyoumean = ">=0.3.0" click-plugins = ">=1.1.1" click-repl = ">=0.2.0" kombu = [ - {version = ">=5.3.1,<6.0"}, + {version = ">=5.3.4,<6.0"}, {version = ">=5.3.0", extras = ["sqs"], optional = true, markers = "extra == \"sqs\""}, ] pycurl = {version = ">=7.43.0.5", optional = true, markers = "sys_platform != \"win32\" and platform_python_implementation == \"CPython\" and extra == \"sqs\""} python-dateutil = ">=2.8.2" tzdata = ">=2022.7" urllib3 = {version = ">=1.26.16", optional = true, markers = "extra == \"sqs\""} -vine = ">=5.0.0,<6.0" +vine = ">=5.1.0,<6.0" [package.extras] -arangodb = ["pyArango (>=2.0.1)"] -auth = ["cryptography (==41.0.1)"] +arangodb = ["pyArango (>=2.0.2)"] +auth = ["cryptography (==41.0.5)"] azureblockblob = ["azure-storage-blob (>=12.15.0)"] brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] cassandra = ["cassandra-driver (>=3.25.0,<4)"] @@ -561,31 +584,32 @@ couchbase = ["couchbase (>=3.0.0)"] couchdb = ["pycouchdb (==1.14.2)"] django = ["Django (>=2.2.28)"] dynamodb = ["boto3 (>=1.26.143)"] -elasticsearch = ["elasticsearch (<8.0)"] +elasticsearch = ["elastic-transport (<=8.10.0)", "elasticsearch (<=8.11.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)"] +msgpack = ["msgpack (==1.0.7)"] pymemcache = ["python-memcached (==1.59)"] pyro = ["pyro4 (==4.82)"] pytest = ["pytest-celery (==0.0.0)"] -redis = ["redis (>=4.5.2,!=4.5.5)"] +redis = ["redis (>=4.5.2,!=4.5.5,<6.0.0)"] s3 = ["boto3 (>=1.26.143)"] slmq = ["softlayer-messaging (>=1.0.3)"] -solar = ["ephem (==4.1.4)"] +solar = ["ephem (==4.1.5)"] 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)"] +zstd = ["zstandard (==0.22.0)"] [[package]] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -597,6 +621,7 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" files = [ @@ -673,6 +698,7 @@ pycparser = "*" name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -772,6 +798,7 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -786,6 +813,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "click-datetime" version = "0.2" description = "Datetime type support for click." +category = "main" optional = false python-versions = "*" files = [ @@ -803,6 +831,7 @@ dev = ["wheel"] name = "click-didyoumean" version = "0.3.0" description = "Enables git-like *did-you-mean* feature in click" +category = "main" optional = false python-versions = ">=3.6.2,<4.0.0" files = [ @@ -817,6 +846,7 @@ click = ">=7" name = "click-plugins" version = "1.1.1" description = "An extension module for click to enable registering CLI commands via setuptools entry-points." +category = "main" optional = false python-versions = "*" files = [ @@ -834,6 +864,7 @@ dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] name = "click-repl" version = "0.3.0" description = "REPL plugin for Click" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -852,6 +883,7 @@ testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -863,6 +895,7 @@ files = [ 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." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -878,6 +911,7 @@ yaml = ["PyYAML"] name = "coverage" version = "5.5" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" files = [ @@ -945,6 +979,7 @@ toml = ["toml"] name = "coveralls" version = "3.3.1" description = "Show coverage stats online via coveralls.io" +category = "dev" optional = false python-versions = ">= 3.5" files = [ @@ -953,7 +988,7 @@ files = [ ] [package.dependencies] -coverage = ">=4.1,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<7.0" +coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" docopt = ">=0.6.1" requests = ">=1.0.0" @@ -964,6 +999,7 @@ yaml = ["PyYAML (>=3.10)"] name = "cryptography" version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1009,6 +1045,7 @@ test-randomorder = ["pytest-randomly"] name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" +category = "main" optional = false python-versions = "*" files = [ @@ -1019,6 +1056,7 @@ files = [ name = "docutils" version = "0.16" description = "Docutils -- Python Documentation Utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1030,6 +1068,7 @@ files = [ name = "environs" version = "9.5.0" description = "simplified environment variable parsing" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1051,6 +1090,7 @@ tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1065,6 +1105,7 @@ test = ["pytest (>=6)"] name = "execnet" version = "2.0.2" description = "execnet: rapid multi-Python deployment" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1079,6 +1120,7 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] name = "fido2" version = "0.9.3" description = "Python based FIDO 2.0 library" +category = "main" optional = false python-versions = ">=2.7.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -1096,6 +1138,7 @@ pcsc = ["pyscard"] name = "filelock" version = "3.13.1" description = "A platform independent file lock." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1112,6 +1155,7 @@ typing = ["typing-extensions (>=4.8)"] name = "flake8" version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -1128,6 +1172,7 @@ pyflakes = ">=3.1.0,<3.2.0" name = "flask" version = "2.3.3" description = "A simple framework for building complex web applications." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1150,6 +1195,7 @@ dotenv = ["python-dotenv"] name = "flask-basicauth" version = "0.2.0" description = "HTTP basic access authentication for Flask." +category = "dev" optional = false python-versions = "*" files = [ @@ -1163,6 +1209,7 @@ Flask = "*" name = "flask-bcrypt" version = "1.0.1" description = "Brcrypt hashing for Flask." +category = "main" optional = false python-versions = "*" files = [ @@ -1178,6 +1225,7 @@ Flask = "*" name = "flask-cors" version = "4.0.0" description = "A Flask extension adding a decorator for CORS support" +category = "dev" optional = false python-versions = "*" files = [ @@ -1192,6 +1240,7 @@ Flask = ">=0.9" name = "flask-marshmallow" version = "0.14.0" description = "Flask + marshmallow for beautiful APIs" +category = "main" optional = false python-versions = "*" files = [ @@ -1215,6 +1264,7 @@ tests = ["flask-sqlalchemy", "marshmallow-sqlalchemy (>=0.13.0)", "marshmallow-s name = "flask-migrate" version = "2.7.0" description = "SQLAlchemy database migrations for Flask applications using Alembic" +category = "main" optional = false python-versions = "*" files = [ @@ -1231,6 +1281,7 @@ Flask-SQLAlchemy = ">=1.0" name = "flask-redis" version = "0.4.0" description = "A nice way to use Redis in your Flask app" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1250,6 +1301,7 @@ tests = ["coverage", "pytest", "pytest-mock"] name = "Flask-SQLAlchemy" version = "2.3.2.dev20231128" description = "Adds SQLAlchemy support to your Flask application" +category = "main" optional = false python-versions = "*" files = [] @@ -1269,6 +1321,7 @@ resolved_reference = "500e732dd1b975a56ab06a46bd1a20a21e682262" name = "freezegun" version = "1.2.2" description = "Let your Python tests travel through time" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1283,6 +1336,7 @@ python-dateutil = ">=2.7" name = "frozenlist" version = "1.4.0" description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1353,6 +1407,7 @@ files = [ name = "future" version = "0.18.3" description = "Clean single-source support for Python 3 and 2" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1361,45 +1416,57 @@ files = [ [[package]] name = "gevent" -version = "23.9.0" +version = "23.9.1" description = "Coroutine-based network library" +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "gevent-23.9.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:1b9ff212cddf245d44651600970c5370c4168f710d4856d8b2350d734dfff1a6"}, - {file = "gevent-23.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03d7452ec45c71ef84cacbf199dac582af2948fb37c638aa6e7232fe5c2840f1"}, - {file = "gevent-23.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41199f10877f368822058b47aca3276e5a4c453e2c4df814a44f23c1bf059488"}, - {file = "gevent-23.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3778609919477ae6eccbf3b10308015f5620c428dbf0dbc6cbeb8a135c9d603a"}, - {file = "gevent-23.9.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:2894d82111ed27b7f80dcbf8c713bb9c9c2c7e8890606cf548f6320fb2ae328f"}, - {file = "gevent-23.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1578b5c6da4e88f5cade5682b13635d17231ade6c549d8498cc95776954675c4"}, - {file = "gevent-23.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1ecd70495749ba1ece8e312e41e98d3371f5aa1e98922b47b3635c8263157938"}, - {file = "gevent-23.9.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:259438ec75b31b199d99941294e50f705d92d20d59578244a882a57c2f0f52b3"}, - {file = "gevent-23.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6eecefb35ceaa33693b971d18e98bfc7e63c9488f1b83eddedb8087a567066"}, - {file = "gevent-23.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17abeca1a8b61a0ce135127f8328265a317efaa3eda047199d65129ca68f0268"}, - {file = "gevent-23.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06a927940f9348b504888f93c8d85c2d8e2277ffd2105734148c0082ade17c20"}, - {file = "gevent-23.9.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:96a3ce33e77277995436656e6f7712f4dcbe9dafa527f245ebbe3de44fd3d4bb"}, - {file = "gevent-23.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:169784ef1594ddac0a773e85e5f996f6f8512abdbe248bff16ed8a22fafecadb"}, - {file = "gevent-23.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:faad7ed513672083eabe1e45d1dbfbbcd7bde9c0da6ef3598e975128c8aee427"}, - {file = "gevent-23.9.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:863a244311e986e924baa1516d821c37fcdb4e091e42f86f9d17d13323893c6d"}, - {file = "gevent-23.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44f1d3a05ce1d041d514b8151aec82e4649e5f8f6e2d837eb9bfc2607d7b64d"}, - {file = "gevent-23.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6179e5419044f8e49ab1fd2165391ffd94071362e03a04d2e499a64b619e105"}, - {file = "gevent-23.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291c380fd708c5e04d63fcb8427da21600fda202ffe14ba59a454efd1715f8d5"}, - {file = "gevent-23.9.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:117904bdb139adeb3378a0631e81fc5939b54edce16ddf21288bde111ab31d47"}, - {file = "gevent-23.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:70bd1e7a6f571837beb190e3a330f289d10ba4d84fa980ffe778b97fb887455c"}, - {file = "gevent-23.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d76871f16df200c9454d3f9695b6cd97e7b80102fdf3e4b69b1e2900f9bd0eee"}, - {file = "gevent-23.9.0-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:436d0e9c1185b15f0204f7e2a687af2fc5307b1fed8ce011b86732ad53edc136"}, - {file = "gevent-23.9.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:2bb65683df8a0a1427d42985158cee7dad8a3211247d5824302e276d9fd5bf65"}, - {file = "gevent-23.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb645e9f68cb4b009d667507195e07314c594b2dac962ae797bd05bccd4076eb"}, - {file = "gevent-23.9.0-cp38-cp38-win32.whl", hash = "sha256:e9e3f81cc36eb623e8716515bd7e28f50ccca5fd930ba493275e60d5fbc69786"}, - {file = "gevent-23.9.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:951d95f1066fb84f661f2bb2c1d6423855a63922abf3e5bd1001711cfb1d8f38"}, - {file = "gevent-23.9.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:d55cf22d6b29593d256bad9fade978fbdc998d6f3bf3aa3399a81feaa91b5a86"}, - {file = "gevent-23.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5afcf6402981f33c84567e6a0a8dcf05e52e8c0b03d80b699644c5f251221e02"}, - {file = "gevent-23.9.0-cp39-cp39-win32.whl", hash = "sha256:91e1e96d50dc4a296dce77b9e28c29a7401fbaca6809989b2029266db6172967"}, + {file = "gevent-23.9.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:a3c5e9b1f766a7a64833334a18539a362fb563f6c4682f9634dea72cbe24f771"}, + {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b101086f109168b23fa3586fccd1133494bdb97f86920a24dc0b23984dc30b69"}, + {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36a549d632c14684bcbbd3014a6ce2666c5f2a500f34d58d32df6c9ea38b6535"}, + {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:272cffdf535978d59c38ed837916dfd2b5d193be1e9e5dcc60a5f4d5025dd98a"}, + {file = "gevent-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcb8612787a7f4626aa881ff15ff25439561a429f5b303048f0fca8a1c781c39"}, + {file = "gevent-23.9.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:d57737860bfc332b9b5aa438963986afe90f49645f6e053140cfa0fa1bdae1ae"}, + {file = "gevent-23.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5f3c781c84794926d853d6fb58554dc0dcc800ba25c41d42f6959c344b4db5a6"}, + {file = "gevent-23.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dbb22a9bbd6a13e925815ce70b940d1578dbe5d4013f20d23e8a11eddf8d14a7"}, + {file = "gevent-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:707904027d7130ff3e59ea387dddceedb133cc742b00b3ffe696d567147a9c9e"}, + {file = "gevent-23.9.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:45792c45d60f6ce3d19651d7fde0bc13e01b56bb4db60d3f32ab7d9ec467374c"}, + {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e24c2af9638d6c989caffc691a039d7c7022a31c0363da367c0d32ceb4a0648"}, + {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e1ead6863e596a8cc2a03e26a7a0981f84b6b3e956101135ff6d02df4d9a6b07"}, + {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65883ac026731ac112184680d1f0f1e39fa6f4389fd1fc0bf46cc1388e2599f9"}, + {file = "gevent-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7af500da05363e66f122896012acb6e101a552682f2352b618e541c941a011"}, + {file = "gevent-23.9.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c3e5d2fa532e4d3450595244de8ccf51f5721a05088813c1abd93ad274fe15e7"}, + {file = "gevent-23.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c84d34256c243b0a53d4335ef0bc76c735873986d478c53073861a92566a8d71"}, + {file = "gevent-23.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ada07076b380918829250201df1d016bdafb3acf352f35e5693b59dceee8dd2e"}, + {file = "gevent-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:921dda1c0b84e3d3b1778efa362d61ed29e2b215b90f81d498eb4d8eafcd0b7a"}, + {file = "gevent-23.9.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ed7a048d3e526a5c1d55c44cb3bc06cfdc1947d06d45006cc4cf60dedc628904"}, + {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c1abc6f25f475adc33e5fc2dbcc26a732608ac5375d0d306228738a9ae14d3b"}, + {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4368f341a5f51611411ec3fc62426f52ac3d6d42eaee9ed0f9eebe715c80184e"}, + {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52b4abf28e837f1865a9bdeef58ff6afd07d1d888b70b6804557e7908032e599"}, + {file = "gevent-23.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52e9f12cd1cda96603ce6b113d934f1aafb873e2c13182cf8e86d2c5c41982ea"}, + {file = "gevent-23.9.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:de350fde10efa87ea60d742901e1053eb2127ebd8b59a7d3b90597eb4e586599"}, + {file = "gevent-23.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fde6402c5432b835fbb7698f1c7f2809c8d6b2bd9d047ac1f5a7c1d5aa569303"}, + {file = "gevent-23.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:dd6c32ab977ecf7c7b8c2611ed95fa4aaebd69b74bf08f4b4960ad516861517d"}, + {file = "gevent-23.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:455e5ee8103f722b503fa45dedb04f3ffdec978c1524647f8ba72b4f08490af1"}, + {file = "gevent-23.9.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:7ccf0fd378257cb77d91c116e15c99e533374a8153632c48a3ecae7f7f4f09fe"}, + {file = "gevent-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d163d59f1be5a4c4efcdd13c2177baaf24aadf721fdf2e1af9ee54a998d160f5"}, + {file = "gevent-23.9.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7532c17bc6c1cbac265e751b95000961715adef35a25d2b0b1813aa7263fb397"}, + {file = "gevent-23.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:78eebaf5e73ff91d34df48f4e35581ab4c84e22dd5338ef32714264063c57507"}, + {file = "gevent-23.9.1-cp38-cp38-win32.whl", hash = "sha256:f632487c87866094546a74eefbca2c74c1d03638b715b6feb12e80120960185a"}, + {file = "gevent-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:62d121344f7465e3739989ad6b91f53a6ca9110518231553fe5846dbe1b4518f"}, + {file = "gevent-23.9.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:bf456bd6b992eb0e1e869e2fd0caf817f0253e55ca7977fd0e72d0336a8c1c6a"}, + {file = "gevent-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43daf68496c03a35287b8b617f9f91e0e7c0d042aebcc060cadc3f049aadd653"}, + {file = "gevent-23.9.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7c28e38dcde327c217fdafb9d5d17d3e772f636f35df15ffae2d933a5587addd"}, + {file = "gevent-23.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fae8d5b5b8fa2a8f63b39f5447168b02db10c888a3e387ed7af2bd1b8612e543"}, + {file = "gevent-23.9.1-cp39-cp39-win32.whl", hash = "sha256:2c7b5c9912378e5f5ccf180d1fdb1e83f42b71823483066eddbe10ef1a2fcaa2"}, + {file = "gevent-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:a2898b7048771917d85a1d548fd378e8a7b2ca963db8e17c6d90c76b495e0e2b"}, + {file = "gevent-23.9.1.tar.gz", hash = "sha256:72c002235390d46f94938a96920d8856d4ffd9ddf62a303a0d7c118894097e34"}, ] [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\""} +greenlet = {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""} "zope.event" = "*" "zope.interface" = "*" @@ -1414,6 +1481,7 @@ test = ["cffi (>=1.12.2)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idn name = "geventhttpclient" version = "2.0.11" description = "http client library for gevent" +category = "dev" optional = false python-versions = "*" files = [ @@ -1537,6 +1605,7 @@ six = "*" name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -1614,6 +1683,7 @@ test = ["objgraph", "psutil"] name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1634,6 +1704,7 @@ tornado = ["tornado (>=0.2)"] name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1645,6 +1716,7 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1656,6 +1728,7 @@ files = [ name = "iso8601" version = "2.0.0" description = "Simple module to parse ISO 8601 dates" +category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -1667,6 +1740,7 @@ files = [ name = "isodate" version = "0.6.1" description = "An ISO 8601 date/time/duration parser and formatter" +category = "main" optional = false python-versions = "*" files = [ @@ -1681,6 +1755,7 @@ six = "*" name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." +category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1698,6 +1773,7 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1709,6 +1785,7 @@ files = [ name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1726,6 +1803,7 @@ i18n = ["Babel (>=2.7)"] name = "jinja2-cli" version = "0.8.2" description = "A CLI interface to Jinja2" +category = "dev" optional = false python-versions = "*" files = [ @@ -1747,6 +1825,7 @@ yaml = ["jinja2", "pyyaml"] name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1758,6 +1837,7 @@ files = [ name = "jsonschema" version = "3.2.0" description = "An implementation of JSON Schema validation for Python" +category = "main" optional = false python-versions = "*" files = [ @@ -1779,6 +1859,7 @@ format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-va name = "kombu" version = "5.3.4" description = "Messaging library for Python." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1814,6 +1895,7 @@ zookeeper = ["kazoo (>=2.8.0)"] name = "locust" version = "2.16.1" description = "Developer friendly load testing framework" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1841,6 +1923,7 @@ Werkzeug = ">=2.0.0" name = "lxml" version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ @@ -1948,6 +2031,7 @@ source = ["Cython (>=0.29.35)"] name = "mako" version = "1.3.0" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1965,97 +2049,100 @@ testing = ["pytest"] [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." +category = "main" 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-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {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"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, + {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, ] [[package]] name = "marshmallow" -version = "3.20.1" +version = "3.20.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" 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"}, + {file = "marshmallow-3.20.2-py3-none-any.whl", hash = "sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"}, + {file = "marshmallow-3.20.2.tar.gz", hash = "sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd"}, ] [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)"] +dev = ["pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.15)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["pre-commit (>=2.4,<4.0)"] tests = ["pytest", "pytz", "simplejson"] [[package]] name = "marshmallow-sqlalchemy" version = "0.29.0" description = "SQLAlchemy integration with the marshmallow (de)serialization library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2078,6 +2165,7 @@ tests = ["pytest", "pytest-lazy-fixture (>=0.6.2)"] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2089,6 +2177,7 @@ files = [ name = "mistune" version = "0.8.4" description = "The fastest markdown parser in pure Python" +category = "main" optional = false python-versions = "*" files = [ @@ -2100,6 +2189,7 @@ files = [ name = "more-itertools" version = "8.14.0" description = "More routines for operating on iterables, beyond itertools" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2111,6 +2201,7 @@ files = [ name = "moto" version = "4.1.11" description = "" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2157,6 +2248,7 @@ xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] name = "msgpack" version = "1.0.7" description = "MessagePack serializer" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2222,6 +2314,7 @@ files = [ name = "multidict" version = "6.0.4" description = "multidict implementation" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2305,6 +2398,7 @@ files = [ name = "mypy" version = "1.5.0" description = "Optional static typing for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2346,6 +2440,7 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2357,6 +2452,7 @@ files = [ name = "nanoid" version = "2.0.0" description = "A tiny, secure, URL-friendly, unique string ID generator for Python" +category = "main" optional = false python-versions = "*" files = [ @@ -2368,6 +2464,7 @@ files = [ name = "networkx" version = "2.8.8" description = "Python package for creating and manipulating graphs and networks" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2386,6 +2483,7 @@ test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] name = "newrelic" version = "6.10.0.165" description = "New Relic Python Agent" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -2411,6 +2509,7 @@ infinite-tracing = ["grpcio (<2)", "protobuf (<4)"] name = "notifications-python-client" version = "6.4.1" description = "Python API client for GOV.UK Notify." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2424,17 +2523,18 @@ requests = ">=2.0.0" [[package]] name = "notifications-utils" -version = "52.0.18" +version = "52.0.19" description = "Shared python code for Notification - Provides logging utils etc." +category = "main" optional = false python-versions = "~3.10" files = [] develop = false [package.dependencies] -awscli = "1.32.19" +awscli = "1.32.25" bleach = "6.0.0" -boto3 = "1.34.19" +boto3 = "1.34.25" cachetools = "4.2.4" certifi = "^2023.7.22" cryptography = "^41.0.2" @@ -2442,10 +2542,10 @@ Flask = "2.3.3" Flask-Redis = "0.4.0" itsdangerous = "2.1.2" Jinja2 = "^3.0.0" -markupsafe = "2.1.3" +markupsafe = "2.1.4" mistune = "0.8.4" ordered-set = "4.1.0" -phonenumbers = "8.13.24" +phonenumbers = "8.13.28" py_w3c = "0.3.1" pypdf2 = "1.28.6" python-json-logger = "2.0.7" @@ -2459,13 +2559,14 @@ werkzeug = "2.3.7" [package.source] type = "git" url = "https://github.com/cds-snc/notifier-utils.git" -reference = "69b6eee0c715fa4c3fe9ac48e4ccd6e3d4c1e239" -resolved_reference = "69b6eee0c715fa4c3fe9ac48e4ccd6e3d4c1e239" +reference = "52.0.19" +resolved_reference = "10210526a168a5d0fe013932144208845b4737b8" [[package]] name = "ordered-set" version = "4.1.0" description = "An OrderedSet is a custom MutableSet that remembers its order, so that every" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2480,6 +2581,7 @@ dev = ["black", "mypy", "pytest"] name = "packaging" version = "23.2" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2491,6 +2593,7 @@ files = [ name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2502,6 +2605,7 @@ files = [ name = "pendulum" version = "2.1.2" description = "Python datetimes made easy" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2534,19 +2638,21 @@ pytzdata = ">=2020.1" [[package]] name = "phonenumbers" -version = "8.13.24" +version = "8.13.28" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." +category = "main" optional = false python-versions = "*" files = [ - {file = "phonenumbers-8.13.24-py2.py3-none-any.whl", hash = "sha256:7dd66c57da00c0f373de83074e78d66a0801381cface4d010cfe07be2fa77fe5"}, - {file = "phonenumbers-8.13.24.tar.gz", hash = "sha256:7abc66f38d92c3b9e827d597b5d590283ca3b05288d9fadea8bc0d6c8ad73c06"}, + {file = "phonenumbers-8.13.28-py2.py3-none-any.whl", hash = "sha256:ad7bc7d7fd6599a124423ffb840409630777c72d0ee58ba8070cc8e7efcb4c38"}, + {file = "phonenumbers-8.13.28.tar.gz", hash = "sha256:e22f276b0c4a70bd5b3f6d668d19cab2578f660b8df44d6418f81d64320151b9"}, ] [[package]] name = "platformdirs" version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2562,6 +2668,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2577,6 +2684,7 @@ testing = ["pytest", "pytest-benchmark"] name = "prompt-toolkit" version = "3.0.41" description = "Library for building powerful interactive command lines in Python" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -2591,6 +2699,7 @@ wcwidth = "*" name = "psutil" version = "5.9.6" description = "Cross-platform lib for process and system monitoring in Python." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -2617,77 +2726,91 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "psycopg2-binary" -version = "2.9.7" +version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" 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"}, + {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, ] [[package]] name = "pwnedpasswords" version = "2.0.0" description = "A Python wrapper for Troy Hunt's Pwned Passwords API." +category = "main" optional = false python-versions = "*" files = [ @@ -2701,6 +2824,7 @@ future = "*" name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2712,6 +2836,7 @@ files = [ name = "py-w3c" version = "0.3.1" description = "W3C services for python." +category = "main" optional = false python-versions = "*" files = [ @@ -2722,6 +2847,7 @@ files = [ name = "pyasn1" version = "0.5.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -2733,6 +2859,7 @@ files = [ name = "pycodestyle" version = "2.11.1" description = "Python style guide checker" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2744,6 +2871,7 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2755,6 +2883,7 @@ files = [ name = "pycurl" version = "7.45.2" description = "PycURL -- A Python Interface To The cURL library" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2765,6 +2894,7 @@ files = [ name = "pydantic" version = "2.5.2" description = "Data validation using Python type hints" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2784,6 +2914,7 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.14.5" description = "" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2901,6 +3032,7 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pyflakes" version = "3.1.0" description = "passive checker of Python programs" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2912,6 +3044,7 @@ files = [ name = "pyjwt" version = "2.8.0" description = "JSON Web Token implementation in Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2929,6 +3062,7 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] name = "pypdf2" version = "1.28.6" description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" +category = "main" optional = false python-versions = ">=2.7" files = [ @@ -2940,6 +3074,7 @@ files = [ name = "pyrsistent" version = "0.20.0" description = "Persistent/Functional/Immutable data structures" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2979,13 +3114,14 @@ files = [ [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.4" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -3003,6 +3139,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-cov" version = "3.0.0" description = "Pytest plugin for measuring coverage." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3021,6 +3158,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-env" version = "0.8.2" description = "py.test plugin that allows you to add environment variables." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3038,6 +3176,7 @@ test = ["coverage (>=7.2.7)", "pytest-mock (>=3.10)"] name = "pytest-forked" version = "1.6.0" description = "run tests in isolated forked subprocesses" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3053,6 +3192,7 @@ pytest = ">=3.10" name = "pytest-mock" version = "3.11.1" description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3068,13 +3208,14 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] [[package]] name = "pytest-mock-resources" -version = "2.9.1" +version = "2.9.2" description = "A pytest plugin for easily instantiating reproducible mock resources." +category = "dev" optional = false python-versions = ">=3.7,<4" files = [ - {file = "pytest_mock_resources-2.9.1-py3-none-any.whl", hash = "sha256:707bbe5552a29ec01dc6811d8e6a5c741dda29dfd2cefb3904fa75f04a32ebb5"}, - {file = "pytest_mock_resources-2.9.1.tar.gz", hash = "sha256:dc3448dead0a87d02e13f85f1ac16ff50f7406196fdf95b8b1a61f68b0981ad6"}, + {file = "pytest_mock_resources-2.9.2-py3-none-any.whl", hash = "sha256:31a585e2fcf96303a98586f7024e4db8f668d8f5a9310cd79c554fd9f5687d49"}, + {file = "pytest_mock_resources-2.9.2.tar.gz", hash = "sha256:3b65808f1e8f01233038768c522fca3de65b01d7bc4b077ac3aa28ce41da04d3"}, ] [package.dependencies] @@ -3100,6 +3241,7 @@ redshift = ["boto3", "filelock", "moto", "python-on-whales (>=0.22.0)", "sqlpars name = "pytest-xdist" version = "2.5.0" description = "pytest xdist plugin for distributed testing and loop-on-failing modes" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3121,6 +3263,7 @@ testing = ["filelock"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -3133,13 +3276,14 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.0.0" +version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, - {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, ] [package.extras] @@ -3149,6 +3293,7 @@ cli = ["click (>=5.0)"] name = "python-json-logger" version = "2.0.7" description = "A python library adding a json log formatter" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3160,6 +3305,7 @@ files = [ name = "python-magic" version = "0.4.27" description = "File type identification using libmagic" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -3171,6 +3317,7 @@ files = [ name = "python-on-whales" version = "0.67.0" description = "A Docker client for Python, designed to be fun and intuitive!" +category = "dev" optional = false python-versions = "<4,>=3.8" files = [ @@ -3179,7 +3326,7 @@ files = [ ] [package.dependencies] -pydantic = ">=1.9,<2.0.dev0 || >=2.1.dev0,<3" +pydantic = ">=1.9,<2.0.0 || >=2.1.0,<3" requests = "*" tqdm = "*" typer = ">=0.4.1" @@ -3192,6 +3339,7 @@ test = ["pytest"] name = "pytz" version = "2021.3" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ @@ -3203,6 +3351,7 @@ files = [ name = "pytzdata" version = "2020.1" description = "The Olson timezone database for Python." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3214,6 +3363,7 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" +category = "dev" optional = false python-versions = "*" files = [ @@ -3237,6 +3387,7 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3297,6 +3448,7 @@ files = [ name = "pyzmq" version = "25.1.1" description = "Python bindings for 0MQ" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3402,6 +3554,7 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} name = "redis" version = "5.0.1" description = "Python client for Redis database and key-value store" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3420,6 +3573,7 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3441,6 +3595,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-file" version = "1.5.1" description = "File transport adapter for Requests" +category = "main" optional = false python-versions = "*" files = [ @@ -3456,6 +3611,7 @@ six = "*" name = "requests-mock" version = "1.11.0" description = "Mock out responses from the requests package" +category = "dev" optional = false python-versions = "*" files = [ @@ -3475,6 +3631,7 @@ test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "tes name = "requests-toolbelt" version = "1.0.0" description = "A utility belt for advanced users of python-requests" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3489,6 +3646,7 @@ requests = ">=2.0.1,<3.0.0" name = "responses" version = "0.24.1" description = "A utility library for mocking out the `requests` Python library." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3508,6 +3666,7 @@ tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asy name = "rfc3987" version = "1.3.8" description = "Parsing and validation of URIs (RFC 3986) and IRIs (RFC 3987)" +category = "dev" optional = false python-versions = "*" files = [ @@ -3519,6 +3678,7 @@ files = [ name = "roundrobin" version = "0.0.4" description = "Collection of roundrobin utilities" +category = "dev" optional = false python-versions = "*" files = [ @@ -3529,6 +3689,7 @@ files = [ name = "rsa" version = "4.7.2" description = "Pure-Python RSA implementation" +category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -3543,6 +3704,7 @@ pyasn1 = ">=0.1.3" name = "s3transfer" version = "0.10.0" description = "An Amazon S3 Transfer Manager" +category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -3560,6 +3722,7 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] name = "setuptools" version = "69.0.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3576,6 +3739,7 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "simple-salesforce" version = "1.12.5" description = "A basic Salesforce.com REST API client." +category = "main" optional = false python-versions = "*" files = [ @@ -3595,6 +3759,7 @@ zeep = "*" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3606,6 +3771,7 @@ files = [ name = "smartypants" version = "2.0.1" description = "Python with the SmartyPants" +category = "main" optional = false python-versions = "*" files = [ @@ -3614,67 +3780,66 @@ files = [ [[package]] name = "sqlalchemy" -version = "1.4.49" +version = "1.4.51" description = "Database Abstraction Library" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "SQLAlchemy-1.4.49-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e126cf98b7fd38f1e33c64484406b78e937b1a280e078ef558b95bf5b6895f6"}, - {file = "SQLAlchemy-1.4.49-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03db81b89fe7ef3857b4a00b63dedd632d6183d4ea5a31c5d8a92e000a41fc71"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca46de16650d143a928d10842939dab208e8d8c3a9a8757600cae9b7c579c5cd"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c21b172dfb22e0db303ff6419451f0cac891d2e911bb9fbf8003d717f1bcf91"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-win32.whl", hash = "sha256:5fb1ebdfc8373b5a291485757bd6431de8d7ed42c27439f543c81f6c8febd729"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-win_amd64.whl", hash = "sha256:f8a65990c9c490f4651b5c02abccc9f113a7f56fa482031ac8cb88b70bc8ccaa"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8923dfdf24d5aa8a3adb59723f54118dd4fe62cf59ed0d0d65d940579c1170a4"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9ab2c507a7a439f13ca4499db6d3f50423d1d65dc9b5ed897e70941d9e135b0"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5debe7d49b8acf1f3035317e63d9ec8d5e4d904c6e75a2a9246a119f5f2fdf3d"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-win32.whl", hash = "sha256:82b08e82da3756765c2e75f327b9bf6b0f043c9c3925fb95fb51e1567fa4ee87"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-win_amd64.whl", hash = "sha256:171e04eeb5d1c0d96a544caf982621a1711d078dbc5c96f11d6469169bd003f1"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f23755c384c2969ca2f7667a83f7c5648fcf8b62a3f2bbd883d805454964a800"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8396e896e08e37032e87e7fbf4a15f431aa878c286dc7f79e616c2feacdb366c"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66da9627cfcc43bbdebd47bfe0145bb662041472393c03b7802253993b6b7c90"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-win32.whl", hash = "sha256:9a06e046ffeb8a484279e54bda0a5abfd9675f594a2e38ef3133d7e4d75b6214"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-win_amd64.whl", hash = "sha256:7cf8b90ad84ad3a45098b1c9f56f2b161601e4670827d6b892ea0e884569bd1d"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:36e58f8c4fe43984384e3fbe6341ac99b6b4e083de2fe838f0fdb91cebe9e9cb"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b31e67ff419013f99ad6f8fc73ee19ea31585e1e9fe773744c0f3ce58c039c30"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc22807a7e161c0d8f3da34018ab7c97ef6223578fcdd99b1d3e7ed1100a5db"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c14b29d9e1529f99efd550cd04dbb6db6ba5d690abb96d52de2bff4ed518bc95"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f3470e084d31247aea228aa1c39bbc0904c2b9ccbf5d3cfa2ea2dac06f26d"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-win32.whl", hash = "sha256:706bfa02157b97c136547c406f263e4c6274a7b061b3eb9742915dd774bbc264"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-win_amd64.whl", hash = "sha256:a7f7b5c07ae5c0cfd24c2db86071fb2a3d947da7bd487e359cc91e67ac1c6d2e"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:4afbbf5ef41ac18e02c8dc1f86c04b22b7a2125f2a030e25bbb4aff31abb224b"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24e300c0c2147484a002b175f4e1361f102e82c345bf263242f0449672a4bccf"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:393cd06c3b00b57f5421e2133e088df9cabcececcea180327e43b937b5a7caa5"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:201de072b818f8ad55c80d18d1a788729cccf9be6d9dc3b9d8613b053cd4836d"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653ed6817c710d0c95558232aba799307d14ae084cc9b1f4c389157ec50df5c"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-win32.whl", hash = "sha256:647e0b309cb4512b1f1b78471fdaf72921b6fa6e750b9f891e09c6e2f0e5326f"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-win_amd64.whl", hash = "sha256:ab73ed1a05ff539afc4a7f8cf371764cdf79768ecb7d2ec691e3ff89abbc541e"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37ce517c011560d68f1ffb28af65d7e06f873f191eb3a73af5671e9c3fada08a"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1878ce508edea4a879015ab5215546c444233881301e97ca16fe251e89f1c55"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ab792ca493891d7a45a077e35b418f68435efb3e1706cb8155e20e86a9013c"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e8e608983e6f85d0852ca61f97e521b62e67969e6e640fe6c6b575d4db68557"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf956da45290df6e809ea12c54c02ace7f8ff4d765d6d3dfb3655ee876ce58d"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-win32.whl", hash = "sha256:f167c8175ab908ce48bd6550679cc6ea20ae169379e73c7720a28f89e53aa532"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-win_amd64.whl", hash = "sha256:45806315aae81a0c202752558f0df52b42d11dd7ba0097bf71e253b4215f34f4"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b6d0c4b15d65087738a6e22e0ff461b407533ff65a73b818089efc8eb2b3e1de"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a843e34abfd4c797018fd8d00ffffa99fd5184c421f190b6ca99def4087689bd"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:738d7321212941ab19ba2acf02a68b8ee64987b248ffa2101630e8fccb549e0d"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c890421651b45a681181301b3497e4d57c0d01dc001e10438a40e9a9c25ee77"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26f280b8f0a8f497bc10573849ad6dc62e671d2468826e5c748d04ed9e670d5"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-win32.whl", hash = "sha256:ec2268de67f73b43320383947e74700e95c6770d0c68c4e615e9897e46296294"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-win_amd64.whl", hash = "sha256:bbdf16372859b8ed3f4d05f925a984771cd2abd18bd187042f24be4886c2a15f"}, - {file = "SQLAlchemy-1.4.49.tar.gz", hash = "sha256:06ff25cbae30c396c4b7737464f2a7fc37a67b7da409993b182b024cec80aed9"}, + {file = "SQLAlchemy-1.4.51-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:1a09d5bd1a40d76ad90e5570530e082ddc000e1d92de495746f6257dc08f166b"}, + {file = "SQLAlchemy-1.4.51-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2be4e6294c53f2ec8ea36486b56390e3bcaa052bf3a9a47005687ccf376745d1"}, + {file = "SQLAlchemy-1.4.51-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca484ca11c65e05639ffe80f20d45e6be81fbec7683d6c9a15cd421e6e8b340"}, + {file = "SQLAlchemy-1.4.51-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0535d5b57d014d06ceeaeffd816bb3a6e2dddeb670222570b8c4953e2d2ea678"}, + {file = "SQLAlchemy-1.4.51-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af55cc207865d641a57f7044e98b08b09220da3d1b13a46f26487cc2f898a072"}, + {file = "SQLAlchemy-1.4.51-cp310-cp310-win32.whl", hash = "sha256:7af40425ac535cbda129d9915edcaa002afe35d84609fd3b9d6a8c46732e02ee"}, + {file = "SQLAlchemy-1.4.51-cp310-cp310-win_amd64.whl", hash = "sha256:8d1d7d63e5d2f4e92a39ae1e897a5d551720179bb8d1254883e7113d3826d43c"}, + {file = "SQLAlchemy-1.4.51-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eaeeb2464019765bc4340214fca1143081d49972864773f3f1e95dba5c7edc7d"}, + {file = "SQLAlchemy-1.4.51-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7deeae5071930abb3669b5185abb6c33ddfd2398f87660fafdb9e6a5fb0f3f2f"}, + {file = "SQLAlchemy-1.4.51-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0892e7ac8bc76da499ad3ee8de8da4d7905a3110b952e2a35a940dab1ffa550e"}, + {file = "SQLAlchemy-1.4.51-cp311-cp311-win32.whl", hash = "sha256:50e074aea505f4427151c286955ea025f51752fa42f9939749336672e0674c81"}, + {file = "SQLAlchemy-1.4.51-cp311-cp311-win_amd64.whl", hash = "sha256:3b0cd89a7bd03f57ae58263d0f828a072d1b440c8c2949f38f3b446148321171"}, + {file = "SQLAlchemy-1.4.51-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a33cb3f095e7d776ec76e79d92d83117438b6153510770fcd57b9c96f9ef623d"}, + {file = "SQLAlchemy-1.4.51-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cacc0b2dd7d22a918a9642fc89840a5d3cee18a0e1fe41080b1141b23b10916"}, + {file = "SQLAlchemy-1.4.51-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:245c67c88e63f1523e9216cad6ba3107dea2d3ee19adc359597a628afcabfbcb"}, + {file = "SQLAlchemy-1.4.51-cp312-cp312-win32.whl", hash = "sha256:8e702e7489f39375601c7ea5a0bef207256828a2bc5986c65cb15cd0cf097a87"}, + {file = "SQLAlchemy-1.4.51-cp312-cp312-win_amd64.whl", hash = "sha256:0525c4905b4b52d8ccc3c203c9d7ab2a80329ffa077d4bacf31aefda7604dc65"}, + {file = "SQLAlchemy-1.4.51-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:1980e6eb6c9be49ea8f89889989127daafc43f0b1b6843d71efab1514973cca0"}, + {file = "SQLAlchemy-1.4.51-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ec7a0ed9b32afdf337172678a4a0e6419775ba4e649b66f49415615fa47efbd"}, + {file = "SQLAlchemy-1.4.51-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352df882088a55293f621328ec33b6ffca936ad7f23013b22520542e1ab6ad1b"}, + {file = "SQLAlchemy-1.4.51-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:86a22143a4001f53bf58027b044da1fb10d67b62a785fc1390b5c7f089d9838c"}, + {file = "SQLAlchemy-1.4.51-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c37bc677690fd33932182b85d37433845de612962ed080c3e4d92f758d1bd894"}, + {file = "SQLAlchemy-1.4.51-cp36-cp36m-win32.whl", hash = "sha256:d0a83afab5e062abffcdcbcc74f9d3ba37b2385294dd0927ad65fc6ebe04e054"}, + {file = "SQLAlchemy-1.4.51-cp36-cp36m-win_amd64.whl", hash = "sha256:a61184c7289146c8cff06b6b41807c6994c6d437278e72cf00ff7fe1c7a263d1"}, + {file = "SQLAlchemy-1.4.51-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:3f0ef620ecbab46e81035cf3dedfb412a7da35340500ba470f9ce43a1e6c423b"}, + {file = "SQLAlchemy-1.4.51-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c55040d8ea65414de7c47f1a23823cd9f3fad0dc93e6b6b728fee81230f817b"}, + {file = "SQLAlchemy-1.4.51-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ef80328e3fee2be0a1abe3fe9445d3a2e52a1282ba342d0dab6edf1fef4707"}, + {file = "SQLAlchemy-1.4.51-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f8cafa6f885a0ff5e39efa9325195217bb47d5929ab0051636610d24aef45ade"}, + {file = "SQLAlchemy-1.4.51-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8f2df79a46e130235bc5e1bbef4de0583fb19d481eaa0bffa76e8347ea45ec6"}, + {file = "SQLAlchemy-1.4.51-cp37-cp37m-win32.whl", hash = "sha256:f2e5b6f5cf7c18df66d082604a1d9c7a2d18f7d1dbe9514a2afaccbb51cc4fc3"}, + {file = "SQLAlchemy-1.4.51-cp37-cp37m-win_amd64.whl", hash = "sha256:5e180fff133d21a800c4f050733d59340f40d42364fcb9d14f6a67764bdc48d2"}, + {file = "SQLAlchemy-1.4.51-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:7d8139ca0b9f93890ab899da678816518af74312bb8cd71fb721436a93a93298"}, + {file = "SQLAlchemy-1.4.51-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb18549b770351b54e1ab5da37d22bc530b8bfe2ee31e22b9ebe650640d2ef12"}, + {file = "SQLAlchemy-1.4.51-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55e699466106d09f028ab78d3c2e1f621b5ef2c8694598242259e4515715da7c"}, + {file = "SQLAlchemy-1.4.51-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2ad16880ccd971ac8e570550fbdef1385e094b022d6fc85ef3ce7df400dddad3"}, + {file = "SQLAlchemy-1.4.51-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b97fd5bb6b7c1a64b7ac0632f7ce389b8ab362e7bd5f60654c2a418496be5d7f"}, + {file = "SQLAlchemy-1.4.51-cp38-cp38-win32.whl", hash = "sha256:cecb66492440ae8592797dd705a0cbaa6abe0555f4fa6c5f40b078bd2740fc6b"}, + {file = "SQLAlchemy-1.4.51-cp38-cp38-win_amd64.whl", hash = "sha256:39b02b645632c5fe46b8dd30755682f629ffbb62ff317ecc14c998c21b2896ff"}, + {file = "SQLAlchemy-1.4.51-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b03850c290c765b87102959ea53299dc9addf76ca08a06ea98383348ae205c99"}, + {file = "SQLAlchemy-1.4.51-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e646b19f47d655261b22df9976e572f588185279970efba3d45c377127d35349"}, + {file = "SQLAlchemy-1.4.51-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3cf56cc36d42908495760b223ca9c2c0f9f0002b4eddc994b24db5fcb86a9e4"}, + {file = "SQLAlchemy-1.4.51-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0d661cff58c91726c601cc0ee626bf167b20cc4d7941c93c5f3ac28dc34ddbea"}, + {file = "SQLAlchemy-1.4.51-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3823dda635988e6744d4417e13f2e2b5fe76c4bf29dd67e95f98717e1b094cad"}, + {file = "SQLAlchemy-1.4.51-cp39-cp39-win32.whl", hash = "sha256:b00cf0471888823b7a9f722c6c41eb6985cf34f077edcf62695ac4bed6ec01ee"}, + {file = "SQLAlchemy-1.4.51-cp39-cp39-win_amd64.whl", hash = "sha256:a055ba17f4675aadcda3005df2e28a86feb731fdcc865e1f6b4f209ed1225cba"}, + {file = "SQLAlchemy-1.4.51.tar.gz", hash = "sha256:e7908c2025eb18394e32d65dd02d2e37e17d733cdbe7d78231c2b6d7eb20cdb9"}, ] [package.dependencies] greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (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\")"} [package.extras] -aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +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)", "greenlet (!=0.4.17)"] mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] @@ -3684,19 +3849,20 @@ mssql-pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] +oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql", "pymysql (<1)"] -sqlcipher = ["sqlcipher3-binary"] +sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sqlalchemy-stubs" version = "0.4" description = "SQLAlchemy stubs and mypy plugin" +category = "dev" optional = false python-versions = "*" files = [ @@ -3710,13 +3876,14 @@ typing-extensions = ">=3.7.4" [[package]] name = "sqlalchemy2-stubs" -version = "0.0.2a35" +version = "0.0.2a38" description = "Typing Stubs for SQLAlchemy 1.4" +category = "dev" optional = false python-versions = ">=3.6" files = [ - {file = "sqlalchemy2-stubs-0.0.2a35.tar.gz", hash = "sha256:bd5d530697d7e8c8504c7fe792ef334538392a5fb7aa7e4f670bfacdd668a19d"}, - {file = "sqlalchemy2_stubs-0.0.2a35-py3-none-any.whl", hash = "sha256:593784ff9fc0dc2ded1895e3322591689db3be06f3ca006e3ef47640baf2d38a"}, + {file = "sqlalchemy2-stubs-0.0.2a38.tar.gz", hash = "sha256:861d722abeb12f13eacd775a9f09379b11a5a9076f469ccd4099961b95800f9e"}, + {file = "sqlalchemy2_stubs-0.0.2a38-py3-none-any.whl", hash = "sha256:b62aa46943807287550e2033dafe07564b33b6a815fbaa3c144e396f9cc53bcb"}, ] [package.dependencies] @@ -3726,6 +3893,7 @@ typing-extensions = ">=3.7.4" name = "statsd" version = "3.3.0" description = "A simple statsd client." +category = "main" optional = false python-versions = "*" files = [ @@ -3737,6 +3905,7 @@ files = [ name = "strict-rfc3339" version = "0.7" description = "Strict, simple, lightweight RFC3339 functions" +category = "dev" optional = false python-versions = "*" files = [ @@ -3747,6 +3916,7 @@ files = [ name = "tldextract" version = "3.4.4" description = "Accurately separates a URL's subdomain, domain, and public suffix, using the Public Suffix List (PSL). By default, this includes the public ICANN TLDs and their exceptions. You can optionally support the Public Suffix List's private domains as well." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3764,6 +3934,7 @@ requests-file = ">=1.4" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3775,6 +3946,7 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3786,6 +3958,7 @@ files = [ name = "tqdm" version = "4.66.1" description = "Fast, Extensible Progress Meter" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3806,6 +3979,7 @@ telegram = ["requests"] name = "typer" version = "0.9.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3827,6 +4001,7 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. name = "types-boto" version = "2.49.18.9" description = "Typing stubs for boto" +category = "dev" optional = false python-versions = "*" files = [ @@ -3838,6 +4013,7 @@ files = [ name = "types-mock" version = "4.0.15.2" description = "Typing stubs for mock" +category = "dev" optional = false python-versions = "*" files = [ @@ -3849,6 +4025,7 @@ files = [ name = "types-pyopenssl" version = "23.3.0.0" description = "Typing stubs for pyOpenSSL" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3861,19 +4038,21 @@ cryptography = ">=35.0.0" [[package]] name = "types-python-dateutil" -version = "2.8.19.14" +version = "2.8.19.20240106" description = "Typing stubs for python-dateutil" +category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"}, - {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"}, + {file = "types-python-dateutil-2.8.19.20240106.tar.gz", hash = "sha256:1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f"}, + {file = "types_python_dateutil-2.8.19.20240106-py3-none-any.whl", hash = "sha256:efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2"}, ] [[package]] name = "types-pytz" version = "2022.7.1.2" description = "Typing stubs for pytz" +category = "dev" optional = false python-versions = "*" files = [ @@ -3883,13 +4062,14 @@ files = [ [[package]] name = "types-redis" -version = "4.6.0.5" +version = "4.6.0.20240106" description = "Typing stubs for redis" +category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-redis-4.6.0.5.tar.gz", hash = "sha256:5f179d10bd3ca995a8134aafcddfc3e12d52b208437c4529ef27e68acb301f38"}, - {file = "types_redis-4.6.0.5-py3-none-any.whl", hash = "sha256:4f662060247a2363c7a8f0b7e52915d68960870ff16a749a891eabcf87ed0be4"}, + {file = "types-redis-4.6.0.20240106.tar.gz", hash = "sha256:2b2fa3a78f84559616242d23f86de5f4130dfd6c3b83fb2d8ce3329e503f756e"}, + {file = "types_redis-4.6.0.20240106-py3-none-any.whl", hash = "sha256:912de6507b631934bd225cdac310b04a58def94391003ba83939e5a10e99568d"}, ] [package.dependencies] @@ -3898,33 +4078,24 @@ types-pyOpenSSL = "*" [[package]] name = "types-requests" -version = "2.31.0.2" +version = "2.31.0.20240106" description = "Typing stubs for requests" +category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.8" 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"}, + {file = "types-requests-2.31.0.20240106.tar.gz", hash = "sha256:0e1c731c17f33618ec58e022b614a1a2ecc25f7dc86800b36ef341380402c612"}, + {file = "types_requests-2.31.0.20240106-py3-none-any.whl", hash = "sha256:da997b3b6a72cc08d09f4dba9802fdbabc89104b35fe24ee588e674037689354"}, ] [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"}, -] +urllib3 = ">=2" [[package]] name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3936,6 +4107,7 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" +category = "main" optional = false python-versions = ">=2" files = [ @@ -3945,35 +4117,39 @@ files = [ [[package]] name = "unidecode" -version = "1.3.6" +version = "1.3.8" description = "ASCII transliterations of Unicode text" +category = "main" optional = false python-versions = ">=3.5" files = [ - {file = "Unidecode-1.3.6-py3-none-any.whl", hash = "sha256:547d7c479e4f377b430dd91ac1275d593308dce0fc464fb2ab7d41f82ec653be"}, - {file = "Unidecode-1.3.6.tar.gz", hash = "sha256:fed09cf0be8cf415b391642c2a5addfc72194407caee4f98719e40ec2a72b830"}, + {file = "Unidecode-1.3.8-py3-none-any.whl", hash = "sha256:d130a61ce6696f8148a3bd8fe779c99adeb4b870584eeb9526584e9aa091fd39"}, + {file = "Unidecode-1.3.8.tar.gz", hash = "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4"}, ] [[package]] name = "urllib3" -version = "1.26.18" +version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7" files = [ - {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, - {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +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 = "vine" version = "5.1.0" description = "Python promises." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3985,6 +4161,7 @@ files = [ name = "wcwidth" version = "0.2.12" description = "Measures the displayed width of unicode strings in a terminal" +category = "main" optional = false python-versions = "*" files = [ @@ -3996,6 +4173,7 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" +category = "main" optional = false python-versions = "*" files = [ @@ -4007,6 +4185,7 @@ files = [ name = "werkzeug" version = "2.3.7" description = "The comprehensive WSGI web application library." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4024,6 +4203,7 @@ watchdog = ["watchdog (>=2.3)"] name = "xmltodict" version = "0.13.0" description = "Makes working with XML feel like you are working with JSON" +category = "dev" optional = false python-versions = ">=3.4" files = [ @@ -4035,6 +4215,7 @@ files = [ name = "yarl" version = "1.9.3" description = "Yet another URL library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4138,6 +4319,7 @@ multidict = ">=4.0" name = "zeep" version = "4.2.1" description = "A Python SOAP client" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4165,6 +4347,7 @@ xmlsec = ["xmlsec (>=0.6.1)"] name = "zope-event" version = "5.0" description = "Very basic event publishing system" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4183,6 +4366,7 @@ test = ["zope.testrunner"] name = "zope-interface" version = "6.1" description = "Interfaces for Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4235,4 +4419,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "~3.10.9" -content-hash = "2a0871640898c6922505047d0efee3d2b911a1b1ee18688cdc82fdfa7932c46b" +content-hash = "ec1cf85359f79c3cf3d8d2cdc1aab0383d55b5b58923adaa4aa8042affcd3b84" diff --git a/pyproject.toml b/pyproject.toml index 7b754e33ad..19d64023dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ python = "~3.10.9" apig-wsgi = "2.18.0" boto = "2.49.0" cffi = "1.15.1" -celery = {extras = ["sqs"], version = "5.3.1"} +celery = {extras = ["sqs"], version = "5.3.6"} docopt = "0.6.2" environs = "9.5.0" # pyup: <9.3.3 # marshmallow v3 throws errors" fido2 = "0.9.3" @@ -35,36 +35,36 @@ Flask-SQLAlchemy = { git = "https://github.com/pallets-eco/flask-sqlalchemy.git" #git+https://github.com/mitsuhiko/flask-sqlalchemy.git@500e732dd1b975a56ab06a46bd1a20a21e682262#egg=Flask-SQLAlchemy==2.3.2.dev20190108 Flask = "2.3.3" click-datetime = "0.2" -gevent = "23.9.0" +gevent = "23.9.1" gunicorn = "20.1.0" iso8601 = "2.0.0" jsonschema = "3.2.0" marshmallow-sqlalchemy = "0.29.0" -marshmallow = "3.20.1" +marshmallow = "3.20.2" python-magic = "0.4.27" -psycopg2-binary = "2.9.7" +psycopg2-binary = "2.9.9" PyJWT = "2.8.0" pytz = "2021.3" PyYAML = "6.0.1" -SQLAlchemy = "1.4.49" +SQLAlchemy = "1.4.51" cachelib = "0.10.2" newrelic = "6.10.0.165" notifications-python-client = "6.4.1" -python-dotenv = "1.0.0" +python-dotenv = "1.0.1" pwnedpasswords = "2.0.0" tldextract = "3.4.4" nanoid = "2.0.0" -unidecode = "1.3.6" +unidecode = "1.3.8" more-itertools = "8.14.0" # PaaS awscli-cwlogs = "1.4.6" aws-embedded-metrics = "1.0.8" # Putting upgrade on hold due to new version introducing breaking changes Werkzeug = "2.3.7" -MarkupSafe = "2.1.3" +MarkupSafe = "2.1.4" # REVIEW: v2 is using sha512 instead of sha1 by default (in v1) itsdangerous = "2.1.2" -notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", rev = "69b6eee0c715fa4c3fe9ac48e4ccd6e3d4c1e239" } +notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", rev = "52.0.19" } # rsa = "4.9 # awscli 1.22.38 depends on rsa<4.8 typing-extensions = "4.7.1" greenlet = "2.0.2" @@ -80,7 +80,7 @@ flake8 = "6.1.0" isort = "5.12.0" moto = "4.1.11" idna = "2.10" -pytest = "7.4.0" +pytest = "7.4.4" pytest-env = "0.8.2" pytest-mock = "3.11.1" pytest-cov = "3.0.0" @@ -97,12 +97,12 @@ black = "23.7.0" locust = "2.16.1" mypy = "1.5" sqlalchemy-stubs = "0.4" -sqlalchemy2-stubs = "0.0.2a35" +sqlalchemy2-stubs = "0.0.2a38" networkx = "2.8.8" # not directly required, pinned by Snyk to avoid a vulnerability -pytest-mock-resources = { extras = ["redis"], version = "2.9.1" } +pytest-mock-resources = { extras = ["redis"], version = "2.9.2" } types-boto = "2.49.18.9" types-mock = "4.0.15.2" -types-python-dateutil = "2.8.19.14" +types-python-dateutil = "2.8.19.20240106" types-pytz = "2022.7.1.2" -types-requests = "2.31.0.2" -types-redis = "4.6.0.5" +types-requests = "2.31.0.20240106" +types-redis = "4.6.0.20240106" diff --git a/tests/app/delivery/test_send_to_providers.py b/tests/app/delivery/test_send_to_providers.py index b6c43b299a..018df49123 100644 --- a/tests/app/delivery/test_send_to_providers.py +++ b/tests/app/delivery/test_send_to_providers.py @@ -1,9 +1,8 @@ import uuid from collections import namedtuple from datetime import datetime -from http.client import HTTPMessage from unittest import TestCase -from unittest.mock import ANY, MagicMock, Mock, call +from unittest.mock import ANY, MagicMock, call import pytest from flask import current_app @@ -907,24 +906,41 @@ def json(): } personalisation["file"]["document"]["sending_method"] = "attach" personalisation["file"]["document"]["filename"] = "file.txt" + personalisation["file"]["document"]["id"] = "1234" db_notification = save_notification(create_notification(template=template, personalisation=personalisation)) mocker.patch("app.delivery.send_to_providers.statsd_client") mocker.patch("app.aws_ses_client.send_email", return_value="reference") - getconn_mock = mocker.patch("urllib3.connectionpool.HTTPConnectionPool._new_conn") - getconn_mock.return_value.getresponse.side_effect = [ - Mock(status=500, msg=HTTPMessage()), - Mock(status=429, msg=HTTPMessage()), - Mock(status=400, msg=HTTPMessage()), - Mock(status=404, msg=HTTPMessage()), - Mock(status=200, msg=HTTPMessage()), + # When a urllib3 request attempts retries and fails it will wrap the offending exception in a MaxRetryError + # thus we'll capture the logged exception and assert it's a MaxRetryError to verify that retries were attempted + mock_logger = mocker.patch("app.delivery.send_to_providers.current_app.logger.error") + logger_args = [] + + def mock_error(*args): + logger_args.append(args) + + mock_logger.side_effect = mock_error + + class MockHTTPResponse: + def __init__(self, status): + self.status = status + self.data = b"file content" if status == 200 else b"" + + mock_http = mocker.patch("urllib3.PoolManager") + mock_http.return_value.request.side_effect = [ + MockHTTPResponse(500), + MockHTTPResponse(500), + MockHTTPResponse(500), + MockHTTPResponse(500), + MockHTTPResponse(500), ] - mock_logger = mocker.patch("app.delivery.send_to_providers.current_app.logger.error") send_to_providers.send_email_to_provider(db_notification) - assert mock_logger.call_count == 0 + exception = logger_args[0][0].split("Exception: ")[1] + assert mock_logger.call_count == 1 + assert "Max retries exceeded" in exception def test_file_attachment_max_retries(mocker, notify_db, notify_db_session): diff --git a/tests_cypress/package-lock.json b/tests_cypress/package-lock.json index d96f723791..d6f9b3c501 100644 --- a/tests_cypress/package-lock.json +++ b/tests_cypress/package-lock.json @@ -186,40 +186,6 @@ "strip-ansi": "^7.0.1" } }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -229,23 +195,6 @@ "ansi-regex": "^6.0.1" } }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - } - } - }, "wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -256,60 +205,6 @@ "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } - }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } } } }, @@ -340,9 +235,9 @@ } }, "@types/node": { - "version": "16.18.40", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.40.tgz", - "integrity": "sha512-+yno3ItTEwGxXiS/75Q/aHaa5srkpnJaH+kdkTVJ3DtJEwv92itpKbxU+FjPoh2m/5G9zmUQfrL4A4C13c+iGA==", + "version": "16.18.76", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.76.tgz", + "integrity": "sha512-/GsO2uv1Z6R42lBr59dtem56gVF/yHKQaScggwU+gLU6DXE25sDmOar4c4IfWb3h+X/7OYZznPOFk7oGF3jQSA==", "dev": true }, "@types/sinonjs__fake-timers": { @@ -352,15 +247,15 @@ "dev": true }, "@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", + "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", "dev": true }, "@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, "optional": true, "requires": { @@ -465,9 +360,9 @@ "dev": true }, "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, "asynckit": { @@ -489,9 +384,9 @@ "dev": true }, "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, "axe-core": { @@ -565,19 +460,20 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, "cachedir": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", - "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", "dev": true }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", "dev": true, "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" } }, "caseless": { @@ -614,9 +510,9 @@ "dev": true }, "ci-info": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", - "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true }, "clean-stack": { @@ -670,9 +566,9 @@ "dev": true }, "colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, "combined-stream": { @@ -719,12 +615,12 @@ } }, "cypress": { - "version": "12.17.3", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.17.3.tgz", - "integrity": "sha512-/R4+xdIDjUSLYkiQfwJd630S81KIgicmQOLXotFxVXkl+eTeVO+3bHXxdi5KBh/OgC33HWN33kHX+0tQR/ZWpg==", + "version": "12.17.4", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.17.4.tgz", + "integrity": "sha512-gAN8Pmns9MA5eCDFSDJXWKUpaL3IDd89N9TtIupjYnzLSmlpVr+ZR+vb4U/qaMp+lB6tBvAmt7504c3Z4RU5KQ==", "dev": true, "requires": { - "@cypress/request": "^2.88.11", + "@cypress/request": "2.88.12", "@cypress/xvfb": "^1.2.4", "@types/node": "^16.18.39", "@types/sinonjs__fake-timers": "8.1.1", @@ -759,6 +655,7 @@ "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", + "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", "semver": "^7.5.3", @@ -796,9 +693,9 @@ } }, "dayjs": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz", - "integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==", + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", "dev": true }, "debug": { @@ -808,6 +705,14 @@ "dev": true, "requires": { "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "deepmerge": { @@ -815,6 +720,17 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, + "define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -899,12 +815,13 @@ } }, "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "requires": { - "ansi-colors": "^4.1.1" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" } }, "entities": { @@ -1060,6 +977,14 @@ "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true + } } }, "fs.realpath": { @@ -1069,21 +994,21 @@ "dev": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true }, "get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "dev": true, "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "get-stream": { @@ -1128,35 +1053,44 @@ } }, "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", "dev": true, "requires": { "ini": "2.0.0" } }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dev": true, "requires": { - "function-bind": "^1.1.1" + "get-intrinsic": "^1.1.3" } }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.2" + } + }, "has-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", @@ -1169,6 +1103,15 @@ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -1484,17 +1427,31 @@ "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true + } } }, "jsonwebtoken": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", - "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "requires": { "jws": "^3.2.2", - "lodash": "^4.17.21", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^7.3.8" + "semver": "^7.5.4" } }, "jsprim": { @@ -1551,9 +1508,9 @@ "integrity": "sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew==" }, "libmime": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.2.0.tgz", - "integrity": "sha512-X2U5Wx0YmK0rXFbk67ASMeqYIkZ6E5vY7pNWRKtnNzqjvdYYG8xtPDpCnuUEnPU9vlgNev+JoSrcaKSUaNvfsw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.2.1.tgz", + "integrity": "sha512-A0z9O4+5q+ZTj7QwNe/Juy1KARNb4WaviO4mYeFC4b8dBT2EEqK2pkM+GC8MVnkOjqhl5nYQxRgnPYRRTNmuSQ==", "requires": { "encoding-japanese": "2.0.0", "iconv-lite": "0.6.3", @@ -1567,11 +1524,11 @@ "integrity": "sha512-Ka0eC5LkF3IPNQHJmYBWljJsw0UvM6j+QdKRbWyCdTmYwvIDE6a7bCm0UkTAL/K+3KXK5qXT/ClcInU01OpdLg==" }, "linkify-it": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", - "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "requires": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "listr2": { @@ -1593,13 +1550,43 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, "log-symbols": { "version": "4.1.0", @@ -1656,32 +1643,19 @@ } }, "mailparser": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.6.5.tgz", - "integrity": "sha512-nteTpF0Khm5JLOnt4sigmzNdUH/6mO7PZ4KEnvxf4mckyXYFFhrtAWZzbq/V5aQMH+049gA7ZjfLdh+QiX2Uqg==", + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.6.6.tgz", + "integrity": "sha512-noCjBl3FToxmqTP2fp7z17hQsiCroWNntfTd8O+UejOAF59xeN5WGZK27ilexXV2e2X/cbUhG3L8sfEKaz0/sw==", "requires": { "encoding-japanese": "2.0.0", "he": "1.2.0", "html-to-text": "9.0.5", "iconv-lite": "0.6.3", "libmime": "5.2.1", - "linkify-it": "4.0.1", + "linkify-it": "5.0.0", "mailsplit": "5.4.0", - "nodemailer": "6.9.3", - "tlds": "1.240.0" - }, - "dependencies": { - "libmime": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.2.1.tgz", - "integrity": "sha512-A0z9O4+5q+ZTj7QwNe/Juy1KARNb4WaviO4mYeFC4b8dBT2EEqK2pkM+GC8MVnkOjqhl5nYQxRgnPYRRTNmuSQ==", - "requires": { - "encoding-japanese": "2.0.0", - "iconv-lite": "0.6.3", - "libbase64": "1.2.1", - "libqp": "2.0.1" - } - } + "nodemailer": "6.9.8", + "tlds": "1.248.0" } }, "mailsplit": { @@ -1692,6 +1666,19 @@ "libbase64": "1.2.1", "libmime": "5.2.0", "libqp": "2.0.1" + }, + "dependencies": { + "libmime": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.2.0.tgz", + "integrity": "sha512-X2U5Wx0YmK0rXFbk67ASMeqYIkZ6E5vY7pNWRKtnNzqjvdYYG8xtPDpCnuUEnPU9vlgNev+JoSrcaKSUaNvfsw==", + "requires": { + "encoding-japanese": "2.0.0", + "iconv-lite": "0.6.3", + "libbase64": "1.2.1", + "libqp": "2.0.1" + } + } } }, "merge-stream": { @@ -1743,9 +1730,9 @@ "dev": true }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "nodeify": { "version": "1.0.1", @@ -1757,9 +1744,9 @@ } }, "nodemailer": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.3.tgz", - "integrity": "sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==" + "version": "6.9.8", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.8.tgz", + "integrity": "sha512-cfrYUk16e67Ks051i4CntM9kshRYei1/o/Gi8K1d+R34OIs21xdFnW7Pt7EucmVKA0LKtqUGNcjMZ7ehjl49mQ==" }, "npm-run-path": { "version": "4.0.1", @@ -1771,9 +1758,9 @@ } }, "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "dev": true }, "once": { @@ -1877,6 +1864,12 @@ "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", "dev": true }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true + }, "promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/promise/-/promise-1.3.0.tgz", @@ -1986,9 +1979,9 @@ } }, "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", "dev": true }, "rimraf": { @@ -2001,9 +1994,9 @@ } }, "rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "requires": { "tslib": "^2.1.0" @@ -2035,6 +2028,19 @@ "lru-cache": "^6.0.0" } }, + "set-function-length": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", + "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "dev": true, + "requires": { + "define-data-property": "^1.1.1", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.2", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2085,9 +2091,9 @@ } }, "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "requires": { "asn1": "~0.2.3", @@ -2112,6 +2118,17 @@ "strip-ansi": "^6.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -2126,6 +2143,15 @@ "ansi-regex": "^5.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -2142,9 +2168,9 @@ } }, "throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", "dev": true }, "through": { @@ -2154,9 +2180,9 @@ "dev": true }, "tlds": { - "version": "1.240.0", - "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.240.0.tgz", - "integrity": "sha512-1OYJQenswGZSOdRw7Bql5Qu7uf75b+F3HFBXbqnG/ifHa0fev1XcG+3pJf3pA/KC6RtHQzfKgIf1vkMlMG7mtQ==" + "version": "1.248.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.248.0.tgz", + "integrity": "sha512-noj0KdpWTBhwsKxMOXk0rN9otg4kTgLm4WohERRHbJ9IY+kSDKr3RmjitaQ3JFzny+DyvBOQKlFZhp0G0qNSfg==" }, "tmp": { "version": "0.2.1", @@ -2177,20 +2203,12 @@ "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" - }, - "dependencies": { - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - } } }, "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, "tunnel-agent": { @@ -2215,14 +2233,14 @@ "dev": true }, "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.0.0.tgz", + "integrity": "sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==" }, "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", "dev": true }, "untildify": { @@ -2312,6 +2330,17 @@ "strip-ansi": "^6.0.0" } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", From 4008f129a954f0731d3035644e6378f6b616eadf Mon Sep 17 00:00:00 2001 From: "sre-read-write[bot]" <92993749+sre-read-write[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 09:14:52 -0500 Subject: [PATCH 17/33] chore: synced local '.github/workflows/ossf-scorecard.yml' with remote 'tools/sre_file_sync/ossf-scorecard.yml' (#2098) Co-authored-by: sre-read-write[bot] <92993749+sre-read-write[bot]@users.noreply.github.com> --- .github/workflows/ossf-scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index d499643c63..69eba84060 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -25,7 +25,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@75cb7af1033cfb77c9fc7d8abc30420008f558f4 + uses: ossf/scorecard-action@155cf0ea68b491a7c47af606d2741b54963ecb04 with: results_file: ossf-results.json results_format: json From 25229a27c8acc613b4dbb3032344a0793e3272cb Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 1 Feb 2024 13:04:33 -0400 Subject: [PATCH 18/33] Fix time range for for daily stats (#2097) * fix(daily stats): for daily stats, use the time range of now back until midnight UTC as this is what is used to apply limits in the app * chore: formatting * fix: remove testing code * chore: remove unused function --------- Co-authored-by: Jumana B --- app/dao/fact_notification_status_dao.py | 18 ++++++++++----- .../dao/test_fact_notification_status_dao.py | 23 +++++++------------ 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/app/dao/fact_notification_status_dao.py b/app/dao/fact_notification_status_dao.py index e67903428e..f210622a2b 100644 --- a/app/dao/fact_notification_status_dao.py +++ b/app/dao/fact_notification_status_dao.py @@ -239,13 +239,19 @@ def fetch_notification_status_for_service_for_day(bst_day, service_id): def fetch_notification_status_for_service_for_today_and_7_previous_days(service_id, by_template=False, limit_days=7): - ft_start_date = utc_midnight_n_days_ago(limit_days) + if limit_days == 1: + ft_start_date = utc_midnight_n_days_ago(limit_days - 1) + # For daily stats, service limits reset at 12:00am UTC each night, so we need to fetch the data from 12:00 UTC to now + start = utc_midnight_n_days_ago(0) + end = datetime.utcnow() + else: + ft_start_date = utc_midnight_n_days_ago(limit_days) - # The nightly task that populates ft_notification_status counts collects notifications from - # 5AM the day before to 5AM of the current day. So we need to match that timeframe when - # we fetch notifications for the current day. - start = (tz_aware_midnight_n_days_ago(1) + timedelta(hours=5)).replace(minute=0, second=0, microsecond=0) - end = (tz_aware_midnight_n_days_ago(0) + timedelta(hours=5)).replace(minute=0, second=0, microsecond=0) + # The nightly task that populates ft_notification_status counts collects notifications from + # 5AM the day before to 5AM of the current day. So we need to match that timeframe when + # we fetch notifications for the current day. + start = (tz_aware_midnight_n_days_ago(1) + timedelta(hours=5)).replace(minute=0, second=0, microsecond=0) + end = (tz_aware_midnight_n_days_ago(0) + timedelta(hours=5)).replace(minute=0, second=0, microsecond=0) stats_for_7_days = db.session.query( FactNotificationStatus.notification_type.label("notification_type"), diff --git a/tests/app/dao/test_fact_notification_status_dao.py b/tests/app/dao/test_fact_notification_status_dao.py index 663cf859f8..70b2c4ac50 100644 --- a/tests/app/dao/test_fact_notification_status_dao.py +++ b/tests/app/dao/test_fact_notification_status_dao.py @@ -1158,29 +1158,22 @@ def test_fetch_notification_status_for_service_for_today_handles_midnight_utc( email_template = create_template(service=service_1, template_type=EMAIL_TYPE) # create notifications that should not be included in today's count - create_ft_notification_status(date(2018, 10, 29), "email", service_1, count=30) + create_ft_notification_status(date(2018, 10, 24), "email", service_1, count=30) + create_ft_notification_status(date(2018, 10, 31), "email", service_1, count=20) + save_notification(create_notification(email_template, created_at=datetime(2018, 10, 31, 0, 0, 0), status="delivered")) save_notification(create_notification(email_template, created_at=datetime(2018, 10, 31, 11, 59, 59), status="delivered")) save_notification(create_notification(email_template, created_at=datetime(2018, 10, 31, 11, 59, 59), status="delivered")) save_notification(create_notification(email_template, created_at=datetime(2018, 10, 31, 23, 59, 59), status="delivered")) # create notifications that should be included in count - create_ft_notification_status(date(2018, 10, 31), "email", service_1, count=5) - create_ft_notification_status(date(2018, 10, 30), "email", service_1, count=5) - save_notification(create_notification(email_template, created_at=datetime(2018, 10, 31, 13, 0, 0), status="delivered")) - save_notification(create_notification(email_template, created_at=datetime(2018, 10, 31, 6, 0, 0), status="delivered")) - save_notification(create_notification(email_template, created_at=datetime(2018, 11, 1, 22, 59, 59), status="delivered")) + save_notification(create_notification(email_template, created_at=datetime(2018, 11, 1, 13, 0, 0), status="delivered")) + save_notification(create_notification(email_template, created_at=datetime(2018, 11, 1, 6, 0, 0), status="delivered")) + save_notification(create_notification(email_template, created_at=datetime(2018, 11, 1, 17, 59, 59), status="delivered")) - # checking the daily stats for this day should give us the 2 created after 12am UTC + # checking the daily stats for this day should give us the 3 created after 12am UTC results = sorted( fetch_notification_status_for_service_for_today_and_7_previous_days(service_1.id, limit_days=1), key=lambda x: (x.notification_type, x.status), ) - assert results[0][2] == 6 - - # checking the daily stats for the last 2 days should give us the 2 created after 12am UTC and the 1 from the day before - results = sorted( - fetch_notification_status_for_service_for_today_and_7_previous_days(service_1.id, limit_days=2), - key=lambda x: (x.notification_type, x.status), - ) - assert results[0][2] == 11 + assert results[0][2] == 3 From 68bcfe8f8ce94784468a325226c307d39d66ec1b Mon Sep 17 00:00:00 2001 From: Jumana B Date: Fri, 2 Feb 2024 10:04:59 -0500 Subject: [PATCH 19/33] Reverting notification history (#2102) --- app/dao/fact_notification_status_dao.py | 9 +------ .../dao/test_fact_notification_status_dao.py | 26 +++++++++---------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/app/dao/fact_notification_status_dao.py b/app/dao/fact_notification_status_dao.py index f210622a2b..86c2404708 100644 --- a/app/dao/fact_notification_status_dao.py +++ b/app/dao/fact_notification_status_dao.py @@ -362,14 +362,7 @@ def get_last_send_for_api_key(api_key_id): .filter(Notification.api_key_id == api_key_id) .all() ) - if not notification_table[0][0]: - notification_table = ( - db.session.query(func.max(NotificationHistory.created_at).label("last_notification_created")) - .filter(NotificationHistory.api_key_id == api_key_id) - .all() - ) - notification_table = [] if notification_table[0][0] is None else notification_table - return notification_table + return [] if notification_table[0][0] is None else notification_table return api_key_table diff --git a/tests/app/dao/test_fact_notification_status_dao.py b/tests/app/dao/test_fact_notification_status_dao.py index 70b2c4ac50..85cd078d91 100644 --- a/tests/app/dao/test_fact_notification_status_dao.py +++ b/tests/app/dao/test_fact_notification_status_dao.py @@ -423,24 +423,24 @@ def test_get_api_key_ranked_by_notifications_created(notify_db_session): assert int(second_place[8]) == sms_sends -def test_last_used_for_api_key(notify_db_session): - service = create_service(service_name="Service 1") - api_key_1 = create_api_key(service, key_type=KEY_TYPE_NORMAL, key_name="Key 1") - api_key_2 = create_api_key(service, key_type=KEY_TYPE_NORMAL, key_name="Key 2") - api_key_3 = create_api_key(service, key_type=KEY_TYPE_NORMAL, key_name="Key 3") - template_email = create_template(service=service, template_type="email") - create_notification_history(template=template_email, api_key=api_key_1, created_at="2022-03-04") - save_notification(create_notification(template=template_email, api_key=api_key_1, created_at="2022-03-05")) +# def test_last_used_for_api_key(notify_db_session): +# service = create_service(service_name="Service 1") +# api_key_1 = create_api_key(service, key_type=KEY_TYPE_NORMAL, key_name="Key 1") +# api_key_2 = create_api_key(service, key_type=KEY_TYPE_NORMAL, key_name="Key 2") +# api_key_3 = create_api_key(service, key_type=KEY_TYPE_NORMAL, key_name="Key 3") +# template_email = create_template(service=service, template_type="email") +# create_notification_history(template=template_email, api_key=api_key_1, created_at="2022-03-04") +# save_notification(create_notification(template=template_email, api_key=api_key_1, created_at="2022-03-05")) - assert (get_last_send_for_api_key(str(api_key_1.id))[0][0]).strftime("%Y-%m-%d") == "2022-03-05" +# assert (get_last_send_for_api_key(str(api_key_1.id))[0][0]).strftime("%Y-%m-%d") == "2022-03-05" - save_notification(create_notification(template=template_email, api_key=api_key_2, created_at="2022-03-06")) +# save_notification(create_notification(template=template_email, api_key=api_key_2, created_at="2022-03-06")) - assert (get_last_send_for_api_key(str(api_key_2.id))[0][0]).strftime("%Y-%m-%d") == "2022-03-06" +# assert (get_last_send_for_api_key(str(api_key_2.id))[0][0]).strftime("%Y-%m-%d") == "2022-03-06" - create_notification_history(template=template_email, api_key=api_key_3, created_at="2022-03-07") +# create_notification_history(template=template_email, api_key=api_key_3, created_at="2022-03-07") - assert (get_last_send_for_api_key(str(api_key_3.id))[0][0]).strftime("%Y-%m-%d") == "2022-03-07" +# assert (get_last_send_for_api_key(str(api_key_3.id))[0][0]).strftime("%Y-%m-%d") == "2022-03-07" @pytest.mark.parametrize( From adea58f6684a1c79b526a6598f39ef11407fcb20 Mon Sep 17 00:00:00 2001 From: Jumana B Date: Mon, 5 Feb 2024 09:25:29 -0500 Subject: [PATCH 20/33] Update api with new utils (#2101) * Update api with new utils * Fix utils upgrade --- .github/workflows/test.yaml | 2 +- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 96e08b4935..e74ffd0f67 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -67,7 +67,7 @@ jobs: run: | cp -f .env.example .env - name: Checks for new endpoints against AWS WAF rules - uses: cds-snc/notification-utils/.github/actions/waffles@69b6eee0c715fa4c3fe9ac48e4ccd6e3d4c1e239 # 52.0.18 + uses: cds-snc/notification-utils/.github/actions/waffles@2da74685e0ffb220f0403e1f2584e783be99bbad # 52.1.0 with: app-loc: '/github/workspace' app-libs: '/github/workspace/env/site-packages' diff --git a/poetry.lock b/poetry.lock index e064e39557..611d76a255 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2523,7 +2523,7 @@ requests = ">=2.0.0" [[package]] name = "notifications-utils" -version = "52.0.19" +version = "52.1.0" description = "Shared python code for Notification - Provides logging utils etc." category = "main" optional = false @@ -2559,8 +2559,8 @@ werkzeug = "2.3.7" [package.source] type = "git" url = "https://github.com/cds-snc/notifier-utils.git" -reference = "52.0.19" -resolved_reference = "10210526a168a5d0fe013932144208845b4737b8" +reference = "52.1.0" +resolved_reference = "2da74685e0ffb220f0403e1f2584e783be99bbad" [[package]] name = "ordered-set" @@ -4419,4 +4419,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "~3.10.9" -content-hash = "ec1cf85359f79c3cf3d8d2cdc1aab0383d55b5b58923adaa4aa8042affcd3b84" +content-hash = "dd1297fd4e697861c78516a245501092c596648e49449bfb4283e21ccd219cdc" diff --git a/pyproject.toml b/pyproject.toml index 19d64023dc..42bf05899a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ Werkzeug = "2.3.7" MarkupSafe = "2.1.4" # REVIEW: v2 is using sha512 instead of sha1 by default (in v1) itsdangerous = "2.1.2" -notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", rev = "52.0.19" } +notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", rev = "52.1.0" } # rsa = "4.9 # awscli 1.22.38 depends on rsa<4.8 typing-extensions = "4.7.1" greenlet = "2.0.2" From 35640af29e5b014b2b3b3d80ada44c7e643fff3e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:30:06 -0500 Subject: [PATCH 21/33] fix(deps): update notifications-utils digest to 2da7468 (#2103) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 183 ++----------------------------------------------- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 179 deletions(-) diff --git a/poetry.lock b/poetry.lock index 611d76a255..7014e7e5fd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.9.1" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -101,7 +100,6 @@ speedups = ["Brotli", "aiodns", "brotlicffi"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -116,7 +114,6 @@ frozenlist = ">=1.1.0" name = "alembic" version = "1.12.1" description = "A database migration tool for SQLAlchemy." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -136,7 +133,6 @@ tz = ["python-dateutil"] name = "amqp" version = "5.2.0" description = "Low-level AMQP client for Python (fork of amqplib)." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -151,7 +147,6 @@ vine = ">=5.0.0,<6.0.0" name = "annotated-types" version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -163,7 +158,6 @@ files = [ name = "apig-wsgi" version = "2.18.0" description = "Wrap a WSGI application in an AWS Lambda handler function for running on API Gateway or an ALB." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -175,7 +169,6 @@ files = [ name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -187,7 +180,6 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -206,7 +198,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "aws-embedded-metrics" version = "1.0.8" description = "AWS Embedded Metrics Package" -category = "main" optional = false python-versions = "*" files = [ @@ -221,7 +212,6 @@ aiohttp = "*" name = "awscli" version = "1.32.25" description = "Universal Command Line Environment for AWS." -category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -241,7 +231,6 @@ s3transfer = ">=0.10.0,<0.11.0" name = "awscli-cwlogs" version = "1.4.6" description = "AWSCLI CloudWatch Logs plugin" -category = "main" optional = false python-versions = "*" files = [ @@ -258,7 +247,6 @@ six = ">=1.1.0" name = "bcrypt" version = "4.1.1" description = "Modern password hashing for your software and your servers" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -289,7 +277,6 @@ typecheck = ["mypy"] name = "billiard" version = "4.2.0" description = "Python multiprocessing fork with improvements and bugfixes" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -301,7 +288,6 @@ files = [ name = "black" version = "23.7.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -347,7 +333,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "bleach" version = "6.0.0" description = "An easy safelist-based HTML-sanitizing tool." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -366,7 +351,6 @@ css = ["tinycss2 (>=1.1.0,<1.2)"] name = "blinker" version = "1.7.0" description = "Fast, simple object-to-object and broadcast signaling" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -378,7 +362,6 @@ files = [ name = "boto" version = "2.49.0" description = "Amazon Web Services Library" -category = "main" optional = false python-versions = "*" files = [ @@ -390,7 +373,6 @@ files = [ name = "boto3" version = "1.34.25" description = "The AWS SDK for Python" -category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -410,7 +392,6 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] name = "botocore" version = "1.34.25" description = "Low-level, data-driven core of boto 3." -category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -430,7 +411,6 @@ crt = ["awscrt (==0.19.19)"] name = "brotli" version = "1.1.0" description = "Python bindings for the Brotli compression library" -category = "dev" optional = false python-versions = "*" files = [ @@ -523,7 +503,6 @@ files = [ name = "cachelib" version = "0.10.2" description = "A collection of cache libraries in the same API interface." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -535,7 +514,6 @@ files = [ name = "cachetools" version = "4.2.4" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = "~=3.5" files = [ @@ -547,7 +525,6 @@ files = [ name = "celery" version = "5.3.6" description = "Distributed Task Queue." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -609,7 +586,6 @@ zstd = ["zstandard (==0.22.0)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -621,7 +597,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -698,7 +673,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -798,7 +772,6 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -813,7 +786,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "click-datetime" version = "0.2" description = "Datetime type support for click." -category = "main" optional = false python-versions = "*" files = [ @@ -831,7 +803,6 @@ dev = ["wheel"] name = "click-didyoumean" version = "0.3.0" description = "Enables git-like *did-you-mean* feature in click" -category = "main" optional = false python-versions = ">=3.6.2,<4.0.0" files = [ @@ -846,7 +817,6 @@ click = ">=7" name = "click-plugins" version = "1.1.1" description = "An extension module for click to enable registering CLI commands via setuptools entry-points." -category = "main" optional = false python-versions = "*" files = [ @@ -864,7 +834,6 @@ dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] name = "click-repl" version = "0.3.0" description = "REPL plugin for Click" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -883,7 +852,6 @@ testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -895,7 +863,6 @@ files = [ 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." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -911,7 +878,6 @@ yaml = ["PyYAML"] name = "coverage" version = "5.5" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" files = [ @@ -979,7 +945,6 @@ toml = ["toml"] name = "coveralls" version = "3.3.1" description = "Show coverage stats online via coveralls.io" -category = "dev" optional = false python-versions = ">= 3.5" files = [ @@ -988,7 +953,7 @@ files = [ ] [package.dependencies] -coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" +coverage = ">=4.1,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<7.0" docopt = ">=0.6.1" requests = ">=1.0.0" @@ -999,7 +964,6 @@ yaml = ["PyYAML (>=3.10)"] name = "cryptography" version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1045,7 +1009,6 @@ test-randomorder = ["pytest-randomly"] name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" -category = "main" optional = false python-versions = "*" files = [ @@ -1056,7 +1019,6 @@ files = [ name = "docutils" version = "0.16" description = "Docutils -- Python Documentation Utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1068,7 +1030,6 @@ files = [ name = "environs" version = "9.5.0" description = "simplified environment variable parsing" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1090,7 +1051,6 @@ tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1105,7 +1065,6 @@ test = ["pytest (>=6)"] name = "execnet" version = "2.0.2" description = "execnet: rapid multi-Python deployment" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1120,7 +1079,6 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] name = "fido2" version = "0.9.3" description = "Python based FIDO 2.0 library" -category = "main" optional = false python-versions = ">=2.7.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -1138,7 +1096,6 @@ pcsc = ["pyscard"] name = "filelock" version = "3.13.1" description = "A platform independent file lock." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1155,7 +1112,6 @@ typing = ["typing-extensions (>=4.8)"] name = "flake8" version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -1172,7 +1128,6 @@ pyflakes = ">=3.1.0,<3.2.0" name = "flask" version = "2.3.3" description = "A simple framework for building complex web applications." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1195,7 +1150,6 @@ dotenv = ["python-dotenv"] name = "flask-basicauth" version = "0.2.0" description = "HTTP basic access authentication for Flask." -category = "dev" optional = false python-versions = "*" files = [ @@ -1209,7 +1163,6 @@ Flask = "*" name = "flask-bcrypt" version = "1.0.1" description = "Brcrypt hashing for Flask." -category = "main" optional = false python-versions = "*" files = [ @@ -1225,7 +1178,6 @@ Flask = "*" name = "flask-cors" version = "4.0.0" description = "A Flask extension adding a decorator for CORS support" -category = "dev" optional = false python-versions = "*" files = [ @@ -1240,7 +1192,6 @@ Flask = ">=0.9" name = "flask-marshmallow" version = "0.14.0" description = "Flask + marshmallow for beautiful APIs" -category = "main" optional = false python-versions = "*" files = [ @@ -1264,7 +1215,6 @@ tests = ["flask-sqlalchemy", "marshmallow-sqlalchemy (>=0.13.0)", "marshmallow-s name = "flask-migrate" version = "2.7.0" description = "SQLAlchemy database migrations for Flask applications using Alembic" -category = "main" optional = false python-versions = "*" files = [ @@ -1281,7 +1231,6 @@ Flask-SQLAlchemy = ">=1.0" name = "flask-redis" version = "0.4.0" description = "A nice way to use Redis in your Flask app" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1301,7 +1250,6 @@ tests = ["coverage", "pytest", "pytest-mock"] name = "Flask-SQLAlchemy" version = "2.3.2.dev20231128" description = "Adds SQLAlchemy support to your Flask application" -category = "main" optional = false python-versions = "*" files = [] @@ -1321,7 +1269,6 @@ resolved_reference = "500e732dd1b975a56ab06a46bd1a20a21e682262" name = "freezegun" version = "1.2.2" description = "Let your Python tests travel through time" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1336,7 +1283,6 @@ python-dateutil = ">=2.7" name = "frozenlist" version = "1.4.0" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1407,7 +1353,6 @@ files = [ name = "future" version = "0.18.3" description = "Clean single-source support for Python 3 and 2" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1418,7 +1363,6 @@ files = [ name = "gevent" version = "23.9.1" description = "Coroutine-based network library" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1481,7 +1425,6 @@ test = ["cffi (>=1.12.2)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idn name = "geventhttpclient" version = "2.0.11" description = "http client library for gevent" -category = "dev" optional = false python-versions = "*" files = [ @@ -1605,7 +1548,6 @@ six = "*" name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -1683,7 +1625,6 @@ test = ["objgraph", "psutil"] name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1704,7 +1645,6 @@ tornado = ["tornado (>=0.2)"] name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1716,7 +1656,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1728,7 +1667,6 @@ files = [ name = "iso8601" version = "2.0.0" description = "Simple module to parse ISO 8601 dates" -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -1740,7 +1678,6 @@ files = [ name = "isodate" version = "0.6.1" description = "An ISO 8601 date/time/duration parser and formatter" -category = "main" optional = false python-versions = "*" files = [ @@ -1755,7 +1692,6 @@ six = "*" name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1773,7 +1709,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1785,7 +1720,6 @@ files = [ name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1803,7 +1737,6 @@ i18n = ["Babel (>=2.7)"] name = "jinja2-cli" version = "0.8.2" description = "A CLI interface to Jinja2" -category = "dev" optional = false python-versions = "*" files = [ @@ -1825,7 +1758,6 @@ yaml = ["jinja2", "pyyaml"] name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1837,7 +1769,6 @@ files = [ name = "jsonschema" version = "3.2.0" description = "An implementation of JSON Schema validation for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -1859,7 +1790,6 @@ format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-va name = "kombu" version = "5.3.4" description = "Messaging library for Python." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1895,7 +1825,6 @@ zookeeper = ["kazoo (>=2.8.0)"] name = "locust" version = "2.16.1" description = "Developer friendly load testing framework" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1923,7 +1852,6 @@ Werkzeug = ">=2.0.0" name = "lxml" version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ @@ -2031,7 +1959,6 @@ source = ["Cython (>=0.29.35)"] name = "mako" version = "1.3.0" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2051,7 +1978,6 @@ testing = ["pytest"] name = "markupsafe" version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2121,7 +2047,6 @@ files = [ name = "marshmallow" version = "3.20.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2142,7 +2067,6 @@ tests = ["pytest", "pytz", "simplejson"] name = "marshmallow-sqlalchemy" version = "0.29.0" description = "SQLAlchemy integration with the marshmallow (de)serialization library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2165,7 +2089,6 @@ tests = ["pytest", "pytest-lazy-fixture (>=0.6.2)"] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2177,7 +2100,6 @@ files = [ name = "mistune" version = "0.8.4" description = "The fastest markdown parser in pure Python" -category = "main" optional = false python-versions = "*" files = [ @@ -2189,7 +2111,6 @@ files = [ name = "more-itertools" version = "8.14.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2201,7 +2122,6 @@ files = [ name = "moto" version = "4.1.11" description = "" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2248,7 +2168,6 @@ xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] name = "msgpack" version = "1.0.7" description = "MessagePack serializer" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2314,7 +2233,6 @@ files = [ name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2398,7 +2316,6 @@ files = [ name = "mypy" version = "1.5.0" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2440,7 +2357,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2452,7 +2368,6 @@ files = [ name = "nanoid" version = "2.0.0" description = "A tiny, secure, URL-friendly, unique string ID generator for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -2464,7 +2379,6 @@ files = [ name = "networkx" version = "2.8.8" description = "Python package for creating and manipulating graphs and networks" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2483,7 +2397,6 @@ test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] name = "newrelic" version = "6.10.0.165" description = "New Relic Python Agent" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -2509,7 +2422,6 @@ infinite-tracing = ["grpcio (<2)", "protobuf (<4)"] name = "notifications-python-client" version = "6.4.1" description = "Python API client for GOV.UK Notify." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2525,7 +2437,6 @@ requests = ">=2.0.0" name = "notifications-utils" version = "52.1.0" description = "Shared python code for Notification - Provides logging utils etc." -category = "main" optional = false python-versions = "~3.10" files = [] @@ -2559,14 +2470,13 @@ werkzeug = "2.3.7" [package.source] type = "git" url = "https://github.com/cds-snc/notifier-utils.git" -reference = "52.1.0" +reference = "2da74685e0ffb220f0403e1f2584e783be99bbad" resolved_reference = "2da74685e0ffb220f0403e1f2584e783be99bbad" [[package]] name = "ordered-set" version = "4.1.0" description = "An OrderedSet is a custom MutableSet that remembers its order, so that every" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2581,7 +2491,6 @@ dev = ["black", "mypy", "pytest"] name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2593,7 +2502,6 @@ files = [ name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2605,7 +2513,6 @@ files = [ name = "pendulum" version = "2.1.2" description = "Python datetimes made easy" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2640,7 +2547,6 @@ pytzdata = ">=2020.1" name = "phonenumbers" version = "8.13.28" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." -category = "main" optional = false python-versions = "*" files = [ @@ -2652,7 +2558,6 @@ files = [ name = "platformdirs" version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2668,7 +2573,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2684,7 +2588,6 @@ testing = ["pytest", "pytest-benchmark"] name = "prompt-toolkit" version = "3.0.41" description = "Library for building powerful interactive command lines in Python" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -2699,7 +2602,6 @@ wcwidth = "*" name = "psutil" version = "5.9.6" description = "Cross-platform lib for process and system monitoring in Python." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -2728,7 +2630,6 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] name = "psycopg2-binary" version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2810,7 +2711,6 @@ files = [ name = "pwnedpasswords" version = "2.0.0" description = "A Python wrapper for Troy Hunt's Pwned Passwords API." -category = "main" optional = false python-versions = "*" files = [ @@ -2824,7 +2724,6 @@ future = "*" name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2836,7 +2735,6 @@ files = [ name = "py-w3c" version = "0.3.1" description = "W3C services for python." -category = "main" optional = false python-versions = "*" files = [ @@ -2847,7 +2745,6 @@ files = [ name = "pyasn1" version = "0.5.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -2859,7 +2756,6 @@ files = [ name = "pycodestyle" version = "2.11.1" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2871,7 +2767,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2883,7 +2778,6 @@ files = [ name = "pycurl" version = "7.45.2" description = "PycURL -- A Python Interface To The cURL library" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2894,7 +2788,6 @@ files = [ name = "pydantic" version = "2.5.2" description = "Data validation using Python type hints" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2914,7 +2807,6 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.14.5" description = "" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3032,7 +2924,6 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pyflakes" version = "3.1.0" description = "passive checker of Python programs" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3044,7 +2935,6 @@ files = [ name = "pyjwt" version = "2.8.0" description = "JSON Web Token implementation in Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3062,7 +2952,6 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] name = "pypdf2" version = "1.28.6" description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" -category = "main" optional = false python-versions = ">=2.7" files = [ @@ -3074,7 +2963,6 @@ files = [ name = "pyrsistent" version = "0.20.0" description = "Persistent/Functional/Immutable data structures" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3116,7 +3004,6 @@ files = [ name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3139,7 +3026,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-cov" version = "3.0.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3158,7 +3044,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-env" version = "0.8.2" description = "py.test plugin that allows you to add environment variables." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3176,7 +3061,6 @@ test = ["coverage (>=7.2.7)", "pytest-mock (>=3.10)"] name = "pytest-forked" version = "1.6.0" description = "run tests in isolated forked subprocesses" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3192,7 +3076,6 @@ pytest = ">=3.10" name = "pytest-mock" version = "3.11.1" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3210,7 +3093,6 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "pytest-mock-resources" version = "2.9.2" description = "A pytest plugin for easily instantiating reproducible mock resources." -category = "dev" optional = false python-versions = ">=3.7,<4" files = [ @@ -3241,7 +3123,6 @@ redshift = ["boto3", "filelock", "moto", "python-on-whales (>=0.22.0)", "sqlpars name = "pytest-xdist" version = "2.5.0" description = "pytest xdist plugin for distributed testing and loop-on-failing modes" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3263,7 +3144,6 @@ testing = ["filelock"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -3278,7 +3158,6 @@ six = ">=1.5" name = "python-dotenv" version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3293,7 +3172,6 @@ cli = ["click (>=5.0)"] name = "python-json-logger" version = "2.0.7" description = "A python library adding a json log formatter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3305,7 +3183,6 @@ files = [ name = "python-magic" version = "0.4.27" description = "File type identification using libmagic" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -3317,7 +3194,6 @@ files = [ name = "python-on-whales" version = "0.67.0" description = "A Docker client for Python, designed to be fun and intuitive!" -category = "dev" optional = false python-versions = "<4,>=3.8" files = [ @@ -3326,7 +3202,7 @@ files = [ ] [package.dependencies] -pydantic = ">=1.9,<2.0.0 || >=2.1.0,<3" +pydantic = ">=1.9,<2.0.dev0 || >=2.1.dev0,<3" requests = "*" tqdm = "*" typer = ">=0.4.1" @@ -3339,7 +3215,6 @@ test = ["pytest"] name = "pytz" version = "2021.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -3351,7 +3226,6 @@ files = [ name = "pytzdata" version = "2020.1" description = "The Olson timezone database for Python." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3363,7 +3237,6 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" -category = "dev" optional = false python-versions = "*" files = [ @@ -3387,7 +3260,6 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3448,7 +3320,6 @@ files = [ name = "pyzmq" version = "25.1.1" description = "Python bindings for 0MQ" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3554,7 +3425,6 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} name = "redis" version = "5.0.1" description = "Python client for Redis database and key-value store" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3573,7 +3443,6 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3595,7 +3464,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-file" version = "1.5.1" description = "File transport adapter for Requests" -category = "main" optional = false python-versions = "*" files = [ @@ -3611,7 +3479,6 @@ six = "*" name = "requests-mock" version = "1.11.0" description = "Mock out responses from the requests package" -category = "dev" optional = false python-versions = "*" files = [ @@ -3631,7 +3498,6 @@ test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "tes name = "requests-toolbelt" version = "1.0.0" description = "A utility belt for advanced users of python-requests" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3646,7 +3512,6 @@ requests = ">=2.0.1,<3.0.0" name = "responses" version = "0.24.1" description = "A utility library for mocking out the `requests` Python library." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3666,7 +3531,6 @@ tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asy name = "rfc3987" version = "1.3.8" description = "Parsing and validation of URIs (RFC 3986) and IRIs (RFC 3987)" -category = "dev" optional = false python-versions = "*" files = [ @@ -3678,7 +3542,6 @@ files = [ name = "roundrobin" version = "0.0.4" description = "Collection of roundrobin utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -3689,7 +3552,6 @@ files = [ name = "rsa" version = "4.7.2" description = "Pure-Python RSA implementation" -category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -3704,7 +3566,6 @@ pyasn1 = ">=0.1.3" name = "s3transfer" version = "0.10.0" description = "An Amazon S3 Transfer Manager" -category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -3722,7 +3583,6 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] name = "setuptools" version = "69.0.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3739,7 +3599,6 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "simple-salesforce" version = "1.12.5" description = "A basic Salesforce.com REST API client." -category = "main" optional = false python-versions = "*" files = [ @@ -3759,7 +3618,6 @@ zeep = "*" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3771,7 +3629,6 @@ files = [ name = "smartypants" version = "2.0.1" description = "Python with the SmartyPants" -category = "main" optional = false python-versions = "*" files = [ @@ -3782,7 +3639,6 @@ files = [ name = "sqlalchemy" version = "1.4.51" description = "Database Abstraction Library" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -3862,7 +3718,6 @@ sqlcipher = ["sqlcipher3_binary"] name = "sqlalchemy-stubs" version = "0.4" description = "SQLAlchemy stubs and mypy plugin" -category = "dev" optional = false python-versions = "*" files = [ @@ -3878,7 +3733,6 @@ typing-extensions = ">=3.7.4" name = "sqlalchemy2-stubs" version = "0.0.2a38" description = "Typing Stubs for SQLAlchemy 1.4" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3893,7 +3747,6 @@ typing-extensions = ">=3.7.4" name = "statsd" version = "3.3.0" description = "A simple statsd client." -category = "main" optional = false python-versions = "*" files = [ @@ -3905,7 +3758,6 @@ files = [ name = "strict-rfc3339" version = "0.7" description = "Strict, simple, lightweight RFC3339 functions" -category = "dev" optional = false python-versions = "*" files = [ @@ -3916,7 +3768,6 @@ files = [ name = "tldextract" version = "3.4.4" description = "Accurately separates a URL's subdomain, domain, and public suffix, using the Public Suffix List (PSL). By default, this includes the public ICANN TLDs and their exceptions. You can optionally support the Public Suffix List's private domains as well." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3934,7 +3785,6 @@ requests-file = ">=1.4" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3946,7 +3796,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3958,7 +3807,6 @@ files = [ name = "tqdm" version = "4.66.1" description = "Fast, Extensible Progress Meter" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3979,7 +3827,6 @@ telegram = ["requests"] name = "typer" version = "0.9.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -4001,7 +3848,6 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. name = "types-boto" version = "2.49.18.9" description = "Typing stubs for boto" -category = "dev" optional = false python-versions = "*" files = [ @@ -4013,7 +3859,6 @@ files = [ name = "types-mock" version = "4.0.15.2" description = "Typing stubs for mock" -category = "dev" optional = false python-versions = "*" files = [ @@ -4025,7 +3870,6 @@ files = [ name = "types-pyopenssl" version = "23.3.0.0" description = "Typing stubs for pyOpenSSL" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4040,7 +3884,6 @@ cryptography = ">=35.0.0" name = "types-python-dateutil" version = "2.8.19.20240106" description = "Typing stubs for python-dateutil" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4052,7 +3895,6 @@ files = [ name = "types-pytz" version = "2022.7.1.2" description = "Typing stubs for pytz" -category = "dev" optional = false python-versions = "*" files = [ @@ -4064,7 +3906,6 @@ files = [ name = "types-redis" version = "4.6.0.20240106" description = "Typing stubs for redis" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4080,7 +3921,6 @@ types-pyOpenSSL = "*" name = "types-requests" version = "2.31.0.20240106" description = "Typing stubs for requests" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4095,7 +3935,6 @@ urllib3 = ">=2" name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4107,7 +3946,6 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -4119,7 +3957,6 @@ files = [ name = "unidecode" version = "1.3.8" description = "ASCII transliterations of Unicode text" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -4131,7 +3968,6 @@ files = [ name = "urllib3" version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4149,7 +3985,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "vine" version = "5.1.0" description = "Python promises." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -4161,7 +3996,6 @@ files = [ name = "wcwidth" version = "0.2.12" description = "Measures the displayed width of unicode strings in a terminal" -category = "main" optional = false python-versions = "*" files = [ @@ -4173,7 +4007,6 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" -category = "main" optional = false python-versions = "*" files = [ @@ -4185,7 +4018,6 @@ files = [ name = "werkzeug" version = "2.3.7" description = "The comprehensive WSGI web application library." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4203,7 +4035,6 @@ watchdog = ["watchdog (>=2.3)"] name = "xmltodict" version = "0.13.0" description = "Makes working with XML feel like you are working with JSON" -category = "dev" optional = false python-versions = ">=3.4" files = [ @@ -4215,7 +4046,6 @@ files = [ name = "yarl" version = "1.9.3" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4319,7 +4149,6 @@ multidict = ">=4.0" name = "zeep" version = "4.2.1" description = "A Python SOAP client" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4347,7 +4176,6 @@ xmlsec = ["xmlsec (>=0.6.1)"] name = "zope-event" version = "5.0" description = "Very basic event publishing system" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4366,7 +4194,6 @@ test = ["zope.testrunner"] name = "zope-interface" version = "6.1" description = "Interfaces for Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4419,4 +4246,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "~3.10.9" -content-hash = "dd1297fd4e697861c78516a245501092c596648e49449bfb4283e21ccd219cdc" +content-hash = "96a15f8c5b9c35b9e581546744ec06b0f17dcf709d49db7f424a585243af67c8" diff --git a/pyproject.toml b/pyproject.toml index 42bf05899a..0df484ce40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ Werkzeug = "2.3.7" MarkupSafe = "2.1.4" # REVIEW: v2 is using sha512 instead of sha1 by default (in v1) itsdangerous = "2.1.2" -notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", rev = "52.1.0" } +notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", rev = "2da74685e0ffb220f0403e1f2584e783be99bbad" } # rsa = "4.9 # awscli 1.22.38 depends on rsa<4.8 typing-extensions = "4.7.1" greenlet = "2.0.2" From 3abbf76401cb5cf63b15307116795c5427b36ad9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 15:42:36 +0000 Subject: [PATCH 22/33] chore(deps): update all non-major github action dependencies (#2105) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_and_push_performance_test.yml | 2 +- .github/workflows/codeql.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_and_push_performance_test.yml b/.github/workflows/build_and_push_performance_test.yml index 9afa851ff6..326cec3389 100644 --- a/.github/workflows/build_and_push_performance_test.yml +++ b/.github/workflows/build_and_push_performance_test.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 + - uses: dorny/paths-filter@7267a8516b6f92bdb098633497bad573efdbf271 # v2.12.0 id: filter with: filters: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 71ca53041f..7976f87fde 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,15 +27,15 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Initialize CodeQL - uses: github/codeql-action/init@4759df8df70c5ebe7042c3029bbace20eee13edd # v2.23.1 + uses: github/codeql-action/init@2f93e4319b2f04a2efc38fa7f78bd681bc3f7b2f # v2.23.2 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@4759df8df70c5ebe7042c3029bbace20eee13edd # v2.23.1 + uses: github/codeql-action/autobuild@2f93e4319b2f04a2efc38fa7f78bd681bc3f7b2f # v2.23.2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4759df8df70c5ebe7042c3029bbace20eee13edd # v2.23.1 + uses: github/codeql-action/analyze@2f93e4319b2f04a2efc38fa7f78bd681bc3f7b2f # v2.23.2 with: category: "/language:${{ matrix.language }}" From fe6a86ec836fe2cf02d5baef5577ae31cc514e80 Mon Sep 17 00:00:00 2001 From: William B <7444334+whabanks@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:57:16 -0500 Subject: [PATCH 23/33] Remove FF_EMAIL_DAILY_LIMIT feature flag (#2096) * Remove FF_EMAIL_DAILY_LIMIT feature flag * Various test fixes * Fix tests, squashed a bug --------- Co-authored-by: Jumana B --- app/celery/tasks.py | 5 ----- app/config.py | 4 ---- app/notifications/rest.py | 8 +++++++- app/notifications/validators.py | 5 ----- app/service/rest.py | 4 +--- app/service/send_notification.py | 8 ++------ tests/app/celery/test_tasks.py | 15 +++++---------- .../notifications/rest/test_send_notification.py | 15 ++++++++++----- tests/app/notifications/test_validators.py | 10 ++++------ tests/app/service/test_rest.py | 4 +--- .../app/service/test_send_one_off_notification.py | 15 ++++++--------- 11 files changed, 36 insertions(+), 57 deletions(-) diff --git a/app/celery/tasks.py b/app/celery/tasks.py index 9dfc90ce54..df10c1db29 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -78,7 +78,6 @@ persist_notifications, send_notification_to_queue, ) -from app.notifications.validators import check_service_over_daily_message_limit from app.types import VerifiedNotification from app.utils import get_csv_max_rows, get_delivery_queue_for_template from app.v2.errors import ( @@ -300,8 +299,6 @@ def save_smss(self, service_id: Optional[str], signed_notifications: List[Signed current_app.logger.debug(f"Sending following sms notifications to AWS: {notification_id_queue.keys()}") for notification_obj in saved_notifications: try: - if not current_app.config["FF_EMAIL_DAILY_LIMIT"]: - check_service_over_daily_message_limit(notification_obj.key_type, service) queue = notification_id_queue.get(notification_obj.id) or get_delivery_queue_for_template(template) send_notification_to_queue( notification_obj, @@ -423,8 +420,6 @@ def try_to_send_notifications_to_queue(notification_id_queue, service, saved_not research_mode = service.research_mode # type: ignore for notification_obj in saved_notifications: try: - if not current_app.config["FF_EMAIL_DAILY_LIMIT"]: - check_service_over_daily_message_limit(notification_obj.key_type, service) queue = notification_id_queue.get(notification_obj.id) or get_delivery_queue_for_template(template) send_notification_to_queue( notification_obj, diff --git a/app/config.py b/app/config.py index 3dbecb98bc..d08a88b854 100644 --- a/app/config.py +++ b/app/config.py @@ -562,8 +562,6 @@ class Config(object): # Feature flag to enable custom retry policies such as lowering retry period for certain priority lanes. FF_CELERY_CUSTOM_TASK_PARAMS = env.bool("FF_CELERY_CUSTOM_TASK_PARAMS", True) FF_CLOUDWATCH_METRICS_ENABLED = env.bool("FF_CLOUDWATCH_METRICS_ENABLED", False) - # Feature flags for email_daily_limit - FF_EMAIL_DAILY_LIMIT = env.bool("FF_EMAIL_DAILY_LIMIT", False) FF_SALESFORCE_CONTACT = env.bool("FF_SALESFORCE_CONTACT", False) # SRE Tools auth keys @@ -671,8 +669,6 @@ class Test(Development): CRM_ORG_LIST_URL = "https://test-url.com" FAILED_LOGIN_LIMIT = 0 - FF_EMAIL_DAILY_LIMIT = False - class Production(Config): NOTIFY_EMAIL_DOMAIN = os.getenv("NOTIFY_EMAIL_DOMAIN", "notification.canada.ca") diff --git a/app/notifications/rest.py b/app/notifications/rest.py index 3a4f80c9d7..70b09228f8 100644 --- a/app/notifications/rest.py +++ b/app/notifications/rest.py @@ -10,6 +10,7 @@ EMAIL_TYPE, INTERNATIONAL_SMS_TYPE, KEY_TYPE_TEAM, + KEY_TYPE_TEST, LETTER_TYPE, SMS_TYPE, NotificationType, @@ -21,6 +22,7 @@ simulated_recipient, ) from app.notifications.validators import ( + check_email_daily_limit, check_rate_limiting, check_template_is_active, check_template_is_for_notification_type, @@ -102,12 +104,17 @@ def send_notification(notification_type: NotificationType): raise InvalidRequest(errors, status_code=400) current_app.logger.info(f"POST to V1 API: send_notification, service_id: {authenticated_service.id}") + check_rate_limiting(authenticated_service, api_user) template = templates_dao.dao_get_template_by_id_and_service_id( template_id=notification_form["template"], service_id=authenticated_service.id ) + simulated = simulated_recipient(notification_form["to"], notification_type) + if not simulated != api_user.key_type == KEY_TYPE_TEST: + check_email_daily_limit(authenticated_service, 1) + check_template_is_for_notification_type(notification_type, template.template_type) check_template_is_active(template) @@ -124,7 +131,6 @@ def send_notification(notification_type: NotificationType): _service_can_send_internationally(authenticated_service, notification_form["to"]) # Do not persist or send notification to the queue if it is a simulated recipient - simulated = simulated_recipient(notification_form["to"], notification_type) notification_model = persist_notification( template_id=template.id, template_version=template.version, diff --git a/app/notifications/validators.py b/app/notifications/validators.py index 3aee7486d6..a144c22229 100644 --- a/app/notifications/validators.py +++ b/app/notifications/validators.py @@ -227,17 +227,12 @@ def increment_sms_daily_count_send_warnings_if_needed(service: Service, requeste def increment_email_daily_count_send_warnings_if_needed(service: Service, requested_email=0) -> None: - if not current_app.config["FF_EMAIL_DAILY_LIMIT"]: - return - increment_todays_email_count(service.id, requested_email) send_warning_email_limit_emails_if_needed(service) def check_rate_limiting(service: Service, api_key: ApiKey): check_service_over_api_rate_limit_and_update_rate(service, api_key) - if not current_app.config["FF_EMAIL_DAILY_LIMIT"]: - check_service_over_daily_message_limit(api_key.key_type, service) def warn_about_daily_message_limit(service: Service, messages_sent): diff --git a/app/service/rest.py b/app/service/rest.py index d3c790d841..8c198b0151 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -342,9 +342,7 @@ def update_service(service_id): def _warn_service_users_about_message_limit_changed(service_id, data): send_notification_to_service_users( service_id=service_id, - template_id=current_app.config["DAILY_EMAIL_LIMIT_UPDATED_TEMPLATE_ID"] - if current_app.config["FF_EMAIL_DAILY_LIMIT"] - else current_app.config["DAILY_LIMIT_UPDATED_TEMPLATE_ID"], + template_id=current_app.config["DAILY_EMAIL_LIMIT_UPDATED_TEMPLATE_ID"], personalisation={ "service_name": data["name"], "message_limit_en": "{:,}".format(data["message_limit"]), diff --git a/app/service/send_notification.py b/app/service/send_notification.py index 813ce2f138..77cd1313e3 100644 --- a/app/service/send_notification.py +++ b/app/service/send_notification.py @@ -67,13 +67,11 @@ def send_one_off_notification(service_id, post_data): _, template_with_content = validate_template(template.id, personalisation, service, template.template_type) - if not current_app.config["FF_EMAIL_DAILY_LIMIT"]: - check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service) if template.template_type == SMS_TYPE: is_test_notification = simulated_recipient(post_data["to"], template.template_type) if not is_test_notification: check_sms_daily_limit(service, 1) - elif template.template_type == EMAIL_TYPE and current_app.config["FF_EMAIL_DAILY_LIMIT"]: + elif template.template_type == EMAIL_TYPE: check_email_daily_limit(service, 1) # 1 email validate_and_format_recipient( @@ -86,13 +84,11 @@ def send_one_off_notification(service_id, post_data): validate_created_by(service, post_data["created_by"]) - if not current_app.config["FF_EMAIL_DAILY_LIMIT"]: - pass # will remove this soon, don't bother refactoring if template.template_type == SMS_TYPE: is_test_notification = simulated_recipient(post_data["to"], template.template_type) if not is_test_notification: increment_sms_daily_count_send_warnings_if_needed(service, 1) - elif template.template_type == EMAIL_TYPE and current_app.config["FF_EMAIL_DAILY_LIMIT"]: + elif template.template_type == EMAIL_TYPE: increment_email_daily_count_send_warnings_if_needed(service, 1) # 1 email sender_id = post_data.get("sender_id", None) diff --git a/tests/app/celery/test_tasks.py b/tests/app/celery/test_tasks.py index 1b4f5b4e31..437d59e7ec 100644 --- a/tests/app/celery/test_tasks.py +++ b/tests/app/celery/test_tasks.py @@ -1328,12 +1328,11 @@ def test_save_sms_should_save_default_smm_sender_notification_reply_to_text_on(s def test_should_save_sms_template_to_and_persist_with_job_id(self, notify_api, sample_job, mocker): notification = _notification_json(sample_job.template, to="+1 650 253 2222", job_id=sample_job.id, row_number=2) mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") - mock_over_daily_limit = mocker.patch("app.celery.tasks.check_service_over_daily_message_limit") notification_id = uuid.uuid4() now = datetime.utcnow() - with set_config_values(notify_api, {"FF_EMAIL_DAILY_LIMIT": False}): - save_smss(sample_job.template.service_id, [signer_notification.sign(notification)], notification_id) + + save_smss(sample_job.template.service_id, [signer_notification.sign(notification)], notification_id) persisted_notification = Notification.query.one() assert persisted_notification.to == "+1 650 253 2222" assert persisted_notification.job_id == sample_job.id @@ -1350,7 +1349,6 @@ def test_should_save_sms_template_to_and_persist_with_job_id(self, notify_api, s provider_tasks.deliver_sms.apply_async.assert_called_once_with( [str(persisted_notification.id)], queue=QueueNames.SEND_SMS_MEDIUM ) - mock_over_daily_limit.assert_called_once_with("normal", sample_job.service) def test_save_sms_should_go_to_retry_queue_if_database_errors(self, sample_template, mocker): notification = _notification_json(sample_template, "+1 650 253 2222") @@ -1672,7 +1670,6 @@ def test_should_use_email_template_and_persist( self, notify_api, sample_email_template_with_placeholders, sample_api_key, mocker ): mocker.patch("app.celery.provider_tasks.deliver_email.apply_async") - mock_over_daily_limit = mocker.patch("app.celery.tasks.check_service_over_daily_message_limit") now = datetime(2016, 1, 1, 11, 9, 0) notification_id = uuid.uuid4() @@ -1686,10 +1683,9 @@ def test_should_use_email_template_and_persist( ) with freeze_time("2016-01-01 11:10:00.00000"): - with set_config_values(notify_api, {"FF_EMAIL_DAILY_LIMIT": False}): - save_emails( - sample_email_template_with_placeholders.service_id, [signer_notification.sign(notification)], notification_id - ) + save_emails( + sample_email_template_with_placeholders.service_id, [signer_notification.sign(notification)], notification_id + ) persisted_notification = Notification.query.one() assert persisted_notification.to == "my_email@my_email.com" @@ -1709,7 +1705,6 @@ def test_should_use_email_template_and_persist( provider_tasks.deliver_email.apply_async.assert_called_once_with( [str(persisted_notification.id)], queue=QueueNames.SEND_EMAIL_MEDIUM ) - mock_over_daily_limit.assert_called_once_with("normal", sample_email_template_with_placeholders.service) def test_save_email_should_use_template_version_from_job_not_latest(self, sample_email_template, mocker): notification = _notification_json(sample_email_template, "my_email@my_email.com") diff --git a/tests/app/notifications/rest/test_send_notification.py b/tests/app/notifications/rest/test_send_notification.py index b1f797f25a..71f8c63ec6 100644 --- a/tests/app/notifications/rest/test_send_notification.py +++ b/tests/app/notifications/rest/test_send_notification.py @@ -28,7 +28,11 @@ Template, ) from app.utils import get_document_url -from app.v2.errors import RateLimitError, TooManyRequestsError +from app.v2.errors import ( + RateLimitError, + TooManyRequestsError, + TrialServiceTooManyEmailRequestsError, +) from tests import create_authorization_header from tests.app.conftest import ( create_sample_api_key, @@ -385,7 +389,7 @@ def test_should_block_api_call_if_over_day_limit_for_live_service(notify_db, not with notify_api.test_request_context(): with notify_api.test_client() as client: mocker.patch( - "app.notifications.validators.check_service_over_daily_message_limit", + "app.notifications.validators.check_service_over_api_rate_limit_and_update_rate", side_effect=TooManyRequestsError(1), ) mocker.patch("app.celery.provider_tasks.deliver_email.apply_async") @@ -417,13 +421,14 @@ def test_should_block_api_call_if_over_day_limit_for_live_service(notify_db, not def test_should_block_api_call_if_over_day_limit_for_restricted_service(notify_db, notify_db_session, notify_api, mocker): with notify_api.test_request_context(): with notify_api.test_client() as client: - mocker.patch("app.celery.provider_tasks.deliver_sms.apply_async") + mocker.patch("app.celery.provider_tasks.deliver_email.apply_async") mocker.patch( - "app.notifications.validators.check_service_over_daily_message_limit", - side_effect=TooManyRequestsError(1), + "app.notifications.validators.check_email_daily_limit", + side_effect=TrialServiceTooManyEmailRequestsError(1), ) service = create_sample_service(notify_db, notify_db_session, limit=1, restricted=True) + create_sample_service_safelist(notify_db, notify_db_session, service=service, email_address="ok@ok.com") email_template = create_sample_email_template(notify_db, notify_db_session, service=service) create_sample_notification( notify_db, diff --git a/tests/app/notifications/test_validators.py b/tests/app/notifications/test_validators.py index 24175b431e..f49c4dc6e0 100644 --- a/tests/app/notifications/test_validators.py +++ b/tests/app/notifications/test_validators.py @@ -217,8 +217,7 @@ def test_check_service_message_limit_records_nearing_daily_limit( if limit_type == "sms": increment_sms_daily_count_send_warnings_if_needed(service) else: - with set_config(notify_api, "FF_EMAIL_DAILY_LIMIT", True): - increment_email_daily_count_send_warnings_if_needed(service) + increment_email_daily_count_send_warnings_if_needed(service) assert redis_get.call_args_list == [ call(count_key(limit_type, service.id)), @@ -290,8 +289,7 @@ def test_check_service_message_limit_in_cache_over_message_limit_fails( assert e.value.fields == [] with pytest.raises(TooManyEmailRequestsError) as e: - with set_config(notify_api, "FF_EMAIL_DAILY_LIMIT", True): - check_email_daily_limit(service) + check_email_daily_limit(service) assert e.value.status_code == 429 assert e.value.message == "Exceeded email daily sending limit of 4 messages" assert e.value.fields == [] @@ -350,8 +348,8 @@ def test_check_service_message_limit_skip_statsd_over_message_no_limit_fails_ema # When service = create_sample_service(notify_db, notify_db_session, restricted=True, limit=4, sms_limit=4) - with set_config(notify_api, "FF_EMAIL_DAILY_LIMIT", True): - check_email_daily_limit(service) + check_email_daily_limit(service) + # Then app_statsd.statsd_client.incr.assert_not_called() diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index d5f01b415d..e3e309cda5 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -2225,9 +2225,7 @@ def test_update_service_updating_daily_limit_sends_notification_to_users( if expected_call: send_notification_mock.assert_called_once_with( service_id=service.id, - template_id=current_app.config["DAILY_EMAIL_LIMIT_UPDATED_TEMPLATE_ID"] - if current_app.config["FF_EMAIL_DAILY_LIMIT"] - else current_app.config["DAILY_LIMIT_UPDATED_TEMPLATE_ID"], + template_id=current_app.config["DAILY_EMAIL_LIMIT_UPDATED_TEMPLATE_ID"], personalisation={ "service_name": service.name, "message_limit_en": "{:,}".format(new_limit), diff --git a/tests/app/service/test_send_one_off_notification.py b/tests/app/service/test_send_one_off_notification.py index 4b8de0890c..b9dc37cfd5 100644 --- a/tests/app/service/test_send_one_off_notification.py +++ b/tests/app/service/test_send_one_off_notification.py @@ -21,7 +21,6 @@ BadRequestError, LiveServiceTooManyEmailRequestsError, LiveServiceTooManySMSRequestsError, - TooManyRequestsError, ) from tests.app.db import ( create_letter_contact, @@ -31,7 +30,6 @@ create_template, create_user, ) -from tests.conftest import set_config_values @pytest.fixture @@ -282,12 +280,12 @@ def test_send_one_off_notification_raises_if_cant_send_to_recipient( assert "service is in trial mode" in e.value.message -def test_send_one_off_notification_raises_if_over_combined_limit(notify_db_session, mocker): +def test_send_one_off_notification_raises_if_over_combined_limit(notify_db_session, notify_api, mocker): service = create_service(message_limit=0) template = create_template(service=service) mocker.patch( - "app.service.send_notification.check_service_over_daily_message_limit", - side_effect=TooManyRequestsError(1), + "app.service.send_notification.check_sms_daily_limit", + side_effect=LiveServiceTooManySMSRequestsError(1), ) post_data = { @@ -296,7 +294,7 @@ def test_send_one_off_notification_raises_if_over_combined_limit(notify_db_sessi "created_by": str(service.created_by_id), } - with pytest.raises(TooManyRequestsError): + with pytest.raises(LiveServiceTooManySMSRequestsError): send_one_off_notification(service.id, post_data) @@ -314,9 +312,8 @@ def test_send_one_off_notification_raises_if_over_email_limit(notify_db_session, "created_by": str(service.created_by_id), } - with set_config_values(notify_api, {"FF_EMAIL_DAILY_LIMIT": True}): - with pytest.raises(LiveServiceTooManyEmailRequestsError): - send_one_off_notification(service.id, post_data) + with pytest.raises(LiveServiceTooManyEmailRequestsError): + send_one_off_notification(service.id, post_data) def test_send_one_off_notification_raises_if_over_sms_daily_limit(notify_db_session, mocker): From 0d5cce49cc8c3ff3f2b35ec5bef417bac75fa1ae Mon Sep 17 00:00:00 2001 From: Jumana B Date: Tue, 6 Feb 2024 12:05:29 -0500 Subject: [PATCH 24/33] Task/add index 2 (#2106) * Add index concurrently for n_hist * fix --------- Co-authored-by: William B <7444334+whabanks@users.noreply.github.com> --- .../versions/0444_add_index_n_history2.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 migrations/versions/0444_add_index_n_history2.py diff --git a/migrations/versions/0444_add_index_n_history2.py b/migrations/versions/0444_add_index_n_history2.py new file mode 100644 index 0000000000..2b3604d984 --- /dev/null +++ b/migrations/versions/0444_add_index_n_history2.py @@ -0,0 +1,40 @@ +""" + +Revision ID: 0439_add_index_n_history +Revises: 0438_sms_templates_msgs_left +Create Date: 2023-10-05 00:00:00 + +""" +from datetime import datetime + +from alembic import op + +revision = "0444_add_index_n_history2" +down_revision = "0443_add_apikey_last_used_column" + + +def index_exists(name): + connection = op.get_bind() + result = connection.execute( + "SELECT exists(SELECT 1 from pg_indexes where indexname = '{}') as ix_exists;".format(name) + ).first() + return result.ix_exists + + +# option 1 +def upgrade(): + op.execute("COMMIT") + if not index_exists("ix_notification_history_api_key_id_created"): + op.create_index( + op.f("ix_notification_history_api_key_id_created"), + "notification_history", + ["api_key_id", "created_at"], + postgresql_concurrently=True, + ) + + +def downgrade(): + op.execute("COMMIT") + op.drop_index( + op.f("ix_notification_history_api_key_id_created"), table_name="notification_history", postgresql_concurrently=True + ) From 5aeb39c9a411b81e80f6515f7337b8f59fdaa7bb Mon Sep 17 00:00:00 2001 From: Jumana B Date: Tue, 6 Feb 2024 15:42:19 -0500 Subject: [PATCH 25/33] Remove notification fallback (#2107) * Remove notification fallback * fix --- app/dao/fact_notification_status_dao.py | 9 +-------- tests/app/api_key/test_rest.py | 9 --------- tests/app/dao/test_fact_notification_status_dao.py | 12 ------------ 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/app/dao/fact_notification_status_dao.py b/app/dao/fact_notification_status_dao.py index 86c2404708..533ee274b3 100644 --- a/app/dao/fact_notification_status_dao.py +++ b/app/dao/fact_notification_status_dao.py @@ -356,14 +356,7 @@ def get_last_send_for_api_key(api_key_id): api_key_table = ( db.session.query(ApiKey.last_used_timestamp.label("last_notification_created")).filter(ApiKey.id == api_key_id).all() ) - if not api_key_table[0][0]: - notification_table = ( - db.session.query(func.max(Notification.created_at).label("last_notification_created")) - .filter(Notification.api_key_id == api_key_id) - .all() - ) - return [] if notification_table[0][0] is None else notification_table - return api_key_table + return [] if api_key_table[0][0] is None else api_key_table def get_api_key_ranked_by_notifications_created(n_days_back): diff --git a/tests/app/api_key/test_rest.py b/tests/app/api_key/test_rest.py index 0def4b7884..a28985884a 100644 --- a/tests/app/api_key/test_rest.py +++ b/tests/app/api_key/test_rest.py @@ -1,9 +1,6 @@ -from datetime import datetime - import pytest from flask import url_for -from app import DATETIME_FORMAT from app.dao.api_key_dao import get_api_key_by_secret, get_unsigned_secret from app.models import KEY_TYPE_NORMAL from tests import create_sre_authorization_header @@ -33,12 +30,6 @@ def test_get_api_key_stats_with_sends(admin_request, notify_db, notify_db_sessio assert api_key_stats["sms_sends"] == 0 assert api_key_stats["total_sends"] == total_sends - # the following lines test that a send has occurred within the last second - last_send_dt = datetime.strptime(api_key_stats["last_send"], DATETIME_FORMAT) - now = datetime.utcnow() - time_delta = now - last_send_dt - assert abs(time_delta.total_seconds()) < 1 - def test_get_api_key_stats_no_sends(admin_request, notify_db, notify_db_session): service = create_service(service_name="Service 2") diff --git a/tests/app/dao/test_fact_notification_status_dao.py b/tests/app/dao/test_fact_notification_status_dao.py index 85cd078d91..e19d5adbae 100644 --- a/tests/app/dao/test_fact_notification_status_dao.py +++ b/tests/app/dao/test_fact_notification_status_dao.py @@ -367,21 +367,9 @@ def test_get_last_send_for_api_key_check_last_used(notify_db_session): def test_get_last_send_for_api_key(notify_db_session): service = create_service(service_name="First Service") api_key = create_api_key(service) - template_email = create_template(service=service, template_type=EMAIL_TYPE) - total_sends = 10 - last_send = get_last_send_for_api_key(str(api_key.id)) assert last_send == [] - for x in range(total_sends): - save_notification(create_notification(template=template_email, api_key=api_key)) - - # the following lines test that a send has occurred within the last second - last_send = get_last_send_for_api_key(str(api_key.id))[0][0] - now = datetime.utcnow() - time_delta = now - last_send - assert abs(time_delta.total_seconds()) < 1 - def test_get_api_key_ranked_by_notifications_created(notify_db_session): service = create_service(service_name="Service 1") From 2e26af0c09ae8ebfefc65da69760191ef4d2acac Mon Sep 17 00:00:00 2001 From: "sre-read-write[bot]" <92993749+sre-read-write[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:44:09 -0500 Subject: [PATCH 26/33] chore: synced local '.github/workflows/ossf-scorecard.yml' with remote 'tools/sre_file_sync/ossf-scorecard.yml' (#2111) Co-authored-by: sre-read-write[bot] <92993749+sre-read-write[bot]@users.noreply.github.com> --- .github/workflows/ossf-scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 69eba84060..f612283a7c 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -25,7 +25,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@155cf0ea68b491a7c47af606d2741b54963ecb04 + uses: ossf/scorecard-action@0ae0fb3a2ca18a43d6dea9c07cfb9bd01d17eae1 with: results_file: ossf-results.json results_format: json From 5bcdb72ee1371e815819221afce6976e77550101 Mon Sep 17 00:00:00 2001 From: Jumana B Date: Tue, 20 Feb 2024 09:33:19 -0500 Subject: [PATCH 27/33] Task: Filter Heartbeats (#2108) * Add filter for heartbeat template * fix formatting * Edit and add a test * test for rest endpoint * Add filter heartbeats for live service data --- app/dao/fact_notification_status_dao.py | 15 ++++- app/dao/services_dao.py | 13 ++++- app/service/rest.py | 6 +- .../dao/test_fact_notification_status_dao.py | 57 +++++++++++++++++++ tests/app/dao/test_services_dao.py | 12 +++- tests/app/service/test_rest.py | 15 +++++ 6 files changed, 108 insertions(+), 10 deletions(-) diff --git a/app/dao/fact_notification_status_dao.py b/app/dao/fact_notification_status_dao.py index 533ee274b3..bdee6ac67c 100644 --- a/app/dao/fact_notification_status_dao.py +++ b/app/dao/fact_notification_status_dao.py @@ -149,8 +149,8 @@ def fetch_notification_status_for_service_by_month(start_date, end_date, service ) -def fetch_delivered_notification_stats_by_month(): - return ( +def fetch_delivered_notification_stats_by_month(filter_heartbeats=None): + query = ( db.session.query( func.date_trunc("month", FactNotificationStatus.bst_date).cast(db.Text).label("month"), FactNotificationStatus.notification_type, @@ -169,8 +169,17 @@ def fetch_delivered_notification_stats_by_month(): func.date_trunc("month", FactNotificationStatus.bst_date).desc(), FactNotificationStatus.notification_type, ) - .all() ) + if filter_heartbeats: + query = query.filter( + FactNotificationStatus.template_id != current_app.config["HEARTBEAT_TEMPLATE_EMAIL_LOW"], + FactNotificationStatus.template_id != current_app.config["HEARTBEAT_TEMPLATE_EMAIL_MEDIUM"], + FactNotificationStatus.template_id != current_app.config["HEARTBEAT_TEMPLATE_EMAIL_HIGH"], + FactNotificationStatus.template_id != current_app.config["HEARTBEAT_TEMPLATE_SMS_LOW"], + FactNotificationStatus.template_id != current_app.config["HEARTBEAT_TEMPLATE_SMS_MEDIUM"], + FactNotificationStatus.template_id != current_app.config["HEARTBEAT_TEMPLATE_SMS_HIGH"], + ) + return query.all() def fetch_notification_stats_for_trial_services(): diff --git a/app/dao/services_dao.py b/app/dao/services_dao.py index 80d1f25c35..6055e9ff9e 100644 --- a/app/dao/services_dao.py +++ b/app/dao/services_dao.py @@ -83,7 +83,7 @@ def dao_count_live_services(): ).count() -def dao_fetch_live_services_data(): +def dao_fetch_live_services_data(filter_heartbeats=None): year_start_date, year_end_date = get_current_financial_year() most_recent_annual_billing = ( @@ -175,8 +175,17 @@ def dao_fetch_live_services_data(): AnnualBilling.free_sms_fragment_limit, ) .order_by(asc(Service.go_live_at)) - .all() ) + if filter_heartbeats: + data = data.join(Template, Service.id == Template.service_id).filter( + Template.id != current_app.config["HEARTBEAT_TEMPLATE_EMAIL_LOW"], + Template.id != current_app.config["HEARTBEAT_TEMPLATE_EMAIL_MEDIUM"], + Template.id != current_app.config["HEARTBEAT_TEMPLATE_EMAIL_HIGH"], + Template.id != current_app.config["HEARTBEAT_TEMPLATE_SMS_LOW"], + Template.id != current_app.config["HEARTBEAT_TEMPLATE_SMS_MEDIUM"], + Template.id != current_app.config["HEARTBEAT_TEMPLATE_SMS_HIGH"], + ) + data = data.all() results = [] for row in data: existing_service = next((x for x in results if x["service_id"] == row.service_id), None) diff --git a/app/service/rest.py b/app/service/rest.py index 8c198b0151..8ecf13f47d 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -209,13 +209,15 @@ def find_services_by_name(): @service_blueprint.route("/live-services-data", methods=["GET"]) def get_live_services_data(): - data = dao_fetch_live_services_data() + filter_heartbeats = request.args.get("filter_heartbeats", None) == "True" + data = dao_fetch_live_services_data(filter_heartbeats=filter_heartbeats) return jsonify(data=data) @service_blueprint.route("/delivered-notifications-stats-by-month-data", methods=["GET"]) def get_delivered_notification_stats_by_month_data(): - return jsonify(data=fetch_delivered_notification_stats_by_month()) + filter_heartbeats = request.args.get("filter_heartbeats", None) == "True" + return jsonify(data=fetch_delivered_notification_stats_by_month(filter_heartbeats=filter_heartbeats)) @service_blueprint.route("/", methods=["GET"]) diff --git a/tests/app/dao/test_fact_notification_status_dao.py b/tests/app/dao/test_fact_notification_status_dao.py index e19d5adbae..ce26830584 100644 --- a/tests/app/dao/test_fact_notification_status_dao.py +++ b/tests/app/dao/test_fact_notification_status_dao.py @@ -51,6 +51,7 @@ create_template, save_notification, ) +from tests.conftest import set_config def test_update_fact_notification_status(notify_db_session): @@ -728,6 +729,62 @@ def test_fetch_delivered_notification_stats_by_month(sample_service): assert results[3].count == 6 +@freeze_time("2020-11-02 14:00") +def test_fetch_delivered_notification_stats_by_month_filter_heartbeats(notify_api, sample_service): + sms_template = create_template(service=sample_service, template_type="sms", template_name="a") + email_template = create_template(service=sample_service, template_type="email", template_name="b") + + # Not counted: before GC Notify started + create_ft_notification_status( + utc_date=date(2019, 10, 10), + service=sample_service, + template=email_template, + count=3, + ) + + create_ft_notification_status( + utc_date=date(2019, 12, 10), + service=sample_service, + template=email_template, + count=3, + ) + + create_ft_notification_status( + utc_date=date(2019, 12, 5), + service=sample_service, + template=sms_template, + notification_status=NOTIFICATION_DELIVERED, + count=6, + ) + + create_ft_notification_status( + utc_date=date(2020, 1, 1), + service=sample_service, + template=sms_template, + notification_status=NOTIFICATION_SENT, + count=4, + ) + + # Not counted: failed notifications + create_ft_notification_status( + utc_date=date(2020, 1, 1), + service=sample_service, + template=sms_template, + notification_status=NOTIFICATION_FAILED, + count=10, + ) + + create_ft_notification_status( + utc_date=date(2020, 3, 1), + service=sample_service, + template=email_template, + count=5, + ) + with set_config(notify_api, "HEARTBEAT_TEMPLATE_EMAIL_LOW", email_template.id): + results = fetch_delivered_notification_stats_by_month(filter_heartbeats=True) + assert len(results) == 2 + + def test_fetch_delivered_notification_stats_by_month_empty(): assert fetch_delivered_notification_stats_by_month() == [] diff --git a/tests/app/dao/test_services_dao.py b/tests/app/dao/test_services_dao.py index 4a81ea4f86..cb5caa8c3b 100644 --- a/tests/app/dao/test_services_dao.py +++ b/tests/app/dao/test_services_dao.py @@ -87,6 +87,7 @@ create_user, save_notification, ) +from tests.conftest import set_config # from unittest import mock @@ -493,7 +494,8 @@ def test_get_all_user_services_should_return_empty_list_if_no_services_for_user( @freeze_time("2019-04-23T10:00:00") -def test_dao_fetch_live_services_data(sample_user): +@pytest.mark.parametrize("filter_heartbeats", [True, False]) +def test_dao_fetch_live_services_data_filter_heartbeats(notify_api, sample_user, filter_heartbeats): org = create_organisation(organisation_type="nhs_central") service = create_service(go_live_user=sample_user, go_live_at="2014-04-20T10:00:00") template = create_template(service=service) @@ -561,8 +563,12 @@ def test_dao_fetch_live_services_data(sample_user): # 3rd service: billing from 2019 create_annual_billing(service_3.id, 200, 2019) - results = dao_fetch_live_services_data() - assert len(results) == 3 + with set_config(notify_api, "HEARTBEAT_TEMPLATE_EMAIL_LOW", template.id): + results = dao_fetch_live_services_data(filter_heartbeats=filter_heartbeats) + if not filter_heartbeats: + assert len(results) == 3 + else: + assert len(results) == 2 # checks the results and that they are ordered by date: # @todo: this test is temporarily forced to pass until we can add the fiscal year back into # the query and create a new endpoint for the homepage stats diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index e3e309cda5..c15b2dec6b 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -70,6 +70,7 @@ create_user, save_notification, ) +from tests.conftest import set_config def test_get_service_list(client, service_factory): @@ -253,6 +254,20 @@ def test_get_delivered_notification_stats_by_month_data(admin_request, sample_se assert first["count"] == 3 +def test_get_delivered_notification_stats_by_month_data_without_heartbeat(notify_api, admin_request, sample_service): + email_template = create_template(service=sample_service, template_type="email", template_name="b") + + create_ft_notification_status( + utc_date=date(2019, 12, 10), + service=sample_service, + template=email_template, + count=3, + ) + with set_config(notify_api, "HEARTBEAT_TEMPLATE_EMAIL_LOW", email_template.id): + response = admin_request.get("service.get_delivered_notification_stats_by_month_data", filter_heartbeats=True)["data"] + assert len(response) == 0 + + def test_get_service_by_id(admin_request, sample_service): json_resp = admin_request.get("service.get_service_by_id", service_id=sample_service.id) assert json_resp["data"]["name"] == sample_service.name From 750c21a4174d3349b414395aaa4fbfda7db93103 Mon Sep 17 00:00:00 2001 From: William B <7444334+whabanks@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:45:23 -0400 Subject: [PATCH 28/33] Revert "Task: Filter Heartbeats (#2108)" (#2117) This reverts commit 5bcdb72ee1371e815819221afce6976e77550101. --- app/dao/fact_notification_status_dao.py | 15 +---- app/dao/services_dao.py | 13 +---- app/service/rest.py | 6 +- .../dao/test_fact_notification_status_dao.py | 57 ------------------- tests/app/dao/test_services_dao.py | 12 +--- tests/app/service/test_rest.py | 15 ----- 6 files changed, 10 insertions(+), 108 deletions(-) diff --git a/app/dao/fact_notification_status_dao.py b/app/dao/fact_notification_status_dao.py index bdee6ac67c..533ee274b3 100644 --- a/app/dao/fact_notification_status_dao.py +++ b/app/dao/fact_notification_status_dao.py @@ -149,8 +149,8 @@ def fetch_notification_status_for_service_by_month(start_date, end_date, service ) -def fetch_delivered_notification_stats_by_month(filter_heartbeats=None): - query = ( +def fetch_delivered_notification_stats_by_month(): + return ( db.session.query( func.date_trunc("month", FactNotificationStatus.bst_date).cast(db.Text).label("month"), FactNotificationStatus.notification_type, @@ -169,17 +169,8 @@ def fetch_delivered_notification_stats_by_month(filter_heartbeats=None): func.date_trunc("month", FactNotificationStatus.bst_date).desc(), FactNotificationStatus.notification_type, ) + .all() ) - if filter_heartbeats: - query = query.filter( - FactNotificationStatus.template_id != current_app.config["HEARTBEAT_TEMPLATE_EMAIL_LOW"], - FactNotificationStatus.template_id != current_app.config["HEARTBEAT_TEMPLATE_EMAIL_MEDIUM"], - FactNotificationStatus.template_id != current_app.config["HEARTBEAT_TEMPLATE_EMAIL_HIGH"], - FactNotificationStatus.template_id != current_app.config["HEARTBEAT_TEMPLATE_SMS_LOW"], - FactNotificationStatus.template_id != current_app.config["HEARTBEAT_TEMPLATE_SMS_MEDIUM"], - FactNotificationStatus.template_id != current_app.config["HEARTBEAT_TEMPLATE_SMS_HIGH"], - ) - return query.all() def fetch_notification_stats_for_trial_services(): diff --git a/app/dao/services_dao.py b/app/dao/services_dao.py index 6055e9ff9e..80d1f25c35 100644 --- a/app/dao/services_dao.py +++ b/app/dao/services_dao.py @@ -83,7 +83,7 @@ def dao_count_live_services(): ).count() -def dao_fetch_live_services_data(filter_heartbeats=None): +def dao_fetch_live_services_data(): year_start_date, year_end_date = get_current_financial_year() most_recent_annual_billing = ( @@ -175,17 +175,8 @@ def dao_fetch_live_services_data(filter_heartbeats=None): AnnualBilling.free_sms_fragment_limit, ) .order_by(asc(Service.go_live_at)) + .all() ) - if filter_heartbeats: - data = data.join(Template, Service.id == Template.service_id).filter( - Template.id != current_app.config["HEARTBEAT_TEMPLATE_EMAIL_LOW"], - Template.id != current_app.config["HEARTBEAT_TEMPLATE_EMAIL_MEDIUM"], - Template.id != current_app.config["HEARTBEAT_TEMPLATE_EMAIL_HIGH"], - Template.id != current_app.config["HEARTBEAT_TEMPLATE_SMS_LOW"], - Template.id != current_app.config["HEARTBEAT_TEMPLATE_SMS_MEDIUM"], - Template.id != current_app.config["HEARTBEAT_TEMPLATE_SMS_HIGH"], - ) - data = data.all() results = [] for row in data: existing_service = next((x for x in results if x["service_id"] == row.service_id), None) diff --git a/app/service/rest.py b/app/service/rest.py index 8ecf13f47d..8c198b0151 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -209,15 +209,13 @@ def find_services_by_name(): @service_blueprint.route("/live-services-data", methods=["GET"]) def get_live_services_data(): - filter_heartbeats = request.args.get("filter_heartbeats", None) == "True" - data = dao_fetch_live_services_data(filter_heartbeats=filter_heartbeats) + data = dao_fetch_live_services_data() return jsonify(data=data) @service_blueprint.route("/delivered-notifications-stats-by-month-data", methods=["GET"]) def get_delivered_notification_stats_by_month_data(): - filter_heartbeats = request.args.get("filter_heartbeats", None) == "True" - return jsonify(data=fetch_delivered_notification_stats_by_month(filter_heartbeats=filter_heartbeats)) + return jsonify(data=fetch_delivered_notification_stats_by_month()) @service_blueprint.route("/", methods=["GET"]) diff --git a/tests/app/dao/test_fact_notification_status_dao.py b/tests/app/dao/test_fact_notification_status_dao.py index ce26830584..e19d5adbae 100644 --- a/tests/app/dao/test_fact_notification_status_dao.py +++ b/tests/app/dao/test_fact_notification_status_dao.py @@ -51,7 +51,6 @@ create_template, save_notification, ) -from tests.conftest import set_config def test_update_fact_notification_status(notify_db_session): @@ -729,62 +728,6 @@ def test_fetch_delivered_notification_stats_by_month(sample_service): assert results[3].count == 6 -@freeze_time("2020-11-02 14:00") -def test_fetch_delivered_notification_stats_by_month_filter_heartbeats(notify_api, sample_service): - sms_template = create_template(service=sample_service, template_type="sms", template_name="a") - email_template = create_template(service=sample_service, template_type="email", template_name="b") - - # Not counted: before GC Notify started - create_ft_notification_status( - utc_date=date(2019, 10, 10), - service=sample_service, - template=email_template, - count=3, - ) - - create_ft_notification_status( - utc_date=date(2019, 12, 10), - service=sample_service, - template=email_template, - count=3, - ) - - create_ft_notification_status( - utc_date=date(2019, 12, 5), - service=sample_service, - template=sms_template, - notification_status=NOTIFICATION_DELIVERED, - count=6, - ) - - create_ft_notification_status( - utc_date=date(2020, 1, 1), - service=sample_service, - template=sms_template, - notification_status=NOTIFICATION_SENT, - count=4, - ) - - # Not counted: failed notifications - create_ft_notification_status( - utc_date=date(2020, 1, 1), - service=sample_service, - template=sms_template, - notification_status=NOTIFICATION_FAILED, - count=10, - ) - - create_ft_notification_status( - utc_date=date(2020, 3, 1), - service=sample_service, - template=email_template, - count=5, - ) - with set_config(notify_api, "HEARTBEAT_TEMPLATE_EMAIL_LOW", email_template.id): - results = fetch_delivered_notification_stats_by_month(filter_heartbeats=True) - assert len(results) == 2 - - def test_fetch_delivered_notification_stats_by_month_empty(): assert fetch_delivered_notification_stats_by_month() == [] diff --git a/tests/app/dao/test_services_dao.py b/tests/app/dao/test_services_dao.py index cb5caa8c3b..4a81ea4f86 100644 --- a/tests/app/dao/test_services_dao.py +++ b/tests/app/dao/test_services_dao.py @@ -87,7 +87,6 @@ create_user, save_notification, ) -from tests.conftest import set_config # from unittest import mock @@ -494,8 +493,7 @@ def test_get_all_user_services_should_return_empty_list_if_no_services_for_user( @freeze_time("2019-04-23T10:00:00") -@pytest.mark.parametrize("filter_heartbeats", [True, False]) -def test_dao_fetch_live_services_data_filter_heartbeats(notify_api, sample_user, filter_heartbeats): +def test_dao_fetch_live_services_data(sample_user): org = create_organisation(organisation_type="nhs_central") service = create_service(go_live_user=sample_user, go_live_at="2014-04-20T10:00:00") template = create_template(service=service) @@ -563,12 +561,8 @@ def test_dao_fetch_live_services_data_filter_heartbeats(notify_api, sample_user, # 3rd service: billing from 2019 create_annual_billing(service_3.id, 200, 2019) - with set_config(notify_api, "HEARTBEAT_TEMPLATE_EMAIL_LOW", template.id): - results = dao_fetch_live_services_data(filter_heartbeats=filter_heartbeats) - if not filter_heartbeats: - assert len(results) == 3 - else: - assert len(results) == 2 + results = dao_fetch_live_services_data() + assert len(results) == 3 # checks the results and that they are ordered by date: # @todo: this test is temporarily forced to pass until we can add the fiscal year back into # the query and create a new endpoint for the homepage stats diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index c15b2dec6b..e3e309cda5 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -70,7 +70,6 @@ create_user, save_notification, ) -from tests.conftest import set_config def test_get_service_list(client, service_factory): @@ -254,20 +253,6 @@ def test_get_delivered_notification_stats_by_month_data(admin_request, sample_se assert first["count"] == 3 -def test_get_delivered_notification_stats_by_month_data_without_heartbeat(notify_api, admin_request, sample_service): - email_template = create_template(service=sample_service, template_type="email", template_name="b") - - create_ft_notification_status( - utc_date=date(2019, 12, 10), - service=sample_service, - template=email_template, - count=3, - ) - with set_config(notify_api, "HEARTBEAT_TEMPLATE_EMAIL_LOW", email_template.id): - response = admin_request.get("service.get_delivered_notification_stats_by_month_data", filter_heartbeats=True)["data"] - assert len(response) == 0 - - def test_get_service_by_id(admin_request, sample_service): json_resp = admin_request.get("service.get_service_by_id", service_id=sample_service.id) assert json_resp["data"]["name"] == sample_service.name From 098df03aeae7335115db88e9941103c6622c7030 Mon Sep 17 00:00:00 2001 From: Steve Astels Date: Thu, 22 Feb 2024 10:06:36 -0500 Subject: [PATCH 29/33] upgrade poetry and cryptography (#2115) Co-authored-by: William B <7444334+whabanks@users.noreply.github.com> --- .devcontainer/Dockerfile | 2 +- .github/workflows/test.yaml | 4 +- Makefile | 2 +- ci/Dockerfile | 3 +- ci/Dockerfile.lambda | 2 +- ci/Dockerfile.test | 4 +- local/Dockerfile | 2 +- poetry.lock | 254 +++++++++++++++++++++++++++++++----- pyproject.toml | 3 +- tests-perf/ops/Dockerfile | 4 +- 10 files changed, 230 insertions(+), 50 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index c6a2f6b839..49963dcbfa 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -2,7 +2,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.10@sha256:ef9cc483a593c95 ARG KUBENS_VERSION="0.9.4" ARG OCTANT_VERSION="0.25.1" -ENV POETRY_VERSION="1.3.2" +ENV POETRY_VERSION="1.7.1" # Install packages RUN apt-get update \ diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e74ffd0f67..3169d77d64 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -33,10 +33,10 @@ jobs: ${{ runner.os }}-pip- - name: Install poetry env: - POETRY_VERSION: "1.3.2" + POETRY_VERSION: "1.7.1" run: pip install poetry==${POETRY_VERSION} && poetry --version - name: Check poetry.lock aligns with pyproject.toml - run: poetry lock --check + run: poetry check --lock - name: Install requirements run: poetry install --with test - name: Run tests diff --git a/Makefile b/Makefile index 0b1064d543..3f98e79566 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ freeze-requirements: .PHONY: test-requirements test-requirements: - poetry lock --check + poetry check --lock .PHONY: coverage coverage: venv ## Create coverage report diff --git a/ci/Dockerfile b/ci/Dockerfile index 3b6c11f11f..144e6bb07f 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -1,10 +1,9 @@ FROM python:3.10-alpine3.16@sha256:afe68972cc00883d70b3760ee0ffbb7375cf09706c122dda7063ffe64c5be21b ENV PYTHONDONTWRITEBYTECODE 1 -ENV POETRY_VERSION "1.3.2" ENV APP_VENV="/app/.venv" ENV POETRY_HOME="/opt/poetry" -ENV POETRY_VERSION="1.3.2" +ENV POETRY_VERSION="1.7.1" ENV POETRY_VIRTUALENVS_CREATE="false" ENV PATH="${APP_VENV}/bin:${POETRY_HOME}/bin:$PATH" diff --git a/ci/Dockerfile.lambda b/ci/Dockerfile.lambda index bf18db168a..c827b08c24 100644 --- a/ci/Dockerfile.lambda +++ b/ci/Dockerfile.lambda @@ -5,7 +5,7 @@ ENV PYTHONDONTWRITEBYTECODE 1 ENV TASK_ROOT /app ENV APP_VENV="${TASK_ROOT}/.venv" ENV POETRY_HOME="/opt/poetry" -ENV POETRY_VERSION="1.3.2" +ENV POETRY_VERSION="1.7.1" ENV POETRY_VIRTUALENVS_CREATE="false" ENV PATH="${APP_VENV}/bin:${POETRY_HOME}/bin:$PATH" diff --git a/ci/Dockerfile.test b/ci/Dockerfile.test index ccdc2d7208..e068dfbfd5 100644 --- a/ci/Dockerfile.test +++ b/ci/Dockerfile.test @@ -3,10 +3,10 @@ FROM python:3.10-alpine@sha256:860f632e67178d9e90c7dfa9844a5e02098220bff5716d3c2fe1870325f00853 ENV PYTHONDONTWRITEBYTECODE 1 -ENV POETRY_VERSION "1.3.2" +ENV POETRY_VERSION "1.7.1" ARG APP_VENV="/app/.venv" ARG POETRY_HOME="/opt/poetry" -ARG POETRY_VERSION="1.3.2" +ARG POETRY_VERSION="1.7.1" ARG POETRY_VIRTUALENVS_CREATE="false" ENV PATH="${APP_VENV}/bin:${POETRY_HOME}/bin:$PATH" diff --git a/local/Dockerfile b/local/Dockerfile index 11444b22cf..f4ea41376c 100644 --- a/local/Dockerfile +++ b/local/Dockerfile @@ -1,7 +1,7 @@ FROM python:3.10-alpine@sha256:860f632e67178d9e90c7dfa9844a5e02098220bff5716d3c2fe1870325f00853 ENV PYTHONDONTWRITEBYTECODE 1 -ENV POETRY_VERSION "1.3.2" +ENV POETRY_VERSION "1.7.1" RUN apk add --no-cache bash build-base git gcc musl-dev postgresql-dev g++ make libffi-dev libmagic libcurl curl-dev && rm -rf /var/cache/apk/* diff --git a/poetry.lock b/poetry.lock index 7014e7e5fd..5482ec4aa3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "aiohttp" version = "3.9.1" description = "Async http client/server framework (asyncio)" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -100,6 +101,7 @@ speedups = ["Brotli", "aiodns", "brotlicffi"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -114,6 +116,7 @@ frozenlist = ">=1.1.0" name = "alembic" version = "1.12.1" description = "A database migration tool for SQLAlchemy." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -133,6 +136,7 @@ tz = ["python-dateutil"] name = "amqp" version = "5.2.0" description = "Low-level AMQP client for Python (fork of amqplib)." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -147,6 +151,7 @@ vine = ">=5.0.0,<6.0.0" name = "annotated-types" version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -158,6 +163,7 @@ files = [ name = "apig-wsgi" version = "2.18.0" description = "Wrap a WSGI application in an AWS Lambda handler function for running on API Gateway or an ALB." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -169,6 +175,7 @@ files = [ name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -180,6 +187,7 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -198,6 +206,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "aws-embedded-metrics" version = "1.0.8" description = "AWS Embedded Metrics Package" +category = "main" optional = false python-versions = "*" files = [ @@ -212,6 +221,7 @@ aiohttp = "*" name = "awscli" version = "1.32.25" description = "Universal Command Line Environment for AWS." +category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -231,6 +241,7 @@ s3transfer = ">=0.10.0,<0.11.0" name = "awscli-cwlogs" version = "1.4.6" description = "AWSCLI CloudWatch Logs plugin" +category = "main" optional = false python-versions = "*" files = [ @@ -247,6 +258,7 @@ six = ">=1.1.0" name = "bcrypt" version = "4.1.1" description = "Modern password hashing for your software and your servers" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -277,6 +289,7 @@ typecheck = ["mypy"] name = "billiard" version = "4.2.0" description = "Python multiprocessing fork with improvements and bugfixes" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -288,6 +301,7 @@ files = [ name = "black" version = "23.7.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -333,6 +347,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "bleach" version = "6.0.0" description = "An easy safelist-based HTML-sanitizing tool." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -351,6 +366,7 @@ css = ["tinycss2 (>=1.1.0,<1.2)"] name = "blinker" version = "1.7.0" description = "Fast, simple object-to-object and broadcast signaling" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -362,6 +378,7 @@ files = [ name = "boto" version = "2.49.0" description = "Amazon Web Services Library" +category = "main" optional = false python-versions = "*" files = [ @@ -373,6 +390,7 @@ files = [ name = "boto3" version = "1.34.25" description = "The AWS SDK for Python" +category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -392,6 +410,7 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] name = "botocore" version = "1.34.25" description = "Low-level, data-driven core of boto 3." +category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -411,6 +430,7 @@ crt = ["awscrt (==0.19.19)"] name = "brotli" version = "1.1.0" description = "Python bindings for the Brotli compression library" +category = "dev" optional = false python-versions = "*" files = [ @@ -503,6 +523,7 @@ files = [ name = "cachelib" version = "0.10.2" description = "A collection of cache libraries in the same API interface." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -514,6 +535,7 @@ files = [ name = "cachetools" version = "4.2.4" description = "Extensible memoizing collections and decorators" +category = "main" optional = false python-versions = "~=3.5" files = [ @@ -525,6 +547,7 @@ files = [ name = "celery" version = "5.3.6" description = "Distributed Task Queue." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -586,6 +609,7 @@ zstd = ["zstandard (==0.22.0)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -597,6 +621,7 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" files = [ @@ -673,6 +698,7 @@ pycparser = "*" name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -772,6 +798,7 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -786,6 +813,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "click-datetime" version = "0.2" description = "Datetime type support for click." +category = "main" optional = false python-versions = "*" files = [ @@ -803,6 +831,7 @@ dev = ["wheel"] name = "click-didyoumean" version = "0.3.0" description = "Enables git-like *did-you-mean* feature in click" +category = "main" optional = false python-versions = ">=3.6.2,<4.0.0" files = [ @@ -817,6 +846,7 @@ click = ">=7" name = "click-plugins" version = "1.1.1" description = "An extension module for click to enable registering CLI commands via setuptools entry-points." +category = "main" optional = false python-versions = "*" files = [ @@ -834,6 +864,7 @@ dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] name = "click-repl" version = "0.3.0" description = "REPL plugin for Click" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -852,6 +883,7 @@ testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -863,6 +895,7 @@ files = [ 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." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -878,6 +911,7 @@ yaml = ["PyYAML"] name = "coverage" version = "5.5" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" files = [ @@ -945,6 +979,7 @@ toml = ["toml"] name = "coveralls" version = "3.3.1" description = "Show coverage stats online via coveralls.io" +category = "dev" optional = false python-versions = ">= 3.5" files = [ @@ -953,7 +988,7 @@ files = [ ] [package.dependencies] -coverage = ">=4.1,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<7.0" +coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" docopt = ">=0.6.1" requests = ">=1.0.0" @@ -962,53 +997,64 @@ yaml = ["PyYAML (>=3.10)"] [[package]] name = "cryptography" -version = "41.0.7" +version = "42.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, - {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, - {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, - {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, + {file = "cryptography-42.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:de5086cd475d67113ccb6f9fae6d8fe3ac54a4f9238fd08bfdb07b03d791ff0a"}, + {file = "cryptography-42.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:935cca25d35dda9e7bd46a24831dfd255307c55a07ff38fd1a92119cffc34857"}, + {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20100c22b298c9eaebe4f0b9032ea97186ac2555f426c3e70670f2517989543b"}, + {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eb6368d5327d6455f20327fb6159b97538820355ec00f8cc9464d617caecead"}, + {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:39d5c93e95bcbc4c06313fc6a500cee414ee39b616b55320c1904760ad686938"}, + {file = "cryptography-42.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d96ea47ce6d0055d5b97e761d37b4e84195485cb5a38401be341fabf23bc32a"}, + {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d1998e545081da0ab276bcb4b33cce85f775adb86a516e8f55b3dac87f469548"}, + {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93fbee08c48e63d5d1b39ab56fd3fdd02e6c2431c3da0f4edaf54954744c718f"}, + {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:90147dad8c22d64b2ff7331f8d4cddfdc3ee93e4879796f837bdbb2a0b141e0c"}, + {file = "cryptography-42.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4dcab7c25e48fc09a73c3e463d09ac902a932a0f8d0c568238b3696d06bf377b"}, + {file = "cryptography-42.0.3-cp37-abi3-win32.whl", hash = "sha256:1e935c2900fb53d31f491c0de04f41110351377be19d83d908c1fd502ae8daa5"}, + {file = "cryptography-42.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:762f3771ae40e111d78d77cbe9c1035e886ac04a234d3ee0856bf4ecb3749d54"}, + {file = "cryptography-42.0.3-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3ec384058b642f7fb7e7bff9664030011ed1af8f852540c76a1317a9dd0d20"}, + {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35772a6cffd1f59b85cb670f12faba05513446f80352fe811689b4e439b5d89e"}, + {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04859aa7f12c2b5f7e22d25198ddd537391f1695df7057c8700f71f26f47a129"}, + {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3d1f5a1d403a8e640fa0887e9f7087331abb3f33b0f2207d2cc7f213e4a864c"}, + {file = "cryptography-42.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df34312149b495d9d03492ce97471234fd9037aa5ba217c2a6ea890e9166f151"}, + {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:de4ae486041878dc46e571a4c70ba337ed5233a1344c14a0790c4c4be4bbb8b4"}, + {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0fab2a5c479b360e5e0ea9f654bcebb535e3aa1e493a715b13244f4e07ea8eec"}, + {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25b09b73db78facdfd7dd0fa77a3f19e94896197c86e9f6dc16bce7b37a96504"}, + {file = "cryptography-42.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d5cf11bc7f0b71fb71af26af396c83dfd3f6eed56d4b6ef95d57867bf1e4ba65"}, + {file = "cryptography-42.0.3-cp39-abi3-win32.whl", hash = "sha256:0fea01527d4fb22ffe38cd98951c9044400f6eff4788cf52ae116e27d30a1ba3"}, + {file = "cryptography-42.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:2619487f37da18d6826e27854a7f9d4d013c51eafb066c80d09c63cf24505306"}, + {file = "cryptography-42.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ead69ba488f806fe1b1b4050febafdbf206b81fa476126f3e16110c818bac396"}, + {file = "cryptography-42.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:20180da1b508f4aefc101cebc14c57043a02b355d1a652b6e8e537967f1e1b46"}, + {file = "cryptography-42.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fbf0f3f0fac7c089308bd771d2c6c7b7d53ae909dce1db52d8e921f6c19bb3a"}, + {file = "cryptography-42.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c23f03cfd7d9826cdcbad7850de67e18b4654179e01fe9bc623d37c2638eb4ef"}, + {file = "cryptography-42.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db0480ffbfb1193ac4e1e88239f31314fe4c6cdcf9c0b8712b55414afbf80db4"}, + {file = "cryptography-42.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:6c25e1e9c2ce682d01fc5e2dde6598f7313027343bd14f4049b82ad0402e52cd"}, + {file = "cryptography-42.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9541c69c62d7446539f2c1c06d7046aef822940d248fa4b8962ff0302862cc1f"}, + {file = "cryptography-42.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b797099d221df7cce5ff2a1d272761d1554ddf9a987d3e11f6459b38cd300fd"}, + {file = "cryptography-42.0.3.tar.gz", hash = "sha256:069d2ce9be5526a44093a0991c450fe9906cdf069e0e7cd67d9dee49a62b9ebe"}, ] [package.dependencies] -cffi = ">=1.12" +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [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)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" +category = "main" optional = false python-versions = "*" files = [ @@ -1019,6 +1065,7 @@ files = [ name = "docutils" version = "0.16" description = "Docutils -- Python Documentation Utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1030,6 +1077,7 @@ files = [ name = "environs" version = "9.5.0" description = "simplified environment variable parsing" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1051,6 +1099,7 @@ tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1065,6 +1114,7 @@ test = ["pytest (>=6)"] name = "execnet" version = "2.0.2" description = "execnet: rapid multi-Python deployment" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1079,6 +1129,7 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] name = "fido2" version = "0.9.3" description = "Python based FIDO 2.0 library" +category = "main" optional = false python-versions = ">=2.7.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -1096,6 +1147,7 @@ pcsc = ["pyscard"] name = "filelock" version = "3.13.1" description = "A platform independent file lock." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1112,6 +1164,7 @@ typing = ["typing-extensions (>=4.8)"] name = "flake8" version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -1128,6 +1181,7 @@ pyflakes = ">=3.1.0,<3.2.0" name = "flask" version = "2.3.3" description = "A simple framework for building complex web applications." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1150,6 +1204,7 @@ dotenv = ["python-dotenv"] name = "flask-basicauth" version = "0.2.0" description = "HTTP basic access authentication for Flask." +category = "dev" optional = false python-versions = "*" files = [ @@ -1163,6 +1218,7 @@ Flask = "*" name = "flask-bcrypt" version = "1.0.1" description = "Brcrypt hashing for Flask." +category = "main" optional = false python-versions = "*" files = [ @@ -1178,6 +1234,7 @@ Flask = "*" name = "flask-cors" version = "4.0.0" description = "A Flask extension adding a decorator for CORS support" +category = "dev" optional = false python-versions = "*" files = [ @@ -1192,6 +1249,7 @@ Flask = ">=0.9" name = "flask-marshmallow" version = "0.14.0" description = "Flask + marshmallow for beautiful APIs" +category = "main" optional = false python-versions = "*" files = [ @@ -1215,6 +1273,7 @@ tests = ["flask-sqlalchemy", "marshmallow-sqlalchemy (>=0.13.0)", "marshmallow-s name = "flask-migrate" version = "2.7.0" description = "SQLAlchemy database migrations for Flask applications using Alembic" +category = "main" optional = false python-versions = "*" files = [ @@ -1231,6 +1290,7 @@ Flask-SQLAlchemy = ">=1.0" name = "flask-redis" version = "0.4.0" description = "A nice way to use Redis in your Flask app" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1250,6 +1310,7 @@ tests = ["coverage", "pytest", "pytest-mock"] name = "Flask-SQLAlchemy" version = "2.3.2.dev20231128" description = "Adds SQLAlchemy support to your Flask application" +category = "main" optional = false python-versions = "*" files = [] @@ -1269,6 +1330,7 @@ resolved_reference = "500e732dd1b975a56ab06a46bd1a20a21e682262" name = "freezegun" version = "1.2.2" description = "Let your Python tests travel through time" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1283,6 +1345,7 @@ python-dateutil = ">=2.7" name = "frozenlist" version = "1.4.0" description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1353,6 +1416,7 @@ files = [ name = "future" version = "0.18.3" description = "Clean single-source support for Python 3 and 2" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1363,6 +1427,7 @@ files = [ name = "gevent" version = "23.9.1" description = "Coroutine-based network library" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1425,6 +1490,7 @@ test = ["cffi (>=1.12.2)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idn name = "geventhttpclient" version = "2.0.11" description = "http client library for gevent" +category = "dev" optional = false python-versions = "*" files = [ @@ -1548,6 +1614,7 @@ six = "*" name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -1625,6 +1692,7 @@ test = ["objgraph", "psutil"] name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1645,6 +1713,7 @@ tornado = ["tornado (>=0.2)"] name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1656,6 +1725,7 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1667,6 +1737,7 @@ files = [ name = "iso8601" version = "2.0.0" description = "Simple module to parse ISO 8601 dates" +category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -1678,6 +1749,7 @@ files = [ name = "isodate" version = "0.6.1" description = "An ISO 8601 date/time/duration parser and formatter" +category = "main" optional = false python-versions = "*" files = [ @@ -1692,6 +1764,7 @@ six = "*" name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." +category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1709,6 +1782,7 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1720,6 +1794,7 @@ files = [ name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1737,6 +1812,7 @@ i18n = ["Babel (>=2.7)"] name = "jinja2-cli" version = "0.8.2" description = "A CLI interface to Jinja2" +category = "dev" optional = false python-versions = "*" files = [ @@ -1758,6 +1834,7 @@ yaml = ["jinja2", "pyyaml"] name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1769,6 +1846,7 @@ files = [ name = "jsonschema" version = "3.2.0" description = "An implementation of JSON Schema validation for Python" +category = "main" optional = false python-versions = "*" files = [ @@ -1790,6 +1868,7 @@ format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-va name = "kombu" version = "5.3.4" description = "Messaging library for Python." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1825,6 +1904,7 @@ zookeeper = ["kazoo (>=2.8.0)"] name = "locust" version = "2.16.1" description = "Developer friendly load testing framework" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1852,6 +1932,7 @@ Werkzeug = ">=2.0.0" name = "lxml" version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ @@ -1959,6 +2040,7 @@ source = ["Cython (>=0.29.35)"] name = "mako" version = "1.3.0" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1978,6 +2060,7 @@ testing = ["pytest"] name = "markupsafe" version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2047,6 +2130,7 @@ files = [ name = "marshmallow" version = "3.20.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2067,6 +2151,7 @@ tests = ["pytest", "pytz", "simplejson"] name = "marshmallow-sqlalchemy" version = "0.29.0" description = "SQLAlchemy integration with the marshmallow (de)serialization library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2089,6 +2174,7 @@ tests = ["pytest", "pytest-lazy-fixture (>=0.6.2)"] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2100,6 +2186,7 @@ files = [ name = "mistune" version = "0.8.4" description = "The fastest markdown parser in pure Python" +category = "main" optional = false python-versions = "*" files = [ @@ -2111,6 +2198,7 @@ files = [ name = "more-itertools" version = "8.14.0" description = "More routines for operating on iterables, beyond itertools" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2122,6 +2210,7 @@ files = [ name = "moto" version = "4.1.11" description = "" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2168,6 +2257,7 @@ xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] name = "msgpack" version = "1.0.7" description = "MessagePack serializer" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2233,6 +2323,7 @@ files = [ name = "multidict" version = "6.0.4" description = "multidict implementation" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2316,6 +2407,7 @@ files = [ name = "mypy" version = "1.5.0" description = "Optional static typing for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2357,6 +2449,7 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2368,6 +2461,7 @@ files = [ name = "nanoid" version = "2.0.0" description = "A tiny, secure, URL-friendly, unique string ID generator for Python" +category = "main" optional = false python-versions = "*" files = [ @@ -2379,6 +2473,7 @@ files = [ name = "networkx" version = "2.8.8" description = "Python package for creating and manipulating graphs and networks" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2397,6 +2492,7 @@ test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] name = "newrelic" version = "6.10.0.165" description = "New Relic Python Agent" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -2422,6 +2518,7 @@ infinite-tracing = ["grpcio (<2)", "protobuf (<4)"] name = "notifications-python-client" version = "6.4.1" description = "Python API client for GOV.UK Notify." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2435,8 +2532,9 @@ requests = ">=2.0.0" [[package]] name = "notifications-utils" -version = "52.1.0" +version = "52.1.3" description = "Shared python code for Notification - Provides logging utils etc." +category = "main" optional = false python-versions = "~3.10" files = [] @@ -2448,7 +2546,7 @@ bleach = "6.0.0" boto3 = "1.34.25" cachetools = "4.2.4" certifi = "^2023.7.22" -cryptography = "^41.0.2" +cryptography = "^42.0.3" Flask = "2.3.3" Flask-Redis = "0.4.0" itsdangerous = "2.1.2" @@ -2470,13 +2568,14 @@ werkzeug = "2.3.7" [package.source] type = "git" url = "https://github.com/cds-snc/notifier-utils.git" -reference = "2da74685e0ffb220f0403e1f2584e783be99bbad" -resolved_reference = "2da74685e0ffb220f0403e1f2584e783be99bbad" +reference = "upgrade-cryptography" +resolved_reference = "ac0422352576898b51325f914cf98d18e66f0bb3" [[package]] name = "ordered-set" version = "4.1.0" description = "An OrderedSet is a custom MutableSet that remembers its order, so that every" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2491,6 +2590,7 @@ dev = ["black", "mypy", "pytest"] name = "packaging" version = "23.2" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2502,6 +2602,7 @@ files = [ name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2513,6 +2614,7 @@ files = [ name = "pendulum" version = "2.1.2" description = "Python datetimes made easy" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2547,6 +2649,7 @@ pytzdata = ">=2020.1" name = "phonenumbers" version = "8.13.28" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." +category = "main" optional = false python-versions = "*" files = [ @@ -2558,6 +2661,7 @@ files = [ name = "platformdirs" version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2573,6 +2677,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2588,6 +2693,7 @@ testing = ["pytest", "pytest-benchmark"] name = "prompt-toolkit" version = "3.0.41" description = "Library for building powerful interactive command lines in Python" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -2602,6 +2708,7 @@ wcwidth = "*" name = "psutil" version = "5.9.6" description = "Cross-platform lib for process and system monitoring in Python." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -2630,6 +2737,7 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] name = "psycopg2-binary" version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2711,6 +2819,7 @@ files = [ name = "pwnedpasswords" version = "2.0.0" description = "A Python wrapper for Troy Hunt's Pwned Passwords API." +category = "main" optional = false python-versions = "*" files = [ @@ -2724,6 +2833,7 @@ future = "*" name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2735,6 +2845,7 @@ files = [ name = "py-w3c" version = "0.3.1" description = "W3C services for python." +category = "main" optional = false python-versions = "*" files = [ @@ -2745,6 +2856,7 @@ files = [ name = "pyasn1" version = "0.5.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -2756,6 +2868,7 @@ files = [ name = "pycodestyle" version = "2.11.1" description = "Python style guide checker" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2767,6 +2880,7 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2778,6 +2892,7 @@ files = [ name = "pycurl" version = "7.45.2" description = "PycURL -- A Python Interface To The cURL library" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2788,6 +2903,7 @@ files = [ name = "pydantic" version = "2.5.2" description = "Data validation using Python type hints" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2807,6 +2923,7 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.14.5" description = "" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2924,6 +3041,7 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pyflakes" version = "3.1.0" description = "passive checker of Python programs" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2935,6 +3053,7 @@ files = [ name = "pyjwt" version = "2.8.0" description = "JSON Web Token implementation in Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2952,6 +3071,7 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] name = "pypdf2" version = "1.28.6" description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" +category = "main" optional = false python-versions = ">=2.7" files = [ @@ -2963,6 +3083,7 @@ files = [ name = "pyrsistent" version = "0.20.0" description = "Persistent/Functional/Immutable data structures" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3004,6 +3125,7 @@ files = [ name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3026,6 +3148,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-cov" version = "3.0.0" description = "Pytest plugin for measuring coverage." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3044,6 +3167,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-env" version = "0.8.2" description = "py.test plugin that allows you to add environment variables." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3061,6 +3185,7 @@ test = ["coverage (>=7.2.7)", "pytest-mock (>=3.10)"] name = "pytest-forked" version = "1.6.0" description = "run tests in isolated forked subprocesses" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3076,6 +3201,7 @@ pytest = ">=3.10" name = "pytest-mock" version = "3.11.1" description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3093,6 +3219,7 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "pytest-mock-resources" version = "2.9.2" description = "A pytest plugin for easily instantiating reproducible mock resources." +category = "dev" optional = false python-versions = ">=3.7,<4" files = [ @@ -3123,6 +3250,7 @@ redshift = ["boto3", "filelock", "moto", "python-on-whales (>=0.22.0)", "sqlpars name = "pytest-xdist" version = "2.5.0" description = "pytest xdist plugin for distributed testing and loop-on-failing modes" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3144,6 +3272,7 @@ testing = ["filelock"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -3158,6 +3287,7 @@ six = ">=1.5" name = "python-dotenv" version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3172,6 +3302,7 @@ cli = ["click (>=5.0)"] name = "python-json-logger" version = "2.0.7" description = "A python library adding a json log formatter" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3183,6 +3314,7 @@ files = [ name = "python-magic" version = "0.4.27" description = "File type identification using libmagic" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -3194,6 +3326,7 @@ files = [ name = "python-on-whales" version = "0.67.0" description = "A Docker client for Python, designed to be fun and intuitive!" +category = "dev" optional = false python-versions = "<4,>=3.8" files = [ @@ -3202,7 +3335,7 @@ files = [ ] [package.dependencies] -pydantic = ">=1.9,<2.0.dev0 || >=2.1.dev0,<3" +pydantic = ">=1.9,<2.0.0 || >=2.1.0,<3" requests = "*" tqdm = "*" typer = ">=0.4.1" @@ -3215,6 +3348,7 @@ test = ["pytest"] name = "pytz" version = "2021.3" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ @@ -3226,6 +3360,7 @@ files = [ name = "pytzdata" version = "2020.1" description = "The Olson timezone database for Python." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3237,6 +3372,7 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" +category = "dev" optional = false python-versions = "*" files = [ @@ -3260,6 +3396,7 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3320,6 +3457,7 @@ files = [ name = "pyzmq" version = "25.1.1" description = "Python bindings for 0MQ" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3425,6 +3563,7 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} name = "redis" version = "5.0.1" description = "Python client for Redis database and key-value store" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3443,6 +3582,7 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3464,6 +3604,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-file" version = "1.5.1" description = "File transport adapter for Requests" +category = "main" optional = false python-versions = "*" files = [ @@ -3479,6 +3620,7 @@ six = "*" name = "requests-mock" version = "1.11.0" description = "Mock out responses from the requests package" +category = "dev" optional = false python-versions = "*" files = [ @@ -3498,6 +3640,7 @@ test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "tes name = "requests-toolbelt" version = "1.0.0" description = "A utility belt for advanced users of python-requests" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3512,6 +3655,7 @@ requests = ">=2.0.1,<3.0.0" name = "responses" version = "0.24.1" description = "A utility library for mocking out the `requests` Python library." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3531,6 +3675,7 @@ tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asy name = "rfc3987" version = "1.3.8" description = "Parsing and validation of URIs (RFC 3986) and IRIs (RFC 3987)" +category = "dev" optional = false python-versions = "*" files = [ @@ -3542,6 +3687,7 @@ files = [ name = "roundrobin" version = "0.0.4" description = "Collection of roundrobin utilities" +category = "dev" optional = false python-versions = "*" files = [ @@ -3552,6 +3698,7 @@ files = [ name = "rsa" version = "4.7.2" description = "Pure-Python RSA implementation" +category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -3566,6 +3713,7 @@ pyasn1 = ">=0.1.3" name = "s3transfer" version = "0.10.0" description = "An Amazon S3 Transfer Manager" +category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -3583,6 +3731,7 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] name = "setuptools" version = "69.0.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3599,6 +3748,7 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "simple-salesforce" version = "1.12.5" description = "A basic Salesforce.com REST API client." +category = "main" optional = false python-versions = "*" files = [ @@ -3618,6 +3768,7 @@ zeep = "*" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3629,6 +3780,7 @@ files = [ name = "smartypants" version = "2.0.1" description = "Python with the SmartyPants" +category = "main" optional = false python-versions = "*" files = [ @@ -3639,6 +3791,7 @@ files = [ name = "sqlalchemy" version = "1.4.51" description = "Database Abstraction Library" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -3718,6 +3871,7 @@ sqlcipher = ["sqlcipher3_binary"] name = "sqlalchemy-stubs" version = "0.4" description = "SQLAlchemy stubs and mypy plugin" +category = "dev" optional = false python-versions = "*" files = [ @@ -3733,6 +3887,7 @@ typing-extensions = ">=3.7.4" name = "sqlalchemy2-stubs" version = "0.0.2a38" description = "Typing Stubs for SQLAlchemy 1.4" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3747,6 +3902,7 @@ typing-extensions = ">=3.7.4" name = "statsd" version = "3.3.0" description = "A simple statsd client." +category = "main" optional = false python-versions = "*" files = [ @@ -3758,6 +3914,7 @@ files = [ name = "strict-rfc3339" version = "0.7" description = "Strict, simple, lightweight RFC3339 functions" +category = "dev" optional = false python-versions = "*" files = [ @@ -3768,6 +3925,7 @@ files = [ name = "tldextract" version = "3.4.4" description = "Accurately separates a URL's subdomain, domain, and public suffix, using the Public Suffix List (PSL). By default, this includes the public ICANN TLDs and their exceptions. You can optionally support the Public Suffix List's private domains as well." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3785,6 +3943,7 @@ requests-file = ">=1.4" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3796,6 +3955,7 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3807,6 +3967,7 @@ files = [ name = "tqdm" version = "4.66.1" description = "Fast, Extensible Progress Meter" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3827,6 +3988,7 @@ telegram = ["requests"] name = "typer" version = "0.9.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3848,6 +4010,7 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. name = "types-boto" version = "2.49.18.9" description = "Typing stubs for boto" +category = "dev" optional = false python-versions = "*" files = [ @@ -3859,6 +4022,7 @@ files = [ name = "types-mock" version = "4.0.15.2" description = "Typing stubs for mock" +category = "dev" optional = false python-versions = "*" files = [ @@ -3870,6 +4034,7 @@ files = [ name = "types-pyopenssl" version = "23.3.0.0" description = "Typing stubs for pyOpenSSL" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3884,6 +4049,7 @@ cryptography = ">=35.0.0" name = "types-python-dateutil" version = "2.8.19.20240106" description = "Typing stubs for python-dateutil" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3895,6 +4061,7 @@ files = [ name = "types-pytz" version = "2022.7.1.2" description = "Typing stubs for pytz" +category = "dev" optional = false python-versions = "*" files = [ @@ -3906,6 +4073,7 @@ files = [ name = "types-redis" version = "4.6.0.20240106" description = "Typing stubs for redis" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3921,6 +4089,7 @@ types-pyOpenSSL = "*" name = "types-requests" version = "2.31.0.20240106" description = "Typing stubs for requests" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3935,6 +4104,7 @@ urllib3 = ">=2" name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3946,6 +4116,7 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" +category = "main" optional = false python-versions = ">=2" files = [ @@ -3957,6 +4128,7 @@ files = [ name = "unidecode" version = "1.3.8" description = "ASCII transliterations of Unicode text" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -3968,6 +4140,7 @@ files = [ name = "urllib3" version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3985,6 +4158,7 @@ zstd = ["zstandard (>=0.18.0)"] name = "vine" version = "5.1.0" description = "Python promises." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3996,6 +4170,7 @@ files = [ name = "wcwidth" version = "0.2.12" description = "Measures the displayed width of unicode strings in a terminal" +category = "main" optional = false python-versions = "*" files = [ @@ -4007,6 +4182,7 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" +category = "main" optional = false python-versions = "*" files = [ @@ -4018,6 +4194,7 @@ files = [ name = "werkzeug" version = "2.3.7" description = "The comprehensive WSGI web application library." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4035,6 +4212,7 @@ watchdog = ["watchdog (>=2.3)"] name = "xmltodict" version = "0.13.0" description = "Makes working with XML feel like you are working with JSON" +category = "dev" optional = false python-versions = ">=3.4" files = [ @@ -4046,6 +4224,7 @@ files = [ name = "yarl" version = "1.9.3" description = "Yet another URL library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4149,6 +4328,7 @@ multidict = ">=4.0" name = "zeep" version = "4.2.1" description = "A Python SOAP client" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4176,6 +4356,7 @@ xmlsec = ["xmlsec (>=0.6.1)"] name = "zope-event" version = "5.0" description = "Very basic event publishing system" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4194,6 +4375,7 @@ test = ["zope.testrunner"] name = "zope-interface" version = "6.1" description = "Interfaces for Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4246,4 +4428,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "~3.10.9" -content-hash = "96a15f8c5b9c35b9e581546744ec06b0f17dcf709d49db7f424a585243af67c8" +content-hash = "e7aced65ef6042df1c2ea1d4f4910a22767a8a521c3f1cf0bf0b191c41b2208a" diff --git a/pyproject.toml b/pyproject.toml index 0df484ce40..e92314b4c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ Werkzeug = "2.3.7" MarkupSafe = "2.1.4" # REVIEW: v2 is using sha512 instead of sha1 by default (in v1) itsdangerous = "2.1.2" -notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", rev = "2da74685e0ffb220f0403e1f2584e783be99bbad" } +notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", branch = "upgrade-cryptography" } # rsa = "4.9 # awscli 1.22.38 depends on rsa<4.8 typing-extensions = "4.7.1" greenlet = "2.0.2" @@ -72,7 +72,6 @@ simple-salesforce = "^1.12.3" # Pinned dependencies certifi = "^2023.7.22" # pinned for security reasons: https://github.com/cds-snc/notification-api/security/dependabot/119 -cryptography = "^41.0.2" # pinned for security reasons: https://github.com/cds-snc/notification-api/security/dependabot/118 idna = "2.10" # pinned to align with test moto dependency requirements (for <=2.9) [tool.poetry.group.test.dependencies] diff --git a/tests-perf/ops/Dockerfile b/tests-perf/ops/Dockerfile index fad9c6f3ee..ba36582848 100644 --- a/tests-perf/ops/Dockerfile +++ b/tests-perf/ops/Dockerfile @@ -1,10 +1,10 @@ FROM python:3.10-alpine3.16@sha256:afe68972cc00883d70b3760ee0ffbb7375cf09706c122dda7063ffe64c5be21b ENV PYTHONDONTWRITEBYTECODE 1 -ENV POETRY_VERSION "1.3.2" +ENV POETRY_VERSION "1.7.1" ENV APP_VENV="/app/.venv" ENV POETRY_HOME="/opt/poetry" -ENV POETRY_VERSION="1.3.2" +ENV POETRY_VERSION="1.7.1" ENV POETRY_VIRTUALENVS_CREATE="false" ENV PATH="${APP_VENV}/bin:${POETRY_HOME}/bin:$PATH" From f4c7a95f705581af548ae39e3ccc3793e56d5dc6 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 22 Feb 2024 11:49:27 -0400 Subject: [PATCH 30/33] Fix/dashboard statistics (#2113) * fix: update dates that are used to popupate ft_notification_status table to use midnight UTC like everything else * fix(get_notifications_for_service): use midnight UTC to stay consistent with the rest of the app * chore: remove unused imports * chore: formatting * chore: remove unused import * test: ensure `get_all_notifications_for_service()` counts notifications for all hours of the day for the entire week * test: ensure `ft_notification_status` includes notifications from all hours of a given day (`00:00:00` to `23:59:59`) * chore: formatting * chore: remove unused import --------- Co-authored-by: William B <7444334+whabanks@users.noreply.github.com> --- app/dao/fact_notification_status_dao.py | 5 ++- app/dao/notifications_dao.py | 9 ++--- .../dao/test_fact_notification_status_dao.py | 33 +++++++++++++++++++ tests/app/service/test_rest.py | 29 ++++++++++++++++ 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/app/dao/fact_notification_status_dao.py b/app/dao/fact_notification_status_dao.py index 533ee274b3..8fdb52ec70 100644 --- a/app/dao/fact_notification_status_dao.py +++ b/app/dao/fact_notification_status_dao.py @@ -1,7 +1,6 @@ from datetime import datetime, time, timedelta from flask import current_app -from notifications_utils.timezones import convert_local_timezone_to_utc from sqlalchemy import Date, case, func from sqlalchemy.dialects.postgresql import insert from sqlalchemy.sql.expression import extract, literal @@ -39,8 +38,8 @@ def fetch_notification_status_for_day(process_day, service_id=None): - start_date = convert_local_timezone_to_utc(datetime.combine(process_day, time.min)) - end_date = convert_local_timezone_to_utc(datetime.combine(process_day + timedelta(days=1), time.min)) + start_date = datetime.combine(process_day, time.min) + end_date = datetime.combine(process_day + timedelta(days=1), time.min) # use notification_history if process day is older than 7 days # this is useful if we need to rebuild the ft_billing table for a date older than 7 days ago. current_app.logger.info("Fetch ft_notification_status for {} to {}".format(start_date, end_date)) diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index a39af634f1..9e20bca476 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -27,6 +27,7 @@ from app import create_uuid, db, signer_personalisation from app.aws.s3 import get_s3_bucket_objects, remove_s3_object from app.dao.dao_utils import transactional +from app.dao.date_util import utc_midnight_n_days_ago from app.errors import InvalidRequest from app.letters.utils import LETTERS_PDF_FILE_LOCATION_STRUCTURE from app.models import ( @@ -51,11 +52,7 @@ Service, ServiceDataRetention, ) -from app.utils import ( - escape_special_characters, - get_local_timezone_midnight_in_utc, - midnight_n_days_ago, -) +from app.utils import escape_special_characters, get_local_timezone_midnight_in_utc @transactional @@ -335,7 +332,7 @@ def get_notifications_for_service( filters = [Notification.service_id == service_id] if limit_days is not None: - filters.append(Notification.created_at >= midnight_n_days_ago(limit_days)) + filters.append(Notification.created_at >= utc_midnight_n_days_ago(limit_days)) if older_than is not None: older_than_created_at = db.session.query(Notification.created_at).filter(Notification.id == older_than).as_scalar() diff --git a/tests/app/dao/test_fact_notification_status_dao.py b/tests/app/dao/test_fact_notification_status_dao.py index e19d5adbae..ce10e24777 100644 --- a/tests/app/dao/test_fact_notification_status_dao.py +++ b/tests/app/dao/test_fact_notification_status_dao.py @@ -331,6 +331,39 @@ def test_fetch_notification_status_by_template_for_service_for_today_and_7_previ ] == sorted(results, key=lambda x: (x.notification_type, x.status, x.template_name, x.count)) +@freeze_time("2018-10-31T18:00:00") +def test_fetch_notification_status_gets_data_from_correct_timeframe( + notify_db_session, +): + service_1 = create_service(service_name="service_1") + sms_template = create_template(service=service_1, template_type=SMS_TYPE) + email_template = create_template(service=service_1, template_type=EMAIL_TYPE) + + # create notifications for every hour of the day + for i in range(24): + save_notification(create_notification(email_template, created_at=datetime(2018, 10, 30, i, 0, 0), status="delivered")) + save_notification(create_notification(email_template, created_at=datetime(2018, 10, 30, i, 0, 59), status="delivered")) + save_notification(create_notification(sms_template, created_at=datetime(2018, 10, 30, i, 0, 0), status="delivered")) + save_notification(create_notification(sms_template, created_at=datetime(2018, 10, 30, i, 0, 30), status="delivered")) + save_notification(create_notification(sms_template, created_at=datetime(2018, 10, 30, i, 0, 59), status="delivered")) + + # too early, shouldn't be included + save_notification( + create_notification( + service_1.templates[0], + created_at=datetime(2018, 10, 29, 23, 59, 59), + status="delivered", + ) + ) + data = fetch_notification_status_for_day(process_day=datetime.utcnow() - timedelta(days=1)) + + assert data[0].notification_type == "email" + assert data[0].notification_count == 48 + + assert data[1].notification_type == "sms" + assert data[1].notification_count == 72 + + def test_get_total_notifications_sent_for_api_key(notify_db_session): service = create_service(service_name="First Service") api_key = create_api_key(service) diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index e3e309cda5..27ded50b48 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -1730,6 +1730,35 @@ def test_get_notifications_for_service_without_page_count( assert resp["notifications"][0]["id"] == str(without_job.id) +@freeze_time("2018-11-20T18:00:00") +@pytest.mark.parametrize("retention_period, expected_count_of_notifications", [(3, 72), (7, 168)]) +def test_get_notifications_for_service_gets_data_from_correct_timeframe( + admin_request, sample_service, retention_period, expected_count_of_notifications +): + email_template = create_template(service=sample_service, template_type=EMAIL_TYPE) + + # WEEK BEFORE + # Create 12 notifications for each hour of the day for 1 week + for i in range(retention_period): + for j in range(24): + save_notification( + create_notification(email_template, created_at=datetime(2018, 11, 5 + i, j, 0, 0), status="delivered") + ) + + # THIS WEEK + # Create 12 notifications for each hour of the day for 1 week + for i in range(retention_period): + for j in range(24): + save_notification( + create_notification(email_template, created_at=datetime(2018, 11, 13 + i, j, 0, 0), status="delivered") + ) + + resp = admin_request.get( + "service.get_all_notifications_for_service", service_id=email_template.service_id, limit_days=7, page_size=1 + ) + assert resp["total"] == expected_count_of_notifications + + @pytest.mark.parametrize( "should_prefix", [ From 9d1d445877c8a92adba6f18d134fe3aae259c7b8 Mon Sep 17 00:00:00 2001 From: William B <7444334+whabanks@users.noreply.github.com> Date: Thu, 22 Feb 2024 13:29:30 -0400 Subject: [PATCH 31/33] Fix daily limit counting when scheduling jobs (#2112) * Fix daily limit counting when scheduling jobs - When scheduling a job, the daily limits in Redis will only be incremented if the job was scheduled for the current day - When refreshing daily count keys from the DB the underlying query will now include counts from jobs scheduled for the current day - Experimenting with a new decorator for feature flags - Fixed an issue with the dev container where the poetry installation will sometimes not be detected by adding the installation dir to the $PATH * Bump utils version and update lock file * fix tests? * Fix tests * Bump waffles commit sha in test action --- .../scripts/notify-dev-entrypoint.sh | 6 +- .github/workflows/test.yaml | 2 +- app/dao/services_dao.py | 43 +++++--- app/email_limit_utils.py | 11 ++- app/job/rest.py | 35 ++++--- app/notifications/rest.py | 2 +- app/v2/notifications/post_notifications.py | 5 +- tests/app/dao/test_services_dao.py | 97 ++++++++++++++++++- tests/app/job/test_rest.py | 1 + .../rest/test_send_notification.py | 2 +- 10 files changed, 164 insertions(+), 40 deletions(-) diff --git a/.devcontainer/scripts/notify-dev-entrypoint.sh b/.devcontainer/scripts/notify-dev-entrypoint.sh index 22cb552d2d..e2f99ea29a 100755 --- a/.devcontainer/scripts/notify-dev-entrypoint.sh +++ b/.devcontainer/scripts/notify-dev-entrypoint.sh @@ -34,8 +34,10 @@ cd /workspace echo -e "fpath+=/.zfunc" >> ~/.zshrc echo -e "autoload -Uz compinit && compinit" -pip install poetry==${POETRY_VERSION} \ - && poetry --version +pip install poetry==${POETRY_VERSION} +export PATH=$PATH:/home/vscode/.local/bin/ +which poetry +poetry --version # Initialize poetry autocompletions mkdir ~/.zfunc diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3169d77d64..8d5c023e27 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -67,7 +67,7 @@ jobs: run: | cp -f .env.example .env - name: Checks for new endpoints against AWS WAF rules - uses: cds-snc/notification-utils/.github/actions/waffles@2da74685e0ffb220f0403e1f2584e783be99bbad # 52.1.0 + uses: cds-snc/notification-utils/.github/actions/waffles@06a40db6286f525fe3551e029418458d33342592 # 52.1.0 with: app-loc: '/github/workspace' app-libs: '/github/workspace/env/site-packages' diff --git a/app/dao/services_dao.py b/app/dao/services_dao.py index 80d1f25c35..082a2566a1 100644 --- a/app/dao/services_dao.py +++ b/app/dao/services_dao.py @@ -23,6 +23,7 @@ CROWN_ORGANISATION_TYPES, EMAIL_TYPE, INTERNATIONAL_SMS_TYPE, + JOB_STATUS_SCHEDULED, KEY_TYPE_TEST, NHS_ORGANISATION_TYPES, NON_CROWN_ORGANISATION_TYPES, @@ -424,20 +425,24 @@ def dao_fetch_todays_stats_for_service(service_id): def fetch_todays_total_message_count(service_id): midnight = get_midnight(datetime.now(tz=pytz.utc)) + scheduled = ( + db.session.query(func.coalesce(func.sum(Job.notification_count), 0).label("count")).filter( + Job.service_id == service_id, + Job.job_status == JOB_STATUS_SCHEDULED, + Job.scheduled_for >= midnight, + Job.scheduled_for < midnight + timedelta(days=1), + ) + ).first() + result = ( - db.session.query(func.count(Notification.id).label("count")) - .filter( + db.session.query(func.coalesce(func.count(Notification.id), 0).label("count")).filter( Notification.service_id == service_id, Notification.key_type != KEY_TYPE_TEST, - Notification.created_at > midnight, + Notification.created_at >= midnight, ) - .group_by( - Notification.notification_type, - Notification.status, - ) - .first() - ) - return 0 if result is None else result.count + ).first() + + return result.count + scheduled.count def fetch_todays_total_sms_count(service_id): @@ -461,17 +466,25 @@ def fetch_service_email_limit(service_id: uuid.UUID) -> int: def fetch_todays_total_email_count(service_id: uuid.UUID) -> int: midnight = get_midnight(datetime.now(tz=pytz.utc)) + scheduled = ( + db.session.query(func.coalesce(func.sum(Job.notification_count), 0).label("total_scheduled_notifications")).filter( + Job.service_id == service_id, + Job.job_status == JOB_STATUS_SCHEDULED, + Job.scheduled_for > midnight, + Job.scheduled_for < midnight + timedelta(hours=23, minutes=59, seconds=59), + ) + ).first() + result = ( - db.session.query(func.count(Notification.id).label("total_email_notifications")) - .filter( + db.session.query(func.coalesce(func.count(Notification.id), 0).label("total_email_notifications")).filter( Notification.service_id == service_id, Notification.key_type != KEY_TYPE_TEST, Notification.created_at > midnight, Notification.notification_type == "email", ) - .first() - ) - return 0 if result is None else result.total_email_notifications + ).first() + + return result.total_email_notifications + scheduled.total_scheduled_notifications def _stats_for_service_query(service_id): diff --git a/app/email_limit_utils.py b/app/email_limit_utils.py index 7086d5c6f0..0fb8bf3364 100644 --- a/app/email_limit_utils.py +++ b/app/email_limit_utils.py @@ -3,6 +3,7 @@ from flask import current_app from notifications_utils.clients.redis import email_daily_count_cache_key +from notifications_utils.decorators import requires_feature from app import redis_store from app.dao.services_dao import fetch_todays_total_email_count @@ -20,9 +21,15 @@ def fetch_todays_email_count(service_id: UUID) -> int: return int(total_email_count) +@requires_feature("REDIS_ENABLED") def increment_todays_email_count(service_id: UUID, increment_by: int) -> None: - if not current_app.config["REDIS_ENABLED"]: - return fetch_todays_email_count(service_id) # to make sure it's set in redis cache_key = email_daily_count_cache_key(service_id) redis_store.incrby(cache_key, increment_by) + + +@requires_feature("REDIS_ENABLED") +def decrement_todays_email_count(service_id: UUID, decrement_by: int) -> None: + fetch_todays_email_count(service_id) + cache_key = email_daily_count_cache_key(service_id) + redis_store.decrby(cache_key, decrement_by) diff --git a/app/job/rest.py b/app/job/rest.py index 3c443ec4ba..49990f2961 100644 --- a/app/job/rest.py +++ b/app/job/rest.py @@ -1,3 +1,5 @@ +from datetime import datetime + import dateutil from flask import Blueprint, current_app, jsonify, request from notifications_utils.recipients import RecipientCSV @@ -20,6 +22,7 @@ from app.dao.notifications_dao import get_notifications_for_job from app.dao.services_dao import dao_fetch_service_by_id from app.dao.templates_dao import dao_get_template_by_id +from app.email_limit_utils import decrement_todays_email_count from app.errors import InvalidRequest, register_errors from app.models import ( EMAIL_TYPE, @@ -67,7 +70,7 @@ def cancel_job(service_id, job_id): job = dao_get_future_scheduled_job_by_id_and_service_id(job_id, service_id) job.job_status = JOB_STATUS_CANCELLED dao_update_job(job) - + decrement_todays_email_count(service_id, job.notification_count) return get_job_by_service_and_job_id(service_id, job_id) @@ -137,15 +140,23 @@ def create_job(service_id): raise InvalidRequest("Create job is not allowed: service is inactive ", 403) data = request.get_json() - data.update({"service": service_id}) + try: data.update(**get_job_metadata_from_s3(service_id, data["id"])) except KeyError: raise InvalidRequest({"id": ["Missing data for required field."]}, status_code=400) + if data.get("valid") != "True": + raise InvalidRequest("File is not valid, can't create job", 400) + data["template"] = data.pop("template_id") + template = dao_get_template_by_id(data["template"]) + template_errors = unarchived_template_schema.validate({"archived": template.archived}) + + if template_errors: + raise InvalidRequest(template_errors, status_code=400) job = get_job_from_s3(service_id, data["id"]) recipient_csv = RecipientCSV( @@ -170,22 +181,16 @@ def create_job(service_id): if not is_test_notification: check_sms_daily_limit(service, len(recipient_csv)) + increment_sms_daily_count_send_warnings_if_needed(service, len(recipient_csv)) - if template.template_type == EMAIL_TYPE: + elif template.template_type == EMAIL_TYPE: check_email_daily_limit(service, len(list(recipient_csv.get_rows()))) + scheduled_for = datetime.fromisoformat(form.get("scheduled_for")) if form.get("scheduled_for") else None # noqa: F821 - if data.get("valid") != "True": - raise InvalidRequest("File is not valid, can't create job", 400) - - errors = unarchived_template_schema.validate({"archived": template.archived}) - - if errors: - raise InvalidRequest(errors, status_code=400) - - if template.template_type == SMS_TYPE and not is_test_notification: - increment_sms_daily_count_send_warnings_if_needed(service, len(recipient_csv)) - elif template.template_type == EMAIL_TYPE: - increment_email_daily_count_send_warnings_if_needed(service, len(list(recipient_csv.get_rows()))) + if scheduled_for is None or not scheduled_for.date() > datetime.today().date(): + increment_email_daily_count_send_warnings_if_needed( + authenticated_service, len(list(recipient_csv.get_rows())) # noqa: F821 + ) data.update({"template_version": template.version}) diff --git a/app/notifications/rest.py b/app/notifications/rest.py index 70b09228f8..e3ec327df6 100644 --- a/app/notifications/rest.py +++ b/app/notifications/rest.py @@ -112,7 +112,7 @@ def send_notification(notification_type: NotificationType): ) simulated = simulated_recipient(notification_form["to"], notification_type) - if not simulated != api_user.key_type == KEY_TYPE_TEST: + if not simulated != api_user.key_type == KEY_TYPE_TEST and notification_type == EMAIL_TYPE: check_email_daily_limit(authenticated_service, 1) check_template_is_for_notification_type(notification_type, template.template_type) diff --git a/app/v2/notifications/post_notifications.py b/app/v2/notifications/post_notifications.py index d9d333c0e0..9a1640b9a2 100644 --- a/app/v2/notifications/post_notifications.py +++ b/app/v2/notifications/post_notifications.py @@ -223,7 +223,10 @@ def post_bulk(): if template.template_type == EMAIL_TYPE and api_user.key_type != KEY_TYPE_TEST: check_email_daily_limit(authenticated_service, len(list(recipient_csv.get_rows()))) - increment_email_daily_count_send_warnings_if_needed(authenticated_service, len(list(recipient_csv.get_rows()))) + scheduled_for = datetime.fromisoformat(form.get("scheduled_for")) if form.get("scheduled_for") else None + + if scheduled_for is None or not scheduled_for.date() > datetime.today().date(): + increment_email_daily_count_send_warnings_if_needed(authenticated_service, len(list(recipient_csv.get_rows()))) if template.template_type == SMS_TYPE: # calculate the number of simulated recipients diff --git a/tests/app/dao/test_services_dao.py b/tests/app/dao/test_services_dao.py index 4a81ea4f86..46941c9887 100644 --- a/tests/app/dao/test_services_dao.py +++ b/tests/app/dao/test_services_dao.py @@ -46,6 +46,7 @@ from app.models import ( EMAIL_TYPE, INTERNATIONAL_SMS_TYPE, + JOB_STATUS_SCHEDULED, KEY_TYPE_NORMAL, KEY_TYPE_TEAM, KEY_TYPE_TEST, @@ -68,6 +69,7 @@ user_folder_permissions, ) from app.schemas import service_schema +from tests.app.conftest import create_sample_job from tests.app.db import ( create_annual_billing, create_api_key, @@ -1396,7 +1398,7 @@ def create_email_sms_letter_template(): class TestServiceEmailLimits: - def test_get_email_count_for_service(self, notify_db_session): + def test_get_email_count_for_service(self): active_user_1 = create_user(email="active1@foo.com", state="active") service = Service( name="service_name", @@ -1432,7 +1434,98 @@ def test_dao_fetch_todays_total_message_count_returns_0_with_yesterday_messages( notification = save_notification( create_notification( created_at=yesterday, - template=create_template(service=create_service(service_name="tester"), template_type="email"), + template=create_template(service=create_service(service_name="tester123"), template_type="email"), ) ) assert fetch_todays_total_message_count(notification.service.id) == 0 + + def test_dao_fetch_todays_total_message_count_counts_notifications_in_jobs_scheduled_for_today( + self, notify_db, notify_db_session + ): + service = create_service(service_name="tester12") + template = create_template(service=service, template_type="email") + today = datetime.utcnow().date() + + create_sample_job( + notify_db, + notify_db_session, + service=service, + template=template, + scheduled_for=today, + job_status=JOB_STATUS_SCHEDULED, + notification_count=10, + ) + save_notification( + create_notification( + created_at=today, + template=template, + ) + ) + assert fetch_todays_total_message_count(service.id) == 11 + + def test_dao_fetch_todays_total_message_count_counts_notifications_in_jobs_scheduled_for_today_but_not_after_today( + self, notify_db, notify_db_session + ): + service = create_service() + template = create_template(service=service, template_type="email") + today = datetime.utcnow().date() + + create_sample_job( + notify_db, + notify_db_session, + service=service, + template=template, + scheduled_for=today, + job_status=JOB_STATUS_SCHEDULED, + notification_count=10, + ) + save_notification( + create_notification( + created_at=today, + template=template, + ) + ) + create_sample_job( + notify_db, + notify_db_session, + service=service, + template=template, + scheduled_for=today + timedelta(days=1), + job_status=JOB_STATUS_SCHEDULED, + notification_count=10, + ) + + assert fetch_todays_total_message_count(service.id) == 11 + + def test_dao_fetch_todays_total_message_count_counts_notifications_in_jobs_scheduled_for_today_but_not_before_today( + self, notify_db, notify_db_session + ): + service = create_service() + template = create_template(service=service, template_type="email") + today = datetime.utcnow().date() + + create_sample_job( + notify_db, + notify_db_session, + service=service, + template=template, + scheduled_for=today, + job_status=JOB_STATUS_SCHEDULED, + notification_count=10, + ) + create_sample_job( + notify_db, + notify_db_session, + service=service, + template=template, + scheduled_for=today - timedelta(days=1), + job_status=JOB_STATUS_SCHEDULED, + notification_count=10, + ) + save_notification( + create_notification( + created_at=today, + template=template, + ) + ) + assert fetch_todays_total_message_count(service.id) == 11 diff --git a/tests/app/job/test_rest.py b/tests/app/job/test_rest.py index e184da8199..932e9c7834 100644 --- a/tests/app/job/test_rest.py +++ b/tests/app/job/test_rest.py @@ -451,6 +451,7 @@ def test_create_job_returns_404_if_template_does_not_exist(client, sample_servic "app.job.rest.get_job_metadata_from_s3", return_value={ "template_id": str(sample_service.id), + "valid": "True", }, ) data = { diff --git a/tests/app/notifications/rest/test_send_notification.py b/tests/app/notifications/rest/test_send_notification.py index 71f8c63ec6..a1cf1f0d52 100644 --- a/tests/app/notifications/rest/test_send_notification.py +++ b/tests/app/notifications/rest/test_send_notification.py @@ -466,7 +466,7 @@ def test_should_allow_api_call_if_under_day_limit_regardless_of_type( sms_template = create_sample_template(notify_db, notify_db_session, service=service) create_sample_notification(notify_db, notify_db_session, template=email_template, service=service) - data = {"to": sample_user.mobile_number, "template": str(sms_template.id)} + data = {"to": sample_user.mobile_number, "template": str(sms_template.id), "valid": "True"} auth_header = create_authorization_header(service_id=service.id) From c4489b802baad6f00b2b35b00ca3a8a29e893f47 Mon Sep 17 00:00:00 2001 From: William B <7444334+whabanks@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:38:51 -0400 Subject: [PATCH 32/33] Fix missing import (#2118) * Fix missing import - Added tests to cover a blind spot that caused this issue to slip by in the first place * Use service we already fetched not authenticated_service * Remove noqa comments --- app/job/rest.py | 6 ++-- tests/app/job/test_rest.py | 65 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/app/job/rest.py b/app/job/rest.py index 49990f2961..950f1554c9 100644 --- a/app/job/rest.py +++ b/app/job/rest.py @@ -185,12 +185,10 @@ def create_job(service_id): elif template.template_type == EMAIL_TYPE: check_email_daily_limit(service, len(list(recipient_csv.get_rows()))) - scheduled_for = datetime.fromisoformat(form.get("scheduled_for")) if form.get("scheduled_for") else None # noqa: F821 + scheduled_for = datetime.fromisoformat(data.get("scheduled_for")) if data.get("scheduled_for") else None if scheduled_for is None or not scheduled_for.date() > datetime.today().date(): - increment_email_daily_count_send_warnings_if_needed( - authenticated_service, len(list(recipient_csv.get_rows())) # noqa: F821 - ) + increment_email_daily_count_send_warnings_if_needed(service, len(list(recipient_csv.get_rows()))) data.update({"template_version": template.version}) diff --git a/tests/app/job/test_rest.py b/tests/app/job/test_rest.py index 932e9c7834..76f89ebdf9 100644 --- a/tests/app/job/test_rest.py +++ b/tests/app/job/test_rest.py @@ -125,6 +125,71 @@ def test_cancel_letter_job_does_not_call_cancel_if_can_letter_job_be_cancelled_r assert response["message"] == "Sorry, it's too late, letters have already been sent." +def test_create_unscheduled_email_job_increments_daily_count(client, mocker, sample_email_job, fake_uuid): + mocker.patch("app.celery.tasks.process_job.apply_async") + mocker.patch("app.job.rest.increment_email_daily_count_send_warnings_if_needed") + mocker.patch( + "app.job.rest.get_job_metadata_from_s3", + return_value={ + "template_id": sample_email_job.template_id, + "original_file_name": sample_email_job.original_file_name, + "notification_count": "1", + "valid": "True", + }, + ) + mocker.patch( + "app.job.rest.get_job_from_s3", + return_value="email address\r\nsome@email.com", + ) + mocker.patch("app.dao.services_dao.dao_fetch_service_by_id", return_value=sample_email_job.service) + data = { + "id": fake_uuid, + "created_by": str(sample_email_job.created_by.id), + } + path = "/service/{}/job".format(sample_email_job.service_id) + auth_header = create_authorization_header() + headers = [("Content-Type", "application/json"), auth_header] + + response = client.post(path, data=json.dumps(data), headers=headers) + + assert response.status_code == 201 + + app.celery.tasks.process_job.apply_async.assert_called_once_with(([str(fake_uuid)]), queue="job-tasks") + app.job.rest.increment_email_daily_count_send_warnings_if_needed.assert_called_once_with(sample_email_job.service, 1) + + +def test_create_future_not_same_day_scheduled_email_job_does_not_increment_daily_count( + client, mocker, sample_email_job, fake_uuid +): + scheduled_date = (datetime.utcnow() + timedelta(hours=36, minutes=59)).isoformat() + mocker.patch("app.celery.tasks.process_job.apply_async") + mocker.patch("app.job.rest.increment_email_daily_count_send_warnings_if_needed") + mocker.patch( + "app.job.rest.get_job_metadata_from_s3", + return_value={ + "template_id": sample_email_job.template_id, + "original_file_name": sample_email_job.original_file_name, + "notification_count": "1", + "valid": "True", + }, + ) + mocker.patch( + "app.job.rest.get_job_from_s3", + return_value="email address\r\nsome@email.com", + ) + mocker.patch("app.dao.services_dao.dao_fetch_service_by_id", return_value=sample_email_job.service) + data = {"id": fake_uuid, "created_by": str(sample_email_job.created_by.id), "scheduled_for": scheduled_date} + path = "/service/{}/job".format(sample_email_job.service_id) + auth_header = create_authorization_header() + headers = [("Content-Type", "application/json"), auth_header] + + response = client.post(path, data=json.dumps(data), headers=headers) + + assert response.status_code == 201 + + app.job.rest.increment_email_daily_count_send_warnings_if_needed.assert_not_called() + + def test_create_unscheduled_job(client, sample_template, mocker, fake_uuid): mocker.patch("app.celery.tasks.process_job.apply_async") mocker.patch( From 67203214b9f2cb876225180865ea2dd6d202d378 Mon Sep 17 00:00:00 2001 From: William B <7444334+whabanks@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:54:26 -0400 Subject: [PATCH 33/33] Update utils ref from branch to tag (#2119) --- poetry.lock | 185 ++----------------------------------------------- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 180 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5482ec4aa3..cd2b5ca1ba 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.9.1" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -101,7 +100,6 @@ speedups = ["Brotli", "aiodns", "brotlicffi"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -116,7 +114,6 @@ frozenlist = ">=1.1.0" name = "alembic" version = "1.12.1" description = "A database migration tool for SQLAlchemy." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -136,7 +133,6 @@ tz = ["python-dateutil"] name = "amqp" version = "5.2.0" description = "Low-level AMQP client for Python (fork of amqplib)." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -151,7 +147,6 @@ vine = ">=5.0.0,<6.0.0" name = "annotated-types" version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -163,7 +158,6 @@ files = [ name = "apig-wsgi" version = "2.18.0" description = "Wrap a WSGI application in an AWS Lambda handler function for running on API Gateway or an ALB." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -175,7 +169,6 @@ files = [ name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -187,7 +180,6 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -206,7 +198,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "aws-embedded-metrics" version = "1.0.8" description = "AWS Embedded Metrics Package" -category = "main" optional = false python-versions = "*" files = [ @@ -221,7 +212,6 @@ aiohttp = "*" name = "awscli" version = "1.32.25" description = "Universal Command Line Environment for AWS." -category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -241,7 +231,6 @@ s3transfer = ">=0.10.0,<0.11.0" name = "awscli-cwlogs" version = "1.4.6" description = "AWSCLI CloudWatch Logs plugin" -category = "main" optional = false python-versions = "*" files = [ @@ -258,7 +247,6 @@ six = ">=1.1.0" name = "bcrypt" version = "4.1.1" description = "Modern password hashing for your software and your servers" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -289,7 +277,6 @@ typecheck = ["mypy"] name = "billiard" version = "4.2.0" description = "Python multiprocessing fork with improvements and bugfixes" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -301,7 +288,6 @@ files = [ name = "black" version = "23.7.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -347,7 +333,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "bleach" version = "6.0.0" description = "An easy safelist-based HTML-sanitizing tool." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -366,7 +351,6 @@ css = ["tinycss2 (>=1.1.0,<1.2)"] name = "blinker" version = "1.7.0" description = "Fast, simple object-to-object and broadcast signaling" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -378,7 +362,6 @@ files = [ name = "boto" version = "2.49.0" description = "Amazon Web Services Library" -category = "main" optional = false python-versions = "*" files = [ @@ -390,7 +373,6 @@ files = [ name = "boto3" version = "1.34.25" description = "The AWS SDK for Python" -category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -410,7 +392,6 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] name = "botocore" version = "1.34.25" description = "Low-level, data-driven core of boto 3." -category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -430,7 +411,6 @@ crt = ["awscrt (==0.19.19)"] name = "brotli" version = "1.1.0" description = "Python bindings for the Brotli compression library" -category = "dev" optional = false python-versions = "*" files = [ @@ -523,7 +503,6 @@ files = [ name = "cachelib" version = "0.10.2" description = "A collection of cache libraries in the same API interface." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -535,7 +514,6 @@ files = [ name = "cachetools" version = "4.2.4" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = "~=3.5" files = [ @@ -547,7 +525,6 @@ files = [ name = "celery" version = "5.3.6" description = "Distributed Task Queue." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -609,7 +586,6 @@ zstd = ["zstandard (==0.22.0)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -621,7 +597,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -698,7 +673,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -798,7 +772,6 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -813,7 +786,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "click-datetime" version = "0.2" description = "Datetime type support for click." -category = "main" optional = false python-versions = "*" files = [ @@ -831,7 +803,6 @@ dev = ["wheel"] name = "click-didyoumean" version = "0.3.0" description = "Enables git-like *did-you-mean* feature in click" -category = "main" optional = false python-versions = ">=3.6.2,<4.0.0" files = [ @@ -846,7 +817,6 @@ click = ">=7" name = "click-plugins" version = "1.1.1" description = "An extension module for click to enable registering CLI commands via setuptools entry-points." -category = "main" optional = false python-versions = "*" files = [ @@ -864,7 +834,6 @@ dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] name = "click-repl" version = "0.3.0" description = "REPL plugin for Click" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -883,7 +852,6 @@ testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -895,7 +863,6 @@ files = [ 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." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -911,7 +878,6 @@ yaml = ["PyYAML"] name = "coverage" version = "5.5" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" files = [ @@ -979,7 +945,6 @@ toml = ["toml"] name = "coveralls" version = "3.3.1" description = "Show coverage stats online via coveralls.io" -category = "dev" optional = false python-versions = ">= 3.5" files = [ @@ -988,7 +953,7 @@ files = [ ] [package.dependencies] -coverage = ">=4.1,<6.0.0 || >6.1,<6.1.1 || >6.1.1,<7.0" +coverage = ">=4.1,<6.0.dev0 || >6.1,<6.1.1 || >6.1.1,<7.0" docopt = ">=0.6.1" requests = ">=1.0.0" @@ -999,7 +964,6 @@ yaml = ["PyYAML (>=3.10)"] name = "cryptography" version = "42.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1054,7 +1018,6 @@ test-randomorder = ["pytest-randomly"] name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" -category = "main" optional = false python-versions = "*" files = [ @@ -1065,7 +1028,6 @@ files = [ name = "docutils" version = "0.16" description = "Docutils -- Python Documentation Utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1077,7 +1039,6 @@ files = [ name = "environs" version = "9.5.0" description = "simplified environment variable parsing" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1099,7 +1060,6 @@ tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] name = "exceptiongroup" version = "1.2.0" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1114,7 +1074,6 @@ test = ["pytest (>=6)"] name = "execnet" version = "2.0.2" description = "execnet: rapid multi-Python deployment" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1129,7 +1088,6 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] name = "fido2" version = "0.9.3" description = "Python based FIDO 2.0 library" -category = "main" optional = false python-versions = ">=2.7.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -1147,7 +1105,6 @@ pcsc = ["pyscard"] name = "filelock" version = "3.13.1" description = "A platform independent file lock." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1164,7 +1121,6 @@ typing = ["typing-extensions (>=4.8)"] name = "flake8" version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -1181,7 +1137,6 @@ pyflakes = ">=3.1.0,<3.2.0" name = "flask" version = "2.3.3" description = "A simple framework for building complex web applications." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1204,7 +1159,6 @@ dotenv = ["python-dotenv"] name = "flask-basicauth" version = "0.2.0" description = "HTTP basic access authentication for Flask." -category = "dev" optional = false python-versions = "*" files = [ @@ -1218,7 +1172,6 @@ Flask = "*" name = "flask-bcrypt" version = "1.0.1" description = "Brcrypt hashing for Flask." -category = "main" optional = false python-versions = "*" files = [ @@ -1234,7 +1187,6 @@ Flask = "*" name = "flask-cors" version = "4.0.0" description = "A Flask extension adding a decorator for CORS support" -category = "dev" optional = false python-versions = "*" files = [ @@ -1249,7 +1201,6 @@ Flask = ">=0.9" name = "flask-marshmallow" version = "0.14.0" description = "Flask + marshmallow for beautiful APIs" -category = "main" optional = false python-versions = "*" files = [ @@ -1273,7 +1224,6 @@ tests = ["flask-sqlalchemy", "marshmallow-sqlalchemy (>=0.13.0)", "marshmallow-s name = "flask-migrate" version = "2.7.0" description = "SQLAlchemy database migrations for Flask applications using Alembic" -category = "main" optional = false python-versions = "*" files = [ @@ -1290,7 +1240,6 @@ Flask-SQLAlchemy = ">=1.0" name = "flask-redis" version = "0.4.0" description = "A nice way to use Redis in your Flask app" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1310,7 +1259,6 @@ tests = ["coverage", "pytest", "pytest-mock"] name = "Flask-SQLAlchemy" version = "2.3.2.dev20231128" description = "Adds SQLAlchemy support to your Flask application" -category = "main" optional = false python-versions = "*" files = [] @@ -1330,7 +1278,6 @@ resolved_reference = "500e732dd1b975a56ab06a46bd1a20a21e682262" name = "freezegun" version = "1.2.2" description = "Let your Python tests travel through time" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1345,7 +1292,6 @@ python-dateutil = ">=2.7" name = "frozenlist" version = "1.4.0" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1416,7 +1362,6 @@ files = [ name = "future" version = "0.18.3" description = "Clean single-source support for Python 3 and 2" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1427,7 +1372,6 @@ files = [ name = "gevent" version = "23.9.1" description = "Coroutine-based network library" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1490,7 +1434,6 @@ test = ["cffi (>=1.12.2)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idn name = "geventhttpclient" version = "2.0.11" description = "http client library for gevent" -category = "dev" optional = false python-versions = "*" files = [ @@ -1614,7 +1557,6 @@ six = "*" name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -1692,7 +1634,6 @@ test = ["objgraph", "psutil"] name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1713,7 +1654,6 @@ tornado = ["tornado (>=0.2)"] name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1725,7 +1665,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1737,7 +1676,6 @@ files = [ name = "iso8601" version = "2.0.0" description = "Simple module to parse ISO 8601 dates" -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -1749,7 +1687,6 @@ files = [ name = "isodate" version = "0.6.1" description = "An ISO 8601 date/time/duration parser and formatter" -category = "main" optional = false python-versions = "*" files = [ @@ -1764,7 +1701,6 @@ six = "*" name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1782,7 +1718,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1794,7 +1729,6 @@ files = [ name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1812,7 +1746,6 @@ i18n = ["Babel (>=2.7)"] name = "jinja2-cli" version = "0.8.2" description = "A CLI interface to Jinja2" -category = "dev" optional = false python-versions = "*" files = [ @@ -1834,7 +1767,6 @@ yaml = ["jinja2", "pyyaml"] name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1846,7 +1778,6 @@ files = [ name = "jsonschema" version = "3.2.0" description = "An implementation of JSON Schema validation for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -1868,7 +1799,6 @@ format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-va name = "kombu" version = "5.3.4" description = "Messaging library for Python." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1904,7 +1834,6 @@ zookeeper = ["kazoo (>=2.8.0)"] name = "locust" version = "2.16.1" description = "Developer friendly load testing framework" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1932,7 +1861,6 @@ Werkzeug = ">=2.0.0" name = "lxml" version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ @@ -2040,7 +1968,6 @@ source = ["Cython (>=0.29.35)"] name = "mako" version = "1.3.0" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2060,7 +1987,6 @@ testing = ["pytest"] name = "markupsafe" version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2130,7 +2056,6 @@ files = [ name = "marshmallow" version = "3.20.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2151,7 +2076,6 @@ tests = ["pytest", "pytz", "simplejson"] name = "marshmallow-sqlalchemy" version = "0.29.0" description = "SQLAlchemy integration with the marshmallow (de)serialization library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2174,7 +2098,6 @@ tests = ["pytest", "pytest-lazy-fixture (>=0.6.2)"] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2186,7 +2109,6 @@ files = [ name = "mistune" version = "0.8.4" description = "The fastest markdown parser in pure Python" -category = "main" optional = false python-versions = "*" files = [ @@ -2198,7 +2120,6 @@ files = [ name = "more-itertools" version = "8.14.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2210,7 +2131,6 @@ files = [ name = "moto" version = "4.1.11" description = "" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2257,7 +2177,6 @@ xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"] name = "msgpack" version = "1.0.7" description = "MessagePack serializer" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2323,7 +2242,6 @@ files = [ name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2407,7 +2325,6 @@ files = [ name = "mypy" version = "1.5.0" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2449,7 +2366,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2461,7 +2377,6 @@ files = [ name = "nanoid" version = "2.0.0" description = "A tiny, secure, URL-friendly, unique string ID generator for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -2473,7 +2388,6 @@ files = [ name = "networkx" version = "2.8.8" description = "Python package for creating and manipulating graphs and networks" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2492,7 +2406,6 @@ test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] name = "newrelic" version = "6.10.0.165" description = "New Relic Python Agent" -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" files = [ @@ -2518,7 +2431,6 @@ infinite-tracing = ["grpcio (<2)", "protobuf (<4)"] name = "notifications-python-client" version = "6.4.1" description = "Python API client for GOV.UK Notify." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2534,7 +2446,6 @@ requests = ">=2.0.0" name = "notifications-utils" version = "52.1.3" description = "Shared python code for Notification - Provides logging utils etc." -category = "main" optional = false python-versions = "~3.10" files = [] @@ -2568,14 +2479,13 @@ werkzeug = "2.3.7" [package.source] type = "git" url = "https://github.com/cds-snc/notifier-utils.git" -reference = "upgrade-cryptography" -resolved_reference = "ac0422352576898b51325f914cf98d18e66f0bb3" +reference = "52.1.3" +resolved_reference = "06a40db6286f525fe3551e029418458d33342592" [[package]] name = "ordered-set" version = "4.1.0" description = "An OrderedSet is a custom MutableSet that remembers its order, so that every" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2590,7 +2500,6 @@ dev = ["black", "mypy", "pytest"] name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2602,7 +2511,6 @@ files = [ name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2614,7 +2522,6 @@ files = [ name = "pendulum" version = "2.1.2" description = "Python datetimes made easy" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2649,7 +2556,6 @@ pytzdata = ">=2020.1" name = "phonenumbers" version = "8.13.28" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." -category = "main" optional = false python-versions = "*" files = [ @@ -2661,7 +2567,6 @@ files = [ name = "platformdirs" version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2677,7 +2582,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2693,7 +2597,6 @@ testing = ["pytest", "pytest-benchmark"] name = "prompt-toolkit" version = "3.0.41" description = "Library for building powerful interactive command lines in Python" -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -2708,7 +2611,6 @@ wcwidth = "*" name = "psutil" version = "5.9.6" description = "Cross-platform lib for process and system monitoring in Python." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -2737,7 +2639,6 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] name = "psycopg2-binary" version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2819,7 +2720,6 @@ files = [ name = "pwnedpasswords" version = "2.0.0" description = "A Python wrapper for Troy Hunt's Pwned Passwords API." -category = "main" optional = false python-versions = "*" files = [ @@ -2833,7 +2733,6 @@ future = "*" name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2845,7 +2744,6 @@ files = [ name = "py-w3c" version = "0.3.1" description = "W3C services for python." -category = "main" optional = false python-versions = "*" files = [ @@ -2856,7 +2754,6 @@ files = [ name = "pyasn1" version = "0.5.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -2868,7 +2765,6 @@ files = [ name = "pycodestyle" version = "2.11.1" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2880,7 +2776,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2892,7 +2787,6 @@ files = [ name = "pycurl" version = "7.45.2" description = "PycURL -- A Python Interface To The cURL library" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2903,7 +2797,6 @@ files = [ name = "pydantic" version = "2.5.2" description = "Data validation using Python type hints" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2923,7 +2816,6 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.14.5" description = "" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3041,7 +2933,6 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pyflakes" version = "3.1.0" description = "passive checker of Python programs" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3053,7 +2944,6 @@ files = [ name = "pyjwt" version = "2.8.0" description = "JSON Web Token implementation in Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3071,7 +2961,6 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] name = "pypdf2" version = "1.28.6" description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" -category = "main" optional = false python-versions = ">=2.7" files = [ @@ -3083,7 +2972,6 @@ files = [ name = "pyrsistent" version = "0.20.0" description = "Persistent/Functional/Immutable data structures" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3125,7 +3013,6 @@ files = [ name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3148,7 +3035,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-cov" version = "3.0.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3167,7 +3053,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-env" version = "0.8.2" description = "py.test plugin that allows you to add environment variables." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3185,7 +3070,6 @@ test = ["coverage (>=7.2.7)", "pytest-mock (>=3.10)"] name = "pytest-forked" version = "1.6.0" description = "run tests in isolated forked subprocesses" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3201,7 +3085,6 @@ pytest = ">=3.10" name = "pytest-mock" version = "3.11.1" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3219,7 +3102,6 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "pytest-mock-resources" version = "2.9.2" description = "A pytest plugin for easily instantiating reproducible mock resources." -category = "dev" optional = false python-versions = ">=3.7,<4" files = [ @@ -3250,7 +3132,6 @@ redshift = ["boto3", "filelock", "moto", "python-on-whales (>=0.22.0)", "sqlpars name = "pytest-xdist" version = "2.5.0" description = "pytest xdist plugin for distributed testing and loop-on-failing modes" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3272,7 +3153,6 @@ testing = ["filelock"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -3287,7 +3167,6 @@ six = ">=1.5" name = "python-dotenv" version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3302,7 +3181,6 @@ cli = ["click (>=5.0)"] name = "python-json-logger" version = "2.0.7" description = "A python library adding a json log formatter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3314,7 +3192,6 @@ files = [ name = "python-magic" version = "0.4.27" description = "File type identification using libmagic" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -3326,7 +3203,6 @@ files = [ name = "python-on-whales" version = "0.67.0" description = "A Docker client for Python, designed to be fun and intuitive!" -category = "dev" optional = false python-versions = "<4,>=3.8" files = [ @@ -3335,7 +3211,7 @@ files = [ ] [package.dependencies] -pydantic = ">=1.9,<2.0.0 || >=2.1.0,<3" +pydantic = ">=1.9,<2.0.dev0 || >=2.1.dev0,<3" requests = "*" tqdm = "*" typer = ">=0.4.1" @@ -3348,7 +3224,6 @@ test = ["pytest"] name = "pytz" version = "2021.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -3360,7 +3235,6 @@ files = [ name = "pytzdata" version = "2020.1" description = "The Olson timezone database for Python." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3372,7 +3246,6 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" -category = "dev" optional = false python-versions = "*" files = [ @@ -3396,7 +3269,6 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3457,7 +3329,6 @@ files = [ name = "pyzmq" version = "25.1.1" description = "Python bindings for 0MQ" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3563,7 +3434,6 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} name = "redis" version = "5.0.1" description = "Python client for Redis database and key-value store" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3582,7 +3452,6 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)" name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3604,7 +3473,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-file" version = "1.5.1" description = "File transport adapter for Requests" -category = "main" optional = false python-versions = "*" files = [ @@ -3620,7 +3488,6 @@ six = "*" name = "requests-mock" version = "1.11.0" description = "Mock out responses from the requests package" -category = "dev" optional = false python-versions = "*" files = [ @@ -3640,7 +3507,6 @@ test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "tes name = "requests-toolbelt" version = "1.0.0" description = "A utility belt for advanced users of python-requests" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -3655,7 +3521,6 @@ requests = ">=2.0.1,<3.0.0" name = "responses" version = "0.24.1" description = "A utility library for mocking out the `requests` Python library." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3675,7 +3540,6 @@ tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asy name = "rfc3987" version = "1.3.8" description = "Parsing and validation of URIs (RFC 3986) and IRIs (RFC 3987)" -category = "dev" optional = false python-versions = "*" files = [ @@ -3687,7 +3551,6 @@ files = [ name = "roundrobin" version = "0.0.4" description = "Collection of roundrobin utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -3698,7 +3561,6 @@ files = [ name = "rsa" version = "4.7.2" description = "Pure-Python RSA implementation" -category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -3713,7 +3575,6 @@ pyasn1 = ">=0.1.3" name = "s3transfer" version = "0.10.0" description = "An Amazon S3 Transfer Manager" -category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -3731,7 +3592,6 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] name = "setuptools" version = "69.0.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3748,7 +3608,6 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "simple-salesforce" version = "1.12.5" description = "A basic Salesforce.com REST API client." -category = "main" optional = false python-versions = "*" files = [ @@ -3768,7 +3627,6 @@ zeep = "*" name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3780,7 +3638,6 @@ files = [ name = "smartypants" version = "2.0.1" description = "Python with the SmartyPants" -category = "main" optional = false python-versions = "*" files = [ @@ -3791,7 +3648,6 @@ files = [ name = "sqlalchemy" version = "1.4.51" description = "Database Abstraction Library" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -3871,7 +3727,6 @@ sqlcipher = ["sqlcipher3_binary"] name = "sqlalchemy-stubs" version = "0.4" description = "SQLAlchemy stubs and mypy plugin" -category = "dev" optional = false python-versions = "*" files = [ @@ -3887,7 +3742,6 @@ typing-extensions = ">=3.7.4" name = "sqlalchemy2-stubs" version = "0.0.2a38" description = "Typing Stubs for SQLAlchemy 1.4" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3902,7 +3756,6 @@ typing-extensions = ">=3.7.4" name = "statsd" version = "3.3.0" description = "A simple statsd client." -category = "main" optional = false python-versions = "*" files = [ @@ -3914,7 +3767,6 @@ files = [ name = "strict-rfc3339" version = "0.7" description = "Strict, simple, lightweight RFC3339 functions" -category = "dev" optional = false python-versions = "*" files = [ @@ -3925,7 +3777,6 @@ files = [ name = "tldextract" version = "3.4.4" description = "Accurately separates a URL's subdomain, domain, and public suffix, using the Public Suffix List (PSL). By default, this includes the public ICANN TLDs and their exceptions. You can optionally support the Public Suffix List's private domains as well." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3943,7 +3794,6 @@ requests-file = ">=1.4" name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3955,7 +3805,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3967,7 +3816,6 @@ files = [ name = "tqdm" version = "4.66.1" description = "Fast, Extensible Progress Meter" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3988,7 +3836,6 @@ telegram = ["requests"] name = "typer" version = "0.9.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -4010,7 +3857,6 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. name = "types-boto" version = "2.49.18.9" description = "Typing stubs for boto" -category = "dev" optional = false python-versions = "*" files = [ @@ -4022,7 +3868,6 @@ files = [ name = "types-mock" version = "4.0.15.2" description = "Typing stubs for mock" -category = "dev" optional = false python-versions = "*" files = [ @@ -4034,7 +3879,6 @@ files = [ name = "types-pyopenssl" version = "23.3.0.0" description = "Typing stubs for pyOpenSSL" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -4049,7 +3893,6 @@ cryptography = ">=35.0.0" name = "types-python-dateutil" version = "2.8.19.20240106" description = "Typing stubs for python-dateutil" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4061,7 +3904,6 @@ files = [ name = "types-pytz" version = "2022.7.1.2" description = "Typing stubs for pytz" -category = "dev" optional = false python-versions = "*" files = [ @@ -4073,7 +3915,6 @@ files = [ name = "types-redis" version = "4.6.0.20240106" description = "Typing stubs for redis" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4089,7 +3930,6 @@ types-pyOpenSSL = "*" name = "types-requests" version = "2.31.0.20240106" description = "Typing stubs for requests" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -4104,7 +3944,6 @@ urllib3 = ">=2" name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4116,7 +3955,6 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -4128,7 +3966,6 @@ files = [ name = "unidecode" version = "1.3.8" description = "ASCII transliterations of Unicode text" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -4140,7 +3977,6 @@ files = [ name = "urllib3" version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4158,7 +3994,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "vine" version = "5.1.0" description = "Python promises." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -4170,7 +4005,6 @@ files = [ name = "wcwidth" version = "0.2.12" description = "Measures the displayed width of unicode strings in a terminal" -category = "main" optional = false python-versions = "*" files = [ @@ -4182,7 +4016,6 @@ files = [ name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" -category = "main" optional = false python-versions = "*" files = [ @@ -4194,7 +4027,6 @@ files = [ name = "werkzeug" version = "2.3.7" description = "The comprehensive WSGI web application library." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -4212,7 +4044,6 @@ watchdog = ["watchdog (>=2.3)"] name = "xmltodict" version = "0.13.0" description = "Makes working with XML feel like you are working with JSON" -category = "dev" optional = false python-versions = ">=3.4" files = [ @@ -4224,7 +4055,6 @@ files = [ name = "yarl" version = "1.9.3" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4328,7 +4158,6 @@ multidict = ">=4.0" name = "zeep" version = "4.2.1" description = "A Python SOAP client" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4356,7 +4185,6 @@ xmlsec = ["xmlsec (>=0.6.1)"] name = "zope-event" version = "5.0" description = "Very basic event publishing system" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4375,7 +4203,6 @@ test = ["zope.testrunner"] name = "zope-interface" version = "6.1" description = "Interfaces for Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -4428,4 +4255,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "~3.10.9" -content-hash = "e7aced65ef6042df1c2ea1d4f4910a22767a8a521c3f1cf0bf0b191c41b2208a" +content-hash = "f2bf5c58fe6d2689072e7b9d4cf91976e07e76ade98dc3153977c4377b98c86e" diff --git a/pyproject.toml b/pyproject.toml index e92314b4c8..609ff16d41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ Werkzeug = "2.3.7" MarkupSafe = "2.1.4" # REVIEW: v2 is using sha512 instead of sha1 by default (in v1) itsdangerous = "2.1.2" -notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", branch = "upgrade-cryptography" } +notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", tag = "52.1.3" } # rsa = "4.9 # awscli 1.22.38 depends on rsa<4.8 typing-extensions = "4.7.1" greenlet = "2.0.2"