From 63d96d46b83d9c215197ae477ec7c7eb96f2f734 Mon Sep 17 00:00:00 2001 From: Jonathan Bobel Date: Wed, 7 May 2025 11:08:03 -0400 Subject: [PATCH 01/14] 2554 - Adding cancel button to Send Message flow --- app/assets/javascripts/notifyModal.js | 91 ++++++++ .../preventDuplicateFormSubmissions.js | 42 ++-- app/assets/js/init.uswds.js | 8 + app/assets/sass/uswds/_main.scss | 4 + app/templates/base.html | 4 +- .../components/components/button/template.njk | 42 ++-- app/templates/views/check/ok.html | 4 +- app/templates/views/notifications/check.html | 6 +- .../views/notifications/preview.html | 49 ++++- app/templates/views/platform-admin/index.html | 2 +- gulpfile.js | 12 +- poetry.lock | 194 ++---------------- tests/javascripts/timeoutPopup.test.js | 2 +- 13 files changed, 238 insertions(+), 222 deletions(-) create mode 100644 app/assets/javascripts/notifyModal.js create mode 100644 app/assets/js/init.uswds.js diff --git a/app/assets/javascripts/notifyModal.js b/app/assets/javascripts/notifyModal.js new file mode 100644 index 000000000..bdf7fa48a --- /dev/null +++ b/app/assets/javascripts/notifyModal.js @@ -0,0 +1,91 @@ +let activeModal = null; +let lastFocusedElement = null; + +function openModal(modalId) { + const wrapper = document.getElementById(modalId); + if (!wrapper) return; + + const modal = wrapper.querySelector('.usa-modal, dialog'); + if (!modal) return; + + lastFocusedElement = document.activeElement; + + wrapper.classList.remove('is-hidden'); + modal.removeAttribute('aria-hidden'); + modal.removeAttribute('inert'); + modal.removeAttribute('hidden'); + document.body.classList.add('modal-open'); + + + // Set focus to the first focusable element inside modal + const focusTarget = modal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); + if (focusTarget) focusTarget.focus(); + + modal.addEventListener('keydown', function(e) { + if (e.key !== 'Tab') return; + + const focusableElements = modal.querySelectorAll( + 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"])' + ); + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + + if (e.shiftKey && document.activeElement === firstElement) { + e.preventDefault(); + lastElement.focus(); + } else if (!e.shiftKey && document.activeElement === lastElement) { + e.preventDefault(); + firstElement.focus(); + } + }); + + activeModal = wrapper; +} + +function closeModal() { + if (!activeModal) return; + + const modal = activeModal.querySelector('.usa-modal, dialog'); + if (modal) { + modal.setAttribute('aria-hidden', 'true'); + modal.setAttribute('inert', ''); + modal.setAttribute('hidden', ''); + } + + activeModal.classList.add('is-hidden'); + document.body.classList.remove('modal-open'); + + if (lastFocusedElement) lastFocusedElement.focus(); + + activeModal = null; + +} + +// Attach open triggers +document.querySelectorAll('[data-open-modal]').forEach(btn => { + btn.addEventListener('click', () => { + const modalId = btn.getAttribute('data-open-modal'); + openModal(modalId); + }); +}); + +// Attach close triggers +document.querySelectorAll('[data-close-modal]').forEach(btn => { + btn.addEventListener('click', () => { + closeModal(); + }); +}); + +// Escape key closes modal +document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && activeModal) { + closeModal(); + } +}); + +// Optional: click outside modal closes it +document.addEventListener('click', (e) => { + if (activeModal && e.target.classList.contains('usa-modal-overlay')) { + closeModal(); + } +}); diff --git a/app/assets/javascripts/preventDuplicateFormSubmissions.js b/app/assets/javascripts/preventDuplicateFormSubmissions.js index e3221d9d2..4c4b97252 100644 --- a/app/assets/javascripts/preventDuplicateFormSubmissions.js +++ b/app/assets/javascripts/preventDuplicateFormSubmissions.js @@ -1,37 +1,39 @@ -(function() { - +(function () { "use strict"; - let disableSubmitButtons = function(event) { - - var $submitButton = $(this).find(':submit'); - - if ($submitButton.data('clicked') == 'true') { + const disableSubmitButtons = function (event) { + const $submitButton = $(this).find(':submit'); + if ($submitButton.data('clicked') === 'true') { event.preventDefault(); + return; + } - } else { + $submitButton.data('clicked', 'true'); - $submitButton.data('clicked', 'true'); + // Add dot animation for Send/Schedule/Cancel buttons + const buttonName = $submitButton.attr('name')?.toLowerCase(); + if (["send", "schedule", "cancel"].includes(buttonName)) { + $submitButton.prop('disabled', true); - if ($submitButton.is('[name="Send"], [name="Schedule"]')) { - $submitButton.prop('disabled', true); - - setTimeout(() => { - renableSubmitButton($submitButton); - }, 10000); - } else { - setTimeout(renableSubmitButton($submitButton), 1500); + // Inject dot animation span if not already present + if ($submitButton.find('.dot-anim').length === 0) { + $submitButton.append(''); } + + setTimeout(() => { + renableSubmitButton($submitButton); + }, 10000); // fallback safety + } else { + setTimeout(renableSubmitButton($submitButton), 1500); } }; - let renableSubmitButton = $submitButton => () => { - + const renableSubmitButton = ($submitButton) => () => { $submitButton.data('clicked', ''); $submitButton.prop('disabled', false); + $submitButton.find('.dot-anim').remove(); // clean up if needed }; $('form').on('submit', disableSubmitButtons); - })(); diff --git a/app/assets/js/init.uswds.js b/app/assets/js/init.uswds.js new file mode 100644 index 000000000..688fff06f --- /dev/null +++ b/app/assets/js/init.uswds.js @@ -0,0 +1,8 @@ +document.addEventListener("DOMContentLoaded", function () { + if (window.uswds && typeof window.uswds.init === 'function') { + console.log("Calling USWDS init"); + window.uswds.init(); + } else { + console.error("USWDS not found or init is not a function"); + } + }); diff --git a/app/assets/sass/uswds/_main.scss b/app/assets/sass/uswds/_main.scss index 88e44042d..61e319f27 100644 --- a/app/assets/sass/uswds/_main.scss +++ b/app/assets/sass/uswds/_main.scss @@ -363,3 +363,7 @@ h2.recipient-list { 66% { content: '..'; } 100% { content: '...'; } } + +.modal-open { + overflow: hidden; +} diff --git a/app/templates/base.html b/app/templates/base.html index c75fae406..ef8ca8998 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -140,7 +140,7 @@
-

+

Your session will end soon. Please choose to extend your session or sign out. Your session will expire in 5 minutes or less.

@@ -176,7 +176,7 @@ {% block extra_javascripts %} {% endblock %} - + {% endblock %} diff --git a/app/templates/components/components/button/template.njk b/app/templates/components/components/button/template.njk index 01bc649ac..cac174879 100644 --- a/app/templates/components/components/button/template.njk +++ b/app/templates/components/components/button/template.njk @@ -1,5 +1,4 @@ {# Determine type of element to use, if not explicitly set -#} - {% if params.element %} {% set element = params.element | lower %} {% else %} @@ -10,26 +9,35 @@ {% endif %} {% endif %} -{#- Define common attributes that we can use across all element types #} - +{# Define common attributes to use across all element types -#} {%- set commonAttributes %} class="usa-button{% if params.classes %} {{ params.classes }}{% endif %}{% if params.disabled %} usa-button--disabled{% endif %}"{% for attribute, value in params.attributes %} {{attribute}}="{{value}}"{% endfor %}{% endset %} -{#- Define common attributes we can use for both button and input types #} - +{# Define attributes for button/input -#} {%- set buttonAttributes %}{% if params.name %} name="{{ params.name | trim }}"{% endif %} type="{{ params.type if params.type else 'submit' }}"{% if params.disabled %} disabled="disabled" aria-disabled="true"{% endif %}{% if params.preventDoubleClick %} data-prevent-double-click="true"{% endif %}{% endset %} -{#- Actually create a button... or a link! #} - -{%- if element == 'a' %} - +{# Auto-append .dot-anim span for Send/Schedule buttons -#} +{%- set isSendOrSchedule = params.name and (params.name | lower == 'send' or params.name | lower == 'schedule') -%} +{%- set textContent %} {{ params.html | safe if params.html else params.text }} - + {%- if isSendOrSchedule -%}{%- endif -%} +{%- endset %} -{%- elseif element == 'button' %} - +{# Render the appropriate element -#} +{% if element == 'a' %} + + {{ textContent | safe }} + -{%- elseif element == 'input' %} - -{%- endif %} +{% elseif element == 'button' %} + + +{% elseif element == 'input' %} + +{% endif %} diff --git a/app/templates/views/check/ok.html b/app/templates/views/check/ok.html index 6da7546e2..aeb7df6f3 100644 --- a/app/templates/views/check/ok.html +++ b/app/templates/views/check/ok.html @@ -27,7 +27,7 @@ {% if choose_time_form %} {{ choose_time_form.scheduled_for(param_extensions={ - 'formGroup': {'classes': 'bottom-gutter-2-3'}, + 'formGroup': {'classes': ''}, 'attributes': { 'data-module': 'radio-select', 'data-categories': choose_time_form.scheduled_for.categories|join(','), @@ -39,7 +39,7 @@ {% set button_text %} Preview {% endset %} - {{ usaButton({ "text": button_text }) }} + {{ usaButton({ "text": "button_text", "classes": "margin-top-4" }) }}
diff --git a/app/templates/views/notifications/check.html b/app/templates/views/notifications/check.html index 6566c395c..1e9e347f0 100644 --- a/app/templates/views/notifications/check.html +++ b/app/templates/views/notifications/check.html @@ -55,7 +55,7 @@ {% if not error %} {% if choose_time_form %} {{ choose_time_form.scheduled_for(param_extensions={ - 'formGroup': {'classes': 'bottom-gutter-2-3'}, + 'formGroup': {'classes': ''}, 'attributes': { 'data-module': 'radio-select', 'data-categories': choose_time_form.scheduled_for.categories|join(','), @@ -66,8 +66,8 @@ {% set button_text %} Preview {% endset %} - {{ usaButton({ "text": button_text }) }} - {% endif %} + {{ usaButton({ "text": button_text, "classes": "margin-top-2" }) }} + {% endif %}
diff --git a/app/templates/views/notifications/preview.html b/app/templates/views/notifications/preview.html index c870ba539..29c4d6aa6 100644 --- a/app/templates/views/notifications/preview.html +++ b/app/templates/views/notifications/preview.html @@ -76,7 +76,7 @@ help='3' if help else 0 )}}" class='page-footer'> -

Does everything look good?

+

Does everything look good?

{% if not error %} {% set button_text %} {{ "Schedule" if scheduled_for else 'Send'}} @@ -84,9 +84,54 @@ {{ usaButton({ "text": button_text, "name": button_text - }) }} + }) }} + {{ usaButton({ + "text": "Cancel", + "name": "Cancel", + "classes": "usa-button--secondary", + "type": "button", + "attributes": { + "data-open-modal": "cancelModal" + } + }) }} {% endif %} + + {% endblock %} diff --git a/app/templates/views/platform-admin/index.html b/app/templates/views/platform-admin/index.html index a90da7224..abb1838cf 100644 --- a/app/templates/views/platform-admin/index.html +++ b/app/templates/views/platform-admin/index.html @@ -32,7 +32,7 @@
{% for noti_type in global_stats %}
- + {{ "{:,}".format(noti_type.black_box.number) }} diff --git a/gulpfile.js b/gulpfile.js index 578c1995b..6665b80e4 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -50,7 +50,7 @@ const javascripts = () => { paths.npm + 'textarea-caret/index.js', paths.npm + 'cbor-js/cbor.js', paths.npm + 'd3/dist/d3.min.js', - paths.npm + 'socket.io-client/dist/socket.io.min.js', + paths.npm + 'socket.io-client/dist/socket.io.min.js' ]) ); @@ -72,6 +72,7 @@ const javascripts = () => { paths.src + 'javascripts/radioSlider.js', paths.src + 'javascripts/updateStatus.js', paths.src + 'javascripts/errorBanner.js', + paths.src + 'javascripts/notifyModal.js', paths.src + 'javascripts/timeoutPopup.js', paths.src + 'javascripts/date.js', paths.src + 'javascripts/loginAlert.js', @@ -119,6 +120,12 @@ const copyPDF = () => { ); }; +const copyUSWDSJS = () => { + return src('node_modules/@uswds/uswds/dist/js/uswds.min.js') + .pipe(dest(paths.dist + 'js/')); +}; + + // Configure USWDS paths uswds.settings.version = 3; uswds.paths.dist.css = paths.dist + 'css'; @@ -172,7 +179,8 @@ exports.default = series( copySetTimezone, copyImages, copyPDF, - copyAssets + copyAssets, + copyUSWDSJS ); exports.backstopTest = backstopTest; exports.backstopReference = backstopReference; diff --git a/poetry.lock b/poetry.lock index 6d2182aa1..275fb3470 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "ago" @@ -6,7 +6,6 @@ version = "0.1.0" description = "ago: Human readable timedeltas" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "ago-0.1.0-py3-none-any.whl", hash = "sha256:c0c60d106fe333ac06e2ff2dd7fb5de2c4ec3183ee39708a35b6144ccb86dece"}, {file = "ago-0.1.0.tar.gz", hash = "sha256:15604159711c47e08f21251f28bfb4383942087535660dbe640b2f6193f92be4"}, @@ -18,7 +17,6 @@ version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -30,19 +28,18 @@ version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "awscli" @@ -50,7 +47,6 @@ version = "1.36.40" description = "Universal Command Line Environment for AWS." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "awscli-1.36.40-py3-none-any.whl", hash = "sha256:971c3b150c06068bc26867fe295753547780f63fcf8256d41cd38760e44d46ca"}, {file = "awscli-1.36.40.tar.gz", hash = "sha256:e2a88f88dc16d5c0f26379afd6f254097e53e8b34c82164e59f4165db6ee6dfa"}, @@ -70,7 +66,6 @@ version = "0.1.0" description = "Automated web accessibility testing using axe-core engine." optional = false python-versions = ">=3.10,<4.0" -groups = ["main"] files = [ {file = "axe-core-python-0.1.0.tar.gz", hash = "sha256:8a9af93a22f1b47da65be2b9878f83be39155ccc4c7286a8de8e4fc88a7a27c6"}, {file = "axe_core_python-0.1.0-py3-none-any.whl", hash = "sha256:f99f6bd674726c631c5e20b876983b44dff2b8f452a0ccf08f3c23ee45d0aac3"}, @@ -82,7 +77,6 @@ version = "1.8.3" description = "Security oriented static analyser for python code." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "bandit-1.8.3-py3-none-any.whl", hash = "sha256:28f04dc0d258e1dd0f99dee8eefa13d1cb5e3fde1a5ab0c523971f97b289bcd8"}, {file = "bandit-1.8.3.tar.gz", hash = "sha256:f5847beb654d309422985c36644649924e0ea4425c76dec2e89110b87506193a"}, @@ -98,7 +92,7 @@ stevedore = ">=1.20.0" baseline = ["GitPython (>=3.1.30)"] sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] -toml = ["tomli (>=1.1.0) ; python_version < \"3.11\""] +toml = ["tomli (>=1.1.0)"] yaml = ["PyYAML"] [[package]] @@ -107,7 +101,6 @@ version = "4.13.4" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" -groups = ["main"] files = [ {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, @@ -130,7 +123,6 @@ version = "25.1.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, @@ -175,7 +167,6 @@ version = "6.2.0" description = "An easy safelist-based HTML-sanitizing tool." optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e"}, {file = "bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f"}, @@ -193,7 +184,6 @@ version = "1.9.0" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, @@ -205,7 +195,6 @@ version = "5.0" description = "Define boolean algebras, create and parse boolean expressions and create custom boolean DSL." optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "boolean_py-5.0-py3-none-any.whl", hash = "sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9"}, {file = "boolean_py-5.0.tar.gz", hash = "sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95"}, @@ -223,7 +212,6 @@ version = "1.35.99" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "boto3-1.35.99-py3-none-any.whl", hash = "sha256:83e560faaec38a956dfb3d62e05e1703ee50432b45b788c09e25107c5058bd71"}, {file = "boto3-1.35.99.tar.gz", hash = "sha256:e0abd794a7a591d90558e92e29a9f8837d25ece8e3c120e530526fe27eba5fca"}, @@ -243,7 +231,6 @@ version = "1.35.99" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "botocore-1.35.99-py3-none-any.whl", hash = "sha256:b22d27b6b617fc2d7342090d6129000af2efd20174215948c0d7ae2da0fab445"}, {file = "botocore-1.35.99.tar.gz", hash = "sha256:1eab44e969c39c5f3d9a3104a0836c24715579a455f12b3979a31d7cde51b3c3"}, @@ -263,7 +250,6 @@ version = "0.14.2" description = "httplib2 caching for requests" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "cachecontrol-0.14.2-py3-none-any.whl", hash = "sha256:ebad2091bf12d0d200dfc2464330db638c5deb41d546f6d7aca079e87290f3b0"}, {file = "cachecontrol-0.14.2.tar.gz", hash = "sha256:7d47d19f866409b98ff6025b6a0fca8e4c791fb31abbd95f622093894ce903a2"}, @@ -285,7 +271,6 @@ version = "5.5.2" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, @@ -297,7 +282,6 @@ version = "2025.4.26" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"}, {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"}, @@ -309,7 +293,6 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -389,7 +372,6 @@ version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -401,7 +383,6 @@ version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, @@ -503,7 +484,6 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -518,12 +498,10 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\""} [[package]] name = "coverage" @@ -531,7 +509,6 @@ version = "7.8.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"}, {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"}, @@ -599,7 +576,7 @@ files = [ ] [package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] +toml = ["tomli"] [[package]] name = "cryptography" @@ -607,7 +584,6 @@ version = "44.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.7" -groups = ["main", "dev"] files = [ {file = "cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88"}, {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137"}, @@ -652,10 +628,10 @@ files = [ cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""] -pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] @@ -667,7 +643,6 @@ version = "9.1.0" description = "Python library for CycloneDX" optional = false python-versions = "<4.0,>=3.8" -groups = ["dev"] files = [ {file = "cyclonedx_python_lib-9.1.0-py3-none-any.whl", hash = "sha256:55693fca8edaecc3363b24af14e82cc6e659eb1e8353e58b587c42652ce0fb52"}, {file = "cyclonedx_python_lib-9.1.0.tar.gz", hash = "sha256:86935f2c88a7b47a529b93c724dbd3e903bc573f6f8bd977628a7ca1b5dadea1"}, @@ -690,7 +665,6 @@ version = "0.7.1" description = "XML bomb protection for Python stdlib modules" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["dev"] files = [ {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, @@ -702,7 +676,6 @@ version = "1.5.0" description = "Tool for detecting secrets in the codebase" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "detect_secrets-1.5.0-py3-none-any.whl", hash = "sha256:e24e7b9b5a35048c313e983f76c4bd09dad89f045ff059e354f9943bf45aa060"}, {file = "detect_secrets-1.5.0.tar.gz", hash = "sha256:6bb46dcc553c10df51475641bb30fd69d25645cc12339e46c824c1e0c388898a"}, @@ -722,7 +695,6 @@ version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -734,7 +706,6 @@ version = "2.7.0" description = "DNS toolkit" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, @@ -755,7 +726,6 @@ version = "0.6.2" description = "Pythonic argument parser, that will make you smile" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] @@ -766,7 +736,6 @@ version = "0.16" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["dev"] files = [ {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, @@ -778,7 +747,6 @@ version = "2.0.0" description = "An implementation of lxml.xmlfile for the standard library" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"}, {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"}, @@ -790,7 +758,6 @@ version = "0.39.1" description = "Highly concurrent networking library" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "eventlet-0.39.1-py3-none-any.whl", hash = "sha256:2a349b6bca3471c7fc51e838beff9be94d3b9a146dc31c80890d69333ba03b80"}, {file = "eventlet-0.39.1.tar.gz", hash = "sha256:4a8a6475282d4021edde06ba335228c230b911b8d014577ddb33114c2b0c0510"}, @@ -809,7 +776,6 @@ version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, @@ -824,7 +790,6 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -839,7 +804,6 @@ version = "3.18.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, @@ -848,7 +812,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] +typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "flake8" @@ -856,7 +820,6 @@ version = "7.2.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343"}, {file = "flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426"}, @@ -873,7 +836,6 @@ version = "24.12.12" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." optional = false python-versions = ">=3.8.1" -groups = ["dev"] files = [ {file = "flake8_bugbear-24.12.12-py3-none-any.whl", hash = "sha256:1b6967436f65ca22a42e5373aaa6f2d87966ade9aa38d4baf2a1be550767545e"}, {file = "flake8_bugbear-24.12.12.tar.gz", hash = "sha256:46273cef0a6b6ff48ca2d69e472f41420a42a46e24b2a8972e4f0d6733d12a64"}, @@ -892,7 +854,6 @@ version = "1.3.3" description = "The package provides base classes and utils for flake8 plugin writing" optional = false python-versions = ">=3.6,<4.0" -groups = ["dev"] files = [ {file = "flake8-plugin-utils-1.3.3.tar.gz", hash = "sha256:39f6f338d038b301c6fd344b06f2e81e382b68fa03c0560dff0d9b1791a11a2c"}, {file = "flake8_plugin_utils-1.3.3-py3-none-any.whl", hash = "sha256:e4848c57d9d50f19100c2d75fa794b72df068666a9041b4b0409be923356a3ed"}, @@ -904,7 +865,6 @@ version = "5.0.0" description = "print statement checker plugin for flake8" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "flake8-print-5.0.0.tar.gz", hash = "sha256:76915a2a389cc1c0879636c219eb909c38501d3a43cc8dae542081c9ba48bdf9"}, {file = "flake8_print-5.0.0-py3-none-any.whl", hash = "sha256:84a1a6ea10d7056b804221ac5e62b1cee1aefc897ce16f2e5c42d3046068f5d8"}, @@ -920,7 +880,6 @@ version = "2.1.0" description = "A flake8 plugin checking common style issues or inconsistencies with pytest-based tests." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "flake8_pytest_style-2.1.0-py3-none-any.whl", hash = "sha256:a0d6dddcd533bfc13f19b8445907be0330c5e6ccf7090bcd9d5fa5a0b1b65e71"}, {file = "flake8_pytest_style-2.1.0.tar.gz", hash = "sha256:fee6befdb5915d600ef24e38d48a077d0dcffb032945ae0169486e7ff8a1079a"}, @@ -935,7 +894,6 @@ version = "3.1.0" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136"}, {file = "flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac"}, @@ -958,7 +916,6 @@ version = "0.2.0" description = "HTTP basic access authentication for Flask." optional = false python-versions = "*" -groups = ["main"] files = [ {file = "Flask-BasicAuth-0.2.0.tar.gz", hash = "sha256:df5ebd489dc0914c224419da059d991eb72988a01cdd4b956d52932ce7d501ff"}, ] @@ -972,7 +929,6 @@ version = "0.6.3" description = "User authentication and session management for Flask." optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333"}, {file = "Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d"}, @@ -988,7 +944,6 @@ version = "0.4.0" description = "A nice way to use Redis in your Flask app" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -groups = ["main"] files = [ {file = "flask-redis-0.4.0.tar.gz", hash = "sha256:e1fccc11e7ea35c2a4d68c0b9aa58226a098e45e834d615c7b6c4928b01ddd6c"}, {file = "flask_redis-0.4.0-py2.py3-none-any.whl", hash = "sha256:8d79eef4eb1217095edab603acc52f935b983ae4b7655ee7c82c0dfd87315d17"}, @@ -1008,7 +963,6 @@ version = "1.1.0" description = "HTTP security headers for Flask." optional = false python-versions = "*" -groups = ["main"] files = [ {file = "flask-talisman-1.1.0.tar.gz", hash = "sha256:c5f486f5f54420729f84b3c3850cd63f96e8b033a9629bee66c524ea363797ff"}, {file = "flask_talisman-1.1.0-py2.py3-none-any.whl", hash = "sha256:3c42b610ebe49b0e35ca150e179bf51aa1da01e4635b49a674868ea681046208"}, @@ -1020,7 +974,6 @@ version = "1.2.2" description = "Form rendering, validation, and CSRF protection for Flask with WTForms." optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "flask_wtf-1.2.2-py3-none-any.whl", hash = "sha256:e93160c5c5b6b571cf99300b6e01b72f9a101027cab1579901f8b10c5daf0b70"}, {file = "flask_wtf-1.2.2.tar.gz", hash = "sha256:79d2ee1e436cf570bccb7d916533fa18757a2f18c290accffab1b9a0b684666b"}, @@ -1040,7 +993,6 @@ version = "1.5.1" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, @@ -1055,7 +1007,6 @@ version = "3.2.0" description = "Python bindings and utilities for GeoJSON" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "geojson-3.2.0-py3-none-any.whl", hash = "sha256:69d14156469e13c79479672eafae7b37e2dcd19bdfd77b53f74fa8fe29910b52"}, {file = "geojson-3.2.0.tar.gz", hash = "sha256:b860baba1e8c6f71f8f5f6e3949a694daccf40820fa8f138b3f712bd85804903"}, @@ -1067,7 +1018,6 @@ version = "3.2.0" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "greenlet-3.2.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:b7a7b7f2bad3ca72eb2fa14643f1c4ca11d115614047299d89bc24a3b11ddd09"}, {file = "greenlet-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60e77242e38e99ecaede853755bbd8165e0b20a2f1f3abcaa6f0dceb826a7411"}, @@ -1136,7 +1086,6 @@ version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -1159,7 +1108,6 @@ version = "4.12.3" description = "Python humanize utilities" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "humanize-4.12.3-py3-none-any.whl", hash = "sha256:2cbf6370af06568fa6d2da77c86edb7886f3160ecd19ee1ffef07979efc597f6"}, {file = "humanize-4.12.3.tar.gz", hash = "sha256:8430be3a615106fdfceb0b2c1b41c4c98c6b0fc5cc59663a5539b111dd325fb0"}, @@ -1174,7 +1122,6 @@ version = "2.6.9" description = "File identification library for Python" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150"}, {file = "identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf"}, @@ -1189,7 +1136,6 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1204,7 +1150,6 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -1216,7 +1161,6 @@ version = "6.0.1" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.9.0" -groups = ["dev"] files = [ {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, @@ -1232,7 +1176,6 @@ version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, @@ -1244,7 +1187,6 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1262,7 +1204,6 @@ version = "0.8.2" description = "A CLI interface to Jinja2" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "jinja2-cli-0.8.2.tar.gz", hash = "sha256:a16bb1454111128e206f568c95938cdef5b5a139929378f72bb8cf6179e18e50"}, {file = "jinja2_cli-0.8.2-py2.py3-none-any.whl", hash = "sha256:b91715c79496beaddad790171e7258a87db21c1a0b6d2b15bca3ba44b74aac5d"}, @@ -1284,7 +1225,6 @@ version = "1.0.1" description = "JSON Matching Expressions" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, @@ -1296,7 +1236,6 @@ version = "30.4.1" description = "license-expression is a comprehensive utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "license_expression-30.4.1-py3-none-any.whl", hash = "sha256:679646bc3261a17690494a3e1cada446e5ee342dbd87dcfa4a0c24cc5dce13ee"}, {file = "license_expression-30.4.1.tar.gz", hash = "sha256:9f02105f9e0fcecba6a85dfbbed7d94ea1c3a70cf23ddbfb5adf3438a6f6fce0"}, @@ -1315,7 +1254,6 @@ version = "0.2.0" description = "Load me later. A lazy plugin management system." optional = false python-versions = "*" -groups = ["main"] files = [ {file = "lml-0.2.0-py2.py3-none-any.whl", hash = "sha256:20c80728189e46e8d986f5d0cdf6d83c493471fc25b1c31a8cb3fa96e80b58f8"}, {file = "lml-0.2.0.tar.gz", hash = "sha256:8dd5afb4367a593d1cdb2144a05874cd9938f5266bebb0c9e1413200423c0d74"}, @@ -1327,7 +1265,6 @@ version = "5.3.2" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "lxml-5.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c4b84d6b580a9625dfa47269bf1fd7fbba7ad69e08b16366a46acb005959c395"}, {file = "lxml-5.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4c08ecb26e4270a62f81f81899dfff91623d349e433b126931c9c4577169666"}, @@ -1482,7 +1419,6 @@ version = "0.7.1" description = "Create Python CLI apps with little to no effort at all!" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "mando-0.7.1-py2.py3-none-any.whl", hash = "sha256:26ef1d70928b6057ee3ca12583d73c63e05c49de8972d620c278a7b206581a8a"}, {file = "mando-0.7.1.tar.gz", hash = "sha256:18baa999b4b613faefb00eac4efadcf14f510b59b924b66e08289aa1de8c3500"}, @@ -1500,7 +1436,6 @@ version = "3.8" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc"}, {file = "markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f"}, @@ -1516,7 +1451,6 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -1541,7 +1475,6 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1612,7 +1545,6 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -1624,7 +1556,6 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -1636,7 +1567,6 @@ version = "3.1.3" description = "A sane and fast Markdown parser with useful plugins and renderers" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9"}, {file = "mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0"}, @@ -1648,7 +1578,6 @@ version = "5.1.4" description = "A library that allows you to easily mock out tests based on AWS infrastructure" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "moto-5.1.4-py3-none-any.whl", hash = "sha256:9a19d7a64c3f03824389cfbd478b64c82bd4d8da21b242a34259360d66cd108b"}, {file = "moto-5.1.4.tar.gz", hash = "sha256:b339c3514f2986ebefa465671b688bdbf51796705702214b1bad46490b68507a"}, @@ -1694,7 +1623,6 @@ version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, @@ -1768,7 +1696,6 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" -groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -1780,7 +1707,6 @@ version = "10.11.0" description = "New Relic Python Agent" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "newrelic-10.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b586cf584f0672bfa001eac0426646b7584c066c9adc94eda9a1c995118131b3"}, {file = "newrelic-10.11.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da796354f0b90cbd93a4212e16f83e0d022ae7ed9f846c169a652f24812f10fa"}, @@ -1822,7 +1748,6 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -1834,7 +1759,6 @@ version = "10.0.1" description = "Python API client for GOV.UK Notify." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "notifications_python_client-10.0.1-py3-none-any.whl", hash = "sha256:00d88eacb6fd6eb0467d7396a7e23677194cfebe0ebd88de2efa031fb51eb23f"}, ] @@ -1850,7 +1774,6 @@ version = "2.2.5" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" -groups = ["main"] files = [ {file = "numpy-2.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f4a922da1729f4c40932b2af4fe84909c7a6e167e6e99f71838ce3a29f3fe26"}, {file = "numpy-2.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6f91524d31b34f4a5fee24f5bc16dcd1491b668798b6d85585d836c1e633a6a"}, @@ -1915,7 +1838,6 @@ version = "3.0.10" description = "A Python library to read/write Excel 2010 xlsx/xlsm files" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "openpyxl-3.0.10-py2.py3-none-any.whl", hash = "sha256:0ab6d25d01799f97a9464630abacbb34aafecdcaa0ef3cba6d6b3499867d0355"}, {file = "openpyxl-3.0.10.tar.gz", hash = "sha256:e47805627aebcf860edb4edf7987b1309c1b3632f3750538ed962bbcc3bd7449"}, @@ -1930,7 +1852,6 @@ version = "4.1.0" description = "An OrderedSet is a custom MutableSet that remembers its order, so that every" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ {file = "ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"}, {file = "ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562"}, @@ -1945,7 +1866,6 @@ version = "0.16.0" description = "A purl aka. Package URL parser and builder" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "packageurl_python-0.16.0-py3-none-any.whl", hash = "sha256:5c3872638b177b0f1cf01c3673017b7b27ebee485693ae12a8bed70fa7fa7c35"}, {file = "packageurl_python-0.16.0.tar.gz", hash = "sha256:69e3bf8a3932fe9c2400f56aaeb9f86911ecee2f9398dbe1b58ec34340be365d"}, @@ -1963,7 +1883,6 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1975,7 +1894,6 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1987,7 +1905,6 @@ version = "6.1.1" description = "Python Build Reasonableness" optional = false python-versions = ">=2.6" -groups = ["dev"] files = [ {file = "pbr-6.1.1-py2.py3-none-any.whl", hash = "sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76"}, {file = "pbr-6.1.1.tar.gz", hash = "sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b"}, @@ -2002,7 +1919,6 @@ version = "9.0.4" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." optional = false python-versions = "*" -groups = ["main"] files = [ {file = "phonenumbers-9.0.4-py2.py3-none-any.whl", hash = "sha256:ac8aa9d4679da5078547aeb159d78c2b60f8d0fb4eafdc7cf464069039b30ccd"}, {file = "phonenumbers-9.0.4.tar.gz", hash = "sha256:b1530fc2a7b67e4c37c63e8e736c7e6efe2fed283c1bbb5c08c4511ec0c160ed"}, @@ -2014,7 +1930,6 @@ version = "25.0.1" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pip-25.0.1-py3-none-any.whl", hash = "sha256:c46efd13b6aa8279f33f2864459c8ce587ea6a1a59ee20de055868d8f7688f7f"}, {file = "pip-25.0.1.tar.gz", hash = "sha256:88f96547ea48b940a3a385494e181e29fb8637898f88d88737c5049780f196ea"}, @@ -2026,7 +1941,6 @@ version = "0.0.34" description = "An unofficial, importable pip API" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pip_api-0.0.34-py3-none-any.whl", hash = "sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb"}, {file = "pip_api-0.0.34.tar.gz", hash = "sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625"}, @@ -2041,7 +1955,6 @@ version = "2.9.0" description = "A tool for scanning Python environments for known vulnerabilities" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pip_audit-2.9.0-py3-none-any.whl", hash = "sha256:348b16e60895749a0839875d7cc27ebd692e1584ebe5d5cb145941c8e25a80bd"}, {file = "pip_audit-2.9.0.tar.gz", hash = "sha256:0b998410b58339d7a231e5aa004326a294e4c7c6295289cdc9d5e1ef07b1f44d"}, @@ -2070,7 +1983,6 @@ version = "32.0.1" description = "pip requirements parser - a mostly correct pip requirements parsing library because it uses pip's own code." optional = false python-versions = ">=3.6.0" -groups = ["dev"] files = [ {file = "pip-requirements-parser-32.0.1.tar.gz", hash = "sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3"}, {file = "pip_requirements_parser-32.0.1-py3-none-any.whl", hash = "sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526"}, @@ -2090,7 +2002,6 @@ version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, @@ -2107,7 +2018,6 @@ version = "1.51.0" description = "A high-level API to automate web browsers" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "playwright-1.51.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:bcaaa3d5d73bda659bfb9ff2a288b51e85a91bd89eda86eaf8186550973e416a"}, {file = "playwright-1.51.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e0ae6eb44297b24738e1a6d9c580ca4243b4e21b7e65cf936a71492c08dd0d4"}, @@ -2128,7 +2038,6 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -2144,7 +2053,6 @@ version = "4.2.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"}, {file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"}, @@ -2163,7 +2071,6 @@ version = "2.0.0" description = "Library for serializing and deserializing Python Objects to and from JSON and XML." optional = false python-versions = "<4.0,>=3.8" -groups = ["dev"] files = [ {file = "py_serializable-2.0.0-py3-none-any.whl", hash = "sha256:1721e4c0368adeec965c183168da4b912024702f19e15e13f8577098b9a4f8fe"}, {file = "py_serializable-2.0.0.tar.gz", hash = "sha256:e9e6491dd7d29c31daf1050232b57f9657f9e8a43b867cca1ff204752cf420a5"}, @@ -2178,7 +2085,6 @@ version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, @@ -2190,7 +2096,6 @@ version = "2.13.0" description = "Python style guide checker" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9"}, {file = "pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae"}, @@ -2202,7 +2107,6 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -2214,7 +2118,6 @@ version = "12.1.1" description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pyee-12.1.1-py3-none-any.whl", hash = "sha256:18a19c650556bb6b32b406d7f017c8f513aceed1ef7ca618fb65de7bd2d347ef"}, {file = "pyee-12.1.1.tar.gz", hash = "sha256:bbc33c09e2ff827f74191e3e5bbc6be7da02f627b7ec30d86f5ce1a6fb2424a3"}, @@ -2224,7 +2127,7 @@ files = [ typing-extensions = "*" [package.extras] -dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "pytest", "pytest-asyncio ; python_version >= \"3.4\"", "pytest-trio ; python_version >= \"3.7\"", "sphinx", "toml", "tox", "trio", "trio ; python_version > \"3.6\"", "trio-typing ; python_version > \"3.6\"", "twine", "twisted", "validate-pyproject[all]"] +dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "pytest", "pytest-asyncio", "pytest-trio", "sphinx", "toml", "tox", "trio", "trio", "trio-typing", "twine", "twisted", "validate-pyproject[all]"] [[package]] name = "pyexcel" @@ -2232,7 +2135,6 @@ version = "0.7.3" description = "A wrapper library that provides one API to read, manipulate and writedata in different excel formats" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "pyexcel-0.7.3-py2.py3-none-any.whl", hash = "sha256:394df2445364fa1960fed4a9bb04c62efdff5435026f3d1346f614a94fe5ba7a"}, {file = "pyexcel-0.7.3.tar.gz", hash = "sha256:57b3c3fb55dd09a62cbbf2ae9f1e78aa11b627c2bfc6072ff2d9587c022b06d4"}, @@ -2254,7 +2156,6 @@ version = "0.3.4" description = "A Python package to create/manipulate OpenDocumentFormat files" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "pyexcel-ezodf-0.3.4.tar.gz", hash = "sha256:972eeea9b0e4bab60dfc5cdcb7378cc7ba5e070a0b7282746c0182c5de011ff1"}, {file = "pyexcel_ezodf-0.3.4-py2.py3-none-any.whl", hash = "sha256:a74ac7636a015fff31d35c5350dc5ad347ba98ecb453de4dbcbb9a9168434e8c"}, @@ -2269,7 +2170,6 @@ version = "0.6.7" description = "A python library to read and write structured data in csv, zipped csvformat and to/from databases" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "pyexcel_io-0.6.7-py2.py3-none-any.whl", hash = "sha256:add9f3c1489070ab0a3226bab7d23d4b84a364f5b44f3a14e2ce785ede88fcb3"}, {file = "pyexcel_io-0.6.7.tar.gz", hash = "sha256:2c33d8df505e21a13bed585ac6ab7d30ec826a9f156b758394af5d22359bddb9"}, @@ -2290,7 +2190,6 @@ version = "0.6.1" description = "A wrapper library to read, manipulate and write data in ods format" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "pyexcel-ods3-0.6.1.tar.gz", hash = "sha256:53740fc9bc6e91e43cdc0ee4f557bb3b252d8493d34f2c11d26a93c53cfebc2e"}, {file = "pyexcel_ods3-0.6.1-py3-none-any.whl", hash = "sha256:ca61d139879349a5d4b0a241add6504474c59fa280d1804b76f56ee4ba30eb8b"}, @@ -2307,7 +2206,6 @@ version = "0.7.1" description = "A wrapper library to read, manipulate and write data in xls format. Itreads xlsx and xlsm format" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "pyexcel_xls-0.7.1-py2.py3-none-any.whl", hash = "sha256:5fd1bc29a50ad7abf5e5d5b6b0dfa1f83e01a97dbb3e7a9dc8ef118d5b78b46a"}, {file = "pyexcel_xls-0.7.1.tar.gz", hash = "sha256:0e8c598dc840f9216507f1c16975c18b06e36df09acd79bda1a9de2f9cad0761"}, @@ -2324,7 +2222,6 @@ version = "0.6.1" description = "A wrapper library to read, manipulate and write data in xlsx and xlsmformat" optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "pyexcel_xlsx-0.6.1-py2.py3-none-any.whl", hash = "sha256:c17d8c90ae6c2ae5672e469e6afe4336597e4ff479e5a93ba846ae46b7b80769"}, {file = "pyexcel_xlsx-0.6.1.tar.gz", hash = "sha256:80b7985d394b73abf5c52212027bbbc0c7f6090192bcbc76403f8672d74c7960"}, @@ -2340,7 +2237,6 @@ version = "3.3.2" description = "passive checker of Python programs" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a"}, {file = "pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b"}, @@ -2352,7 +2248,6 @@ version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, @@ -2367,7 +2262,6 @@ version = "2.10.1" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, @@ -2385,7 +2279,6 @@ version = "3.2.3" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, @@ -2400,7 +2293,6 @@ version = "3.7.1" description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" optional = false python-versions = ">=3.10" -groups = ["main"] files = [ {file = "pyproj-3.7.1-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:bf09dbeb333c34e9c546364e7df1ff40474f9fddf9e70657ecb0e4f670ff0b0e"}, {file = "pyproj-3.7.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:6575b2e53cc9e3e461ad6f0692a5564b96e7782c28631c7771c668770915e169"}, @@ -2446,7 +2338,6 @@ version = "8.3.5" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, @@ -2467,7 +2358,6 @@ version = "2.1.0" description = "pytest plugin for URL based testing" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest_base_url-2.1.0-py3-none-any.whl", hash = "sha256:3ad15611778764d451927b2a53240c1a7a591b521ea44cebfe45849d2d2812e6"}, {file = "pytest_base_url-2.1.0.tar.gz", hash = "sha256:02748589a54f9e63fcbe62301d6b0496da0d10231b753e950c63e03aee745d45"}, @@ -2486,7 +2376,6 @@ version = "1.1.5" description = "pytest plugin that allows you to add environment variables." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest_env-1.1.5-py3-none-any.whl", hash = "sha256:ce90cf8772878515c24b31cd97c7fa1f4481cd68d588419fd45f10ecaee6bc30"}, {file = "pytest_env-1.1.5.tar.gz", hash = "sha256:91209840aa0e43385073ac464a554ad2947cc2fd663a9debf88d03b01e0cc1cf"}, @@ -2504,7 +2393,6 @@ version = "3.14.0" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, @@ -2522,7 +2410,6 @@ version = "0.7.0" description = "A pytest wrapper with fixtures for Playwright to automate web browsers" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "pytest_playwright-0.7.0-py3-none-any.whl", hash = "sha256:2516d0871fa606634bfe32afbcc0342d68da2dbff97fe3459849e9c428486da2"}, {file = "pytest_playwright-0.7.0.tar.gz", hash = "sha256:b3f2ea514bbead96d26376fac182f68dcd6571e7cb41680a89ff1673c05d60b6"}, @@ -2540,7 +2427,6 @@ version = "3.6.1" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, @@ -2561,7 +2447,6 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2576,7 +2461,6 @@ version = "1.1.0" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, @@ -2591,14 +2475,13 @@ version = "3.3.0" description = "JSON Log Formatter for the Python Logging Package" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7"}, {file = "python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84"}, ] [package.extras] -dev = ["backports.zoneinfo ; python_version < \"3.9\"", "black", "build", "freezegun", "mdx_truly_sane_lists", "mike", "mkdocs", "mkdocs-awesome-pages-plugin", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-material (>=8.5)", "mkdocstrings[python]", "msgspec ; implementation_name != \"pypy\"", "mypy", "orjson ; implementation_name != \"pypy\"", "pylint", "pytest", "tzdata", "validate-pyproject[all]"] +dev = ["backports.zoneinfo", "black", "build", "freezegun", "mdx_truly_sane_lists", "mike", "mkdocs", "mkdocs-awesome-pages-plugin", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-material (>=8.5)", "mkdocstrings[python]", "msgspec", "mypy", "orjson", "pylint", "pytest", "tzdata", "validate-pyproject[all]"] [[package]] name = "python-slugify" @@ -2606,7 +2489,6 @@ version = "8.0.4" description = "A Python slugify application that also handles Unicode" optional = false python-versions = ">=3.7" -groups = ["dev"] files = [ {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"}, {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"}, @@ -2624,7 +2506,6 @@ version = "2025.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, @@ -2636,7 +2517,6 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -2699,7 +2579,6 @@ version = "6.0.1" description = "Code Metrics in Python" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "radon-6.0.1-py2.py3-none-any.whl", hash = "sha256:632cc032364a6f8bb1010a2f6a12d0f14bc7e5ede76585ef29dc0cecf4cd8859"}, {file = "radon-6.0.1.tar.gz", hash = "sha256:d1ac0053943a893878940fedc8b19ace70386fc9c9bf0a09229a44125ebf45b5"}, @@ -2718,7 +2597,6 @@ version = "5.2.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, @@ -2734,7 +2612,6 @@ version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, @@ -2838,7 +2715,6 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -2860,7 +2736,6 @@ version = "1.12.1" description = "Mock out responses from the requests package" optional = false python-versions = ">=3.5" -groups = ["dev"] files = [ {file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"}, {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"}, @@ -2878,7 +2753,6 @@ version = "0.25.7" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "responses-0.25.7-py3-none-any.whl", hash = "sha256:92ca17416c90fe6b35921f52179bff29332076bb32694c0df02dcac2c6bc043c"}, {file = "responses-0.25.7.tar.gz", hash = "sha256:8ebae11405d7a5df79ab6fd54277f6f2bc29b2d002d0dd2d5c632594d1ddcedb"}, @@ -2890,7 +2764,7 @@ requests = ">=2.30.0,<3.0" urllib3 = ">=1.25.10,<3.0" [package.extras] -tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli ; python_version < \"3.11\"", "tomli-w", "types-PyYAML", "types-requests"] +tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] [[package]] name = "rich" @@ -2898,7 +2772,6 @@ version = "14.0.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" -groups = ["dev"] files = [ {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, @@ -2917,7 +2790,6 @@ version = "4.7.2" description = "Pure-Python RSA implementation" optional = false python-versions = ">=3.5, <4" -groups = ["dev"] files = [ {file = "rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2"}, {file = "rsa-4.7.2.tar.gz", hash = "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"}, @@ -2932,7 +2804,6 @@ version = "0.2.0" description = "Pluggable R-tree implementation in pure Python." optional = false python-versions = ">=3.6.0" -groups = ["main"] files = [ {file = "rtreelib-0.2.0-py3-none-any.whl", hash = "sha256:cedd4c9e4014f39b290f90f9a0a2bff10a852feb4cd9652ac1933f4f8a77cad3"}, {file = "rtreelib-0.2.0.tar.gz", hash = "sha256:f162d63ae5465d1f25d76ca55e2dc8524a4215c190f9442e18ce48d3d2b61a52"}, @@ -2947,7 +2818,6 @@ version = "0.10.4" description = "An Amazon S3 Transfer Manager" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e"}, {file = "s3transfer-0.10.4.tar.gz", hash = "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7"}, @@ -2965,20 +2835,19 @@ version = "78.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8"}, {file = "setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] +core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "shapely" @@ -2986,7 +2855,6 @@ version = "2.1.0" description = "Manipulation and analysis of geometric objects" optional = false python-versions = ">=3.10" -groups = ["main"] files = [ {file = "shapely-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3e5c5e3864d4dc431dd85a8e5137ebd39c8ac287b009d3fa80a07017b29c940"}, {file = "shapely-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6eea89b16f5f3a064659126455d23fa3066bc3d6cd385c35214f06bf5871aa6"}, @@ -3044,7 +2912,6 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -3056,7 +2923,6 @@ version = "2.0.1" description = "Python with the SmartyPants" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "smartypants-2.0.1-py2.py3-none-any.whl", hash = "sha256:8db97f7cbdf08d15b158a86037cd9e116b4cf37703d24e0419a0d64ca5808f0d"}, ] @@ -3067,7 +2933,6 @@ version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, @@ -3079,7 +2944,6 @@ version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" -groups = ["main"] files = [ {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, @@ -3091,7 +2955,6 @@ version = "5.4.1" description = "Manage dynamic plugins for Python applications" optional = false python-versions = ">=3.9" -groups = ["dev"] files = [ {file = "stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe"}, {file = "stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b"}, @@ -3106,7 +2969,6 @@ version = "1.3" description = "The most basic Text::Unidecode port" optional = false python-versions = "*" -groups = ["dev"] files = [ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, @@ -3118,7 +2980,6 @@ version = "1.7.0" description = "module to create simple ASCII tables" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "texttable-1.7.0-py2.py3-none-any.whl", hash = "sha256:72227d592c82b3d7f672731ae73e4d1f88cd8e2ef5b075a7a7f01a23a3743917"}, {file = "texttable-1.7.0.tar.gz", hash = "sha256:2d2068fb55115807d3ac77a4ca68fa48803e84ebb0ee2340f858107a36522638"}, @@ -3130,7 +2991,6 @@ version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["dev"] files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -3142,7 +3002,6 @@ version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, @@ -3154,14 +3013,13 @@ version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -3172,7 +3030,6 @@ version = "20.30.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6"}, {file = "virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8"}, @@ -3185,7 +3042,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "vulture" @@ -3193,7 +3050,6 @@ version = "2.14" description = "Find dead code" optional = false python-versions = ">=3.8" -groups = ["dev"] files = [ {file = "vulture-2.14-py2.py3-none-any.whl", hash = "sha256:d9a90dba89607489548a49d557f8bac8112bd25d3cbc8aeef23e860811bd5ed9"}, {file = "vulture-2.14.tar.gz", hash = "sha256:cb8277902a1138deeab796ec5bef7076a6e0248ca3607a3f3dee0b6d9e9b8415"}, @@ -3205,7 +3061,6 @@ version = "0.5.1" description = "Character encoding aliases for legacy web content" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, @@ -3217,7 +3072,6 @@ version = "3.1.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] files = [ {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, @@ -3235,7 +3089,6 @@ version = "3.2.1" description = "Form validation and rendering for Python web development." optional = false python-versions = ">=3.9" -groups = ["main"] files = [ {file = "wtforms-3.2.1-py3-none-any.whl", hash = "sha256:583bad77ba1dd7286463f21e11aa3043ca4869d03575921d1a1698d0715e0fd4"}, {file = "wtforms-3.2.1.tar.gz", hash = "sha256:df3e6b70f3192e92623128123ec8dca3067df9cfadd43d59681e210cfb8d4682"}, @@ -3253,7 +3106,6 @@ version = "2.0.1" description = "Library for developers to extract data from Microsoft Excel (tm) .xls spreadsheet files" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -groups = ["main"] files = [ {file = "xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd"}, {file = "xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88"}, @@ -3270,7 +3122,6 @@ version = "1.3.0" description = "Library to create spreadsheet files compatible with MS Excel 97/2000/XP/2003 XLS files, on any platform, with Python 2.6, 2.7, 3.3+" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "xlwt-1.3.0-py2.py3-none-any.whl", hash = "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e"}, {file = "xlwt-1.3.0.tar.gz", hash = "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"}, @@ -3282,13 +3133,12 @@ version = "0.14.2" description = "Makes working with XML feel like you are working with JSON" optional = false python-versions = ">=3.6" -groups = ["dev"] files = [ {file = "xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac"}, {file = "xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553"}, ] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = "^3.12.2" content-hash = "7195e2de888208ee0028cd95daefcd2e9467d8d7b7f3510d7d22e699152f8c49" diff --git a/tests/javascripts/timeoutPopup.test.js b/tests/javascripts/timeoutPopup.test.js index 8d615105e..1909362f2 100644 --- a/tests/javascripts/timeoutPopup.test.js +++ b/tests/javascripts/timeoutPopup.test.js @@ -5,7 +5,7 @@ beforeAll(() => {
-

+

Your session will end soon. Please choose to extend your session or sign out. Your session will expire in 5 minutes or less.

From be038daa0a38314d3509f414bcd105b1bfc285e3 Mon Sep 17 00:00:00 2001 From: Jonathan Bobel Date: Wed, 7 May 2025 11:16:00 -0400 Subject: [PATCH 02/14] Create notifyModal.test.js --- tests/javascripts/notifyModal.test.js | 120 ++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 tests/javascripts/notifyModal.test.js diff --git a/tests/javascripts/notifyModal.test.js b/tests/javascripts/notifyModal.test.js new file mode 100644 index 000000000..df1aba404 --- /dev/null +++ b/tests/javascripts/notifyModal.test.js @@ -0,0 +1,120 @@ +/** + * @jest-environment jsdom + */ + +const { openModal, closeModal } = require("../../app/assets/javascripts/modal"); // adjust path if needed + +describe("Modal functionality", () => { + let modalWrapper, modalElement, openBtn, closeBtn, anotherFocusable; + + beforeEach(() => { + document.body.innerHTML = ` + + + + `; + + modalWrapper = document.getElementById("myModal"); + modalElement = modalWrapper.querySelector(".usa-modal"); + openBtn = document.querySelector('[data-open-modal]'); + closeBtn = modalWrapper.querySelector('[data-close-modal]'); + anotherFocusable = modalWrapper.querySelector('a'); + }); + + afterEach(() => { + document.body.innerHTML = ""; + }); + + test("Opens the modal and sets focus to the first focusable element", () => { + document.activeElement.blur(); // ensure focus starts elsewhere + openModal("myModal"); + + expect(modalWrapper.classList.contains("is-hidden")).toBe(false); + expect(modalElement.hasAttribute("aria-hidden")).toBe(false); + expect(modalElement.hasAttribute("inert")).toBe(false); + expect(modalElement.hasAttribute("hidden")).toBe(false); + expect(document.body.classList.contains("modal-open")).toBe(true); + expect(document.activeElement).toBe(closeBtn); + }); + + test("Closes the modal and restores focus", () => { + openBtn.focus(); + openModal("myModal"); + closeModal(); + + expect(modalWrapper.classList.contains("is-hidden")).toBe(true); + expect(modalElement.getAttribute("aria-hidden")).toBe("true"); + expect(modalElement.hasAttribute("inert")).toBe(true); + expect(modalElement.hasAttribute("hidden")).toBe(true); + expect(document.body.classList.contains("modal-open")).toBe(false); + expect(document.activeElement).toBe(openBtn); + }); + + test("Closes the modal when pressing Escape", () => { + openModal("myModal"); + + const event = new KeyboardEvent("keydown", { key: "Escape" }); + document.dispatchEvent(event); + + expect(modalWrapper.classList.contains("is-hidden")).toBe(true); + }); + + test("Traps focus within the modal when Tab is pressed", () => { + openModal("myModal"); + + const focusableElements = modalElement.querySelectorAll( + 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"])' + ); + + focusableElements[focusableElements.length - 1].focus(); // Last element + + const tabEvent = new KeyboardEvent("keydown", { + key: "Tab", + bubbles: true + }); + + modalElement.dispatchEvent(tabEvent); + expect(document.activeElement).toBe(focusableElements[0]); + }); + + test("Traps focus backwards when Shift+Tab is pressed from first element", () => { + openModal("myModal"); + + const focusableElements = modalElement.querySelectorAll( + 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"])' + ); + + focusableElements[0].focus(); // First element + + const shiftTabEvent = new KeyboardEvent("keydown", { + key: "Tab", + shiftKey: true, + bubbles: true + }); + + modalElement.dispatchEvent(shiftTabEvent); + expect(document.activeElement).toBe(focusableElements[focusableElements.length - 1]); + }); + + test("Closes modal when clicking on overlay", () => { + openModal("myModal"); + const overlay = modalElement.querySelector(".usa-modal-overlay"); + + const clickEvent = new MouseEvent("click", { + bubbles: true + }); + + overlay.dispatchEvent(clickEvent); + expect(modalWrapper.classList.contains("is-hidden")).toBe(true); + }); +}); From abb3ba64d97d728d9e85236bd17e44bbb386fed8 Mon Sep 17 00:00:00 2001 From: Jonathan Bobel Date: Wed, 7 May 2025 15:06:39 -0400 Subject: [PATCH 03/14] Updates to the cancel modal --- app/assets/javascripts/notifyModal.js | 31 ++++++++----- .../preventDuplicateFormSubmissions.js | 6 +++ app/assets/sass/uswds/_main.scss | 13 ++++++ tests/javascripts/notifyModal.test.js | 43 ++++++++++++++++++- 4 files changed, 81 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/notifyModal.js b/app/assets/javascripts/notifyModal.js index bdf7fa48a..ccda2a1a2 100644 --- a/app/assets/javascripts/notifyModal.js +++ b/app/assets/javascripts/notifyModal.js @@ -61,20 +61,20 @@ function closeModal() { } -// Attach open triggers -document.querySelectorAll('[data-open-modal]').forEach(btn => { - btn.addEventListener('click', () => { - const modalId = btn.getAttribute('data-open-modal'); - openModal(modalId); +function attachModalTriggers() { + document.querySelectorAll('[data-open-modal]').forEach(btn => { + btn.addEventListener('click', () => { + const modalId = btn.getAttribute('data-open-modal'); + openModal(modalId); + }); }); -}); -// Attach close triggers -document.querySelectorAll('[data-close-modal]').forEach(btn => { - btn.addEventListener('click', () => { - closeModal(); + document.querySelectorAll('[data-close-modal]').forEach(btn => { + btn.addEventListener('click', () => { + closeModal(); + }); }); -}); +} // Escape key closes modal document.addEventListener('keydown', (e) => { @@ -89,3 +89,12 @@ document.addEventListener('click', (e) => { closeModal(); } }); + +document.addEventListener('DOMContentLoaded', () => { + attachModalTriggers(); +}); + +// ✅ Check if we're in a Node.js environment (for Jest) before using `module.exports` +if (typeof module !== "undefined" && typeof module.exports !== "undefined") { + module.exports = { closeModal, openModal, attachModalTriggers }; +} diff --git a/app/assets/javascripts/preventDuplicateFormSubmissions.js b/app/assets/javascripts/preventDuplicateFormSubmissions.js index 4c4b97252..c62aaa9ec 100644 --- a/app/assets/javascripts/preventDuplicateFormSubmissions.js +++ b/app/assets/javascripts/preventDuplicateFormSubmissions.js @@ -21,6 +21,12 @@ $submitButton.append(''); } + // Disable Cancel button too + const $cancelButton = $('button[name]').filter(function () { + return $(this).attr('name')?.toLowerCase() === 'cancel'; + }); + $cancelButton.prop('disabled', true); + setTimeout(() => { renableSubmitButton($submitButton); }, 10000); // fallback safety diff --git a/app/assets/sass/uswds/_main.scss b/app/assets/sass/uswds/_main.scss index 61e319f27..585d0ee63 100644 --- a/app/assets/sass/uswds/_main.scss +++ b/app/assets/sass/uswds/_main.scss @@ -352,6 +352,19 @@ h2.recipient-list { } // Button ellipses loading +.dot-anim { + display: inline-block; + margin-left: 0; /* remove left margin if it exists */ + padding-left: 0; + font-size: 1em; + animation: dots 1s steps(3, end) infinite; +} + +/* Optional: reduce spacing by removing whitespace node */ +button span.dot-anim { + margin-left: 0; /* forces no space even if white-space exists */ +} + .dot-anim::after { content: '.'; animation: dotPulse 1.5s steps(3, end) infinite; diff --git a/tests/javascripts/notifyModal.test.js b/tests/javascripts/notifyModal.test.js index df1aba404..be703fd97 100644 --- a/tests/javascripts/notifyModal.test.js +++ b/tests/javascripts/notifyModal.test.js @@ -2,7 +2,7 @@ * @jest-environment jsdom */ -const { openModal, closeModal } = require("../../app/assets/javascripts/modal"); // adjust path if needed +const { openModal, closeModal, attachModalTriggers } = require("../../app/assets/javascripts/notifyModal.js"); // adjust path if needed describe("Modal functionality", () => { let modalWrapper, modalElement, openBtn, closeBtn, anotherFocusable; @@ -118,3 +118,44 @@ describe("Modal functionality", () => { expect(modalWrapper.classList.contains("is-hidden")).toBe(true); }); }); + +describe("Modal trigger buttons", () => { + beforeEach(() => { + document.body.innerHTML = ` + + + `; + }); + + afterEach(() => { + document.body.innerHTML = ""; + }); + + test("Clicking [data-open-modal] opens the modal", () => { + attachModalTriggers(); + const openButton = document.querySelector('[data-open-modal]'); + openButton.click(); + + const modalWrapper = document.getElementById("myModal"); + expect(modalWrapper.classList.contains("is-hidden")).toBe(false); + }); + + test("Clicking [data-close-modal] closes the modal", () => { + const modalWrapper = document.getElementById("myModal"); + modalWrapper.classList.remove("is-hidden"); + + attachModalTriggers(); + const closeButton = document.querySelector('[data-close-modal]'); + closeModal(); // ensure modal is open to begin with + openModal("myModal"); + + closeButton.click(); + expect(modalWrapper.classList.contains("is-hidden")).toBe(true); + }); + }); From 9b8d0c2b2f9f7ad60321cfbd8b01030950da969a Mon Sep 17 00:00:00 2001 From: Jonathan Bobel Date: Wed, 7 May 2025 15:26:14 -0400 Subject: [PATCH 04/14] Update ok.html --- app/templates/views/check/ok.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/views/check/ok.html b/app/templates/views/check/ok.html index aeb7df6f3..c7461bc2a 100644 --- a/app/templates/views/check/ok.html +++ b/app/templates/views/check/ok.html @@ -39,7 +39,7 @@ {% set button_text %} Preview {% endset %} - {{ usaButton({ "text": "button_text", "classes": "margin-top-4" }) }} + {{ usaButton({ "text": button_text, "classes": "margin-top-4" }) }}
From 96cbebefed81f9574cd9fbd8da758c3f26680aea Mon Sep 17 00:00:00 2001 From: Jonathan Bobel Date: Thu, 8 May 2025 14:33:36 -0400 Subject: [PATCH 05/14] 2211 - Update content to inform user of page refresh --- app/templates/views/jobs/job.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/templates/views/jobs/job.html b/app/templates/views/jobs/job.html index cd8e9267f..2ae2d76c1 100644 --- a/app/templates/views/jobs/job.html +++ b/app/templates/views/jobs/job.html @@ -11,6 +11,9 @@ {% block maincolumn_content %} {{ page_header("Message status") }} + {% if not job.finished_processing %} +

This page refreshes automatically to show the latest message activity delivery rates, details, and reports.
You can watch it in progress or check back later.

+ {% endif %}
{% if not job.finished_processing %}
Date: Fri, 9 May 2025 11:06:39 -0400 Subject: [PATCH 06/14] Update test_jobs.py --- tests/app/main/views/test_jobs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py index ab09f7ecd..beff6a868 100644 --- a/tests/app/main/views/test_jobs.py +++ b/tests/app/main/views/test_jobs.py @@ -237,11 +237,11 @@ def test_should_show_job_with_sending_limit_exceeded_status( job_id=fake_uuid, ) - assert normalize_spaces(page.select("main p")[2].text) == ( + assert normalize_spaces(page.select("main p")[3].text) == ( "Notify cannot send these messages because you have reached a limit. " "You can only send 1,000 messages per day and 250,000 messages in total." ) - assert normalize_spaces(page.select("main p")[3].text) == ( + assert normalize_spaces(page.select("main p")[4].text) == ( "Upload this spreadsheet again tomorrow or contact the Notify.gov team to raise the limit." ) From be0dcd103f439b9dcc6176da5360a221e2075c41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 00:18:34 +0000 Subject: [PATCH 07/14] Bump sass-embedded from 1.87.0 to 1.88.0 Bumps [sass-embedded](https://github.com/sass/embedded-host-node) from 1.87.0 to 1.88.0. - [Changelog](https://github.com/sass/embedded-host-node/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/embedded-host-node/compare/1.87.0...1.88.0) --- updated-dependencies: - dependency-name: sass-embedded dependency-version: 1.88.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 168 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 85 insertions(+), 85 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1f7b00ce2..e5fa946ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "playwright": "^1.52.0", "python": "^0.0.4", "query-command-supported": "1.0.0", - "sass-embedded": "^1.87.0", + "sass-embedded": "^1.88.0", "socket.io-client": "^4.8.1", "textarea-caret": "3.1.0", "vinyl-buffer": "^1.0.1", @@ -11638,9 +11638,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass-embedded": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.87.0.tgz", - "integrity": "sha512-1IA3iTJNh4BkkA/nidKiVwbmkxr9o6LsPegycHMX/JYs255zpocN5GdLF1+onohQCJxbs5ldr8osKV7qNaNBjg==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.88.0.tgz", + "integrity": "sha512-GQUxgZFuej3NZ1TSPUHU8aebtYdnIeXqYsbNEEKBtE+SC7/Gr18KH1ijTAZHPw25OUfQCdtJaRy6Fo866dHmgw==", "license": "MIT", "dependencies": { "@bufbuild/protobuf": "^2.0.0", @@ -11659,32 +11659,32 @@ "node": ">=16.0.0" }, "optionalDependencies": { - "sass-embedded-android-arm": "1.87.0", - "sass-embedded-android-arm64": "1.87.0", - "sass-embedded-android-ia32": "1.87.0", - "sass-embedded-android-riscv64": "1.87.0", - "sass-embedded-android-x64": "1.87.0", - "sass-embedded-darwin-arm64": "1.87.0", - "sass-embedded-darwin-x64": "1.87.0", - "sass-embedded-linux-arm": "1.87.0", - "sass-embedded-linux-arm64": "1.87.0", - "sass-embedded-linux-ia32": "1.87.0", - "sass-embedded-linux-musl-arm": "1.87.0", - "sass-embedded-linux-musl-arm64": "1.87.0", - "sass-embedded-linux-musl-ia32": "1.87.0", - "sass-embedded-linux-musl-riscv64": "1.87.0", - "sass-embedded-linux-musl-x64": "1.87.0", - "sass-embedded-linux-riscv64": "1.87.0", - "sass-embedded-linux-x64": "1.87.0", - "sass-embedded-win32-arm64": "1.87.0", - "sass-embedded-win32-ia32": "1.87.0", - "sass-embedded-win32-x64": "1.87.0" + "sass-embedded-android-arm": "1.88.0", + "sass-embedded-android-arm64": "1.88.0", + "sass-embedded-android-ia32": "1.88.0", + "sass-embedded-android-riscv64": "1.88.0", + "sass-embedded-android-x64": "1.88.0", + "sass-embedded-darwin-arm64": "1.88.0", + "sass-embedded-darwin-x64": "1.88.0", + "sass-embedded-linux-arm": "1.88.0", + "sass-embedded-linux-arm64": "1.88.0", + "sass-embedded-linux-ia32": "1.88.0", + "sass-embedded-linux-musl-arm": "1.88.0", + "sass-embedded-linux-musl-arm64": "1.88.0", + "sass-embedded-linux-musl-ia32": "1.88.0", + "sass-embedded-linux-musl-riscv64": "1.88.0", + "sass-embedded-linux-musl-x64": "1.88.0", + "sass-embedded-linux-riscv64": "1.88.0", + "sass-embedded-linux-x64": "1.88.0", + "sass-embedded-win32-arm64": "1.88.0", + "sass-embedded-win32-ia32": "1.88.0", + "sass-embedded-win32-x64": "1.88.0" } }, "node_modules/sass-embedded-android-arm": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.87.0.tgz", - "integrity": "sha512-Z20u/Y1kFDpMbgiloR5YPLxNuMVeKQRC8e/n68oAAxf3u7rDSmNn2msi7USqgT1f2zdBBNawn/ifbFEla6JiHw==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.88.0.tgz", + "integrity": "sha512-jveGkHhHxJ2+GnNxl3OyhZAxR8YXJCSuj7JYzoVuFTxlsaFqFQwtUrvZro61xOVOrwfe8xMk2HE3ZEw6dolhBA==", "cpu": [ "arm" ], @@ -11698,9 +11698,9 @@ } }, "node_modules/sass-embedded-android-arm64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.87.0.tgz", - "integrity": "sha512-uqeZoBuXm3W2KhxolScAAfWOLHL21e50g7AxlLmG0he7WZsWw6e9kSnmq301iLIFp4kvmXYXbXbNKAeu9ItRYA==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.88.0.tgz", + "integrity": "sha512-YVdxVywlbXH74uomIcRsYLHF1644V+0per6YrfZndWicjfYnWqgbGq1xixdOzLxe3vac90RlsRNxTEb0VWlhmA==", "cpu": [ "arm64" ], @@ -11714,9 +11714,9 @@ } }, "node_modules/sass-embedded-android-ia32": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.87.0.tgz", - "integrity": "sha512-hSWTqo2Igdig528cUb1W1+emw9d1J4+nqOoR4tERS04zcwRRFNDiuBT0o5meV7nkEwE982F+h57YdcRXj8gTtg==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.88.0.tgz", + "integrity": "sha512-6C4o+lGFsYcUPGtCvOdFhFLQl1rrcBUNuC4DILDayI4bZeh3Y2CjonzCT4VNKPsOm7LFGf0OKQAZm+3/oXVIKg==", "cpu": [ "ia32" ], @@ -11730,9 +11730,9 @@ } }, "node_modules/sass-embedded-android-riscv64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.87.0.tgz", - "integrity": "sha512-kBAPSjiTBLy5ua/0LRNAJwOAARhzFU7gP35fYORJcdBuz1lkIVPVnid1lh9qQ6Ce9MOJcr7VKFtGnTuqVeig5A==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.88.0.tgz", + "integrity": "sha512-zW1NmFHwPkBBg8wqVu8e5uCKeuTSk8vasB5BBEPvQubj4tWbgxrXGIVrQyseeGXJJQYSzjNiq3ua4qNoadBWJA==", "cpu": [ "riscv64" ], @@ -11746,9 +11746,9 @@ } }, "node_modules/sass-embedded-android-x64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.87.0.tgz", - "integrity": "sha512-ZHMrNdtdMSpJUYco2MesnlPwDTZftD3pqkkOMI2pbqarPoFUKJtP5k80nwCM0sJGtqfNE+O16w9yPght0CMiJg==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.88.0.tgz", + "integrity": "sha512-b33Ja8sU67CcWCX9C3M+k8AcWXOb9uhyUJuKg/2hb/RhKUqBRCpMtQhsChpV7/DyXvyevLeosy28j673qNfnuQ==", "cpu": [ "x64" ], @@ -11762,9 +11762,9 @@ } }, "node_modules/sass-embedded-darwin-arm64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.87.0.tgz", - "integrity": "sha512-7TK1JWJdCIRSdZv5CJv/HpDz/wIfwUy2FoPz9sVOEj1pDTH0N+VfJd5VutCddIdoQN9jr0ap8vwkc65FbAxV2A==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.88.0.tgz", + "integrity": "sha512-Zu+A4OzoFtZwTlcXn66ovZRTI9Ia610KJbtJBrpsXPfqR9QcCg7pPDB/zlPK5E5xFjsxGWaL0tICOifim1HCMg==", "cpu": [ "arm64" ], @@ -11778,9 +11778,9 @@ } }, "node_modules/sass-embedded-darwin-x64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.87.0.tgz", - "integrity": "sha512-2JiQzt7FmgUC4MYT2QvbeH/Bi3e76WEhaYoc5P3WyTW8unsHksyTdMuTuYe0Qf9usIyt6bmm5no/4BBw7c8Cig==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.88.0.tgz", + "integrity": "sha512-nZ+/j5Z4llLejNyFcLUWJvbU3WNJDKiyZ7W+Hpn/52dDhzHiNWRVHH7humfzCEgLXZctPZlr56ubaNk/RsoSlA==", "cpu": [ "x64" ], @@ -11794,9 +11794,9 @@ } }, "node_modules/sass-embedded-linux-arm": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.87.0.tgz", - "integrity": "sha512-z5P6INMsGXiUcq1sRRbksyQUhalFFYjTEexuxfSYdK3U2YQMADHubQh8pGzkWvFRPOpnh83RiGuwvpaARYHnsw==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.88.0.tgz", + "integrity": "sha512-bjiTZ4MNvArReXgwnA56mT3i+vHH3BgkLQT3qVwRv6fVTPQpYopK8D/QzQKbrVGYKgzWPYzZfksSQFC9lzM2yA==", "cpu": [ "arm" ], @@ -11810,9 +11810,9 @@ } }, "node_modules/sass-embedded-linux-arm64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.87.0.tgz", - "integrity": "sha512-5z+mwJCbGZcg+q+MwdEVSh0ogFK7OSAe175Gsozzr/Izw34Q+RGUw9O82jsV2c4YNuTAQvzEHgIO5cvNvt3Quw==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.88.0.tgz", + "integrity": "sha512-aphDl0Z4Y+YpPAqT0fEDDxZfrTXS/v36IRpGpVcbuRIua/iHd9L3wrZuwco1nbbY+sShFNiXPE1A9/k/ZGt8rw==", "cpu": [ "arm64" ], @@ -11826,9 +11826,9 @@ } }, "node_modules/sass-embedded-linux-ia32": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.87.0.tgz", - "integrity": "sha512-Xzcp+YPp0iakGL148Jl57CO+MxLuj2jsry3M+rc1cSnDlvkjNVs6TMxaL70GFeV5HdU2V60voYcgE7adDUtJjw==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.88.0.tgz", + "integrity": "sha512-m+pQMD14JQeMlQ/J8vQxHXwAQPAcfLG034BQz05a8ahXmNrk9qJkrC7FLptDlhsJ6weldX54UvXceoSpw2VsxQ==", "cpu": [ "ia32" ], @@ -11842,9 +11842,9 @@ } }, "node_modules/sass-embedded-linux-musl-arm": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.87.0.tgz", - "integrity": "sha512-4PyqOWhRzyu06RRmpCCBOJdF4BOv7s446wrV6yODtEyyfSIDx3MJabo3KT0oJ1lTWSI/aU3R89bKx0JFXcIHHw==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.88.0.tgz", + "integrity": "sha512-jGRZZYP8XOiE521Pep2u9ktx1FFkLHosjO7Dj/0pvjwUddBVT16jE40gv9pqtTynG0saD8jokqdkqJ+FM3NJzA==", "cpu": [ "arm" ], @@ -11858,9 +11858,9 @@ } }, "node_modules/sass-embedded-linux-musl-arm64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.87.0.tgz", - "integrity": "sha512-HWE5eTRCoKzFZWsxOjDMTF5m4DDTQ0n7NJxSYiUXPBDydr9viPXbGOMYG7WVJLjiF7upr7DYo/mfp/SNTMlZyg==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.88.0.tgz", + "integrity": "sha512-Wxo9qklXqw+eYFHLo+uE9r9sbK/xklMt6xPU/HXs+ikoJcGtmugE7KRyyWeSfvPTi8jZvgfkFfNDZD9elzxEFA==", "cpu": [ "arm64" ], @@ -11874,9 +11874,9 @@ } }, "node_modules/sass-embedded-linux-musl-ia32": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.87.0.tgz", - "integrity": "sha512-aQaPvlRn3kh93PLQvl6BcFKu8Ji92+42blFEkg6nMVvmugD5ZwH2TGFrX25ibx4CYxRpMS4ssF7a0i7vy5HB1Q==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.88.0.tgz", + "integrity": "sha512-utdTihiPCCP5HdKqwblQQWz864c7CqSplSGQ+p06GS+0ZfnuB/SKhtwe8fd11v4+IN8S2o0HAQ5KtWmRmk3eTA==", "cpu": [ "ia32" ], @@ -11890,9 +11890,9 @@ } }, "node_modules/sass-embedded-linux-musl-riscv64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.87.0.tgz", - "integrity": "sha512-o5DxcqiFzET3KRWo+futHr/lhAMBP3tJGGx8YIgpHQYfvDMbsvE0hiFC+nZ/GF9dbcGd+ceIQwfvE5mcc7Gsjw==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.88.0.tgz", + "integrity": "sha512-P8XB7QVSU8KJry4oxegzAnuFVWjbHc/JCHgF2ktq2dURVyxcaKDfQZtzbUgiPOKP/R6MZIFhXaJVJIhppcruEQ==", "cpu": [ "riscv64" ], @@ -11906,9 +11906,9 @@ } }, "node_modules/sass-embedded-linux-musl-x64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.87.0.tgz", - "integrity": "sha512-dKxWsu9Wu/CyfzQmHdeiGqrRSzJ85VUjbSx+aP1/7ttmps3SSg+YW95PuqnCOa7GSuSreC3dKKpXHTywUxMLQA==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.88.0.tgz", + "integrity": "sha512-OGEfD6AAm68vZTazFkIN7Dsu0ZQY983GZU+mWE9zZPLTIBzvNrrEZrEE/mpM6LemkwbqR+GaFP6rxGrkDz0Mhw==", "cpu": [ "x64" ], @@ -11922,9 +11922,9 @@ } }, "node_modules/sass-embedded-linux-riscv64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.87.0.tgz", - "integrity": "sha512-Sy3ESZ4FwBiijvmTA9n+0p0w3MNCue1AgINVPzpAY27EFi0h49eqQm9SWfOkFqmkFS2zFRYowdQOr5Bbr2gOXA==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.88.0.tgz", + "integrity": "sha512-3hBlfq4bXx0RkkNxvw/FPZSmUC1GMU8NE1Ef+2dJowxAeneRotHy5WXZIMKvH7NGpskf7U8ButK05U3OxPzrTA==", "cpu": [ "riscv64" ], @@ -11938,9 +11938,9 @@ } }, "node_modules/sass-embedded-linux-x64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.87.0.tgz", - "integrity": "sha512-+UfjakOcHHKTnEqB3EZ+KqzezQOe1emvy4Rs+eQhLyfekpYuNze/qlRvYxfKTmrtvDiUrIto8MXsyZfMLzkuMA==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.88.0.tgz", + "integrity": "sha512-FzM5mCxkFE20efDDSPO5N5o0ZKPqs51zowt2JAe5tdAzmy/jUQ0t515tph40dV2mfX0flBJgoou76gZKhylHGg==", "cpu": [ "x64" ], @@ -11954,9 +11954,9 @@ } }, "node_modules/sass-embedded-win32-arm64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.87.0.tgz", - "integrity": "sha512-m1DS6FYUE0/fv+vt38uQB/kxR4UjnyD+2zcSc298pFmA0aYh/XZIPWw7RxG1HL3KLE1ZrGyu3254MPoxRhs3ig==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.88.0.tgz", + "integrity": "sha512-Zp3yNEzk/gCCBIClQx8ihAGZ1YqPbjWjTnLWtruS9FcVrkrSAIjhqaesoN1Hy61aaIoiRektOyeffHH54jiQ3g==", "cpu": [ "arm64" ], @@ -11970,9 +11970,9 @@ } }, "node_modules/sass-embedded-win32-ia32": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.87.0.tgz", - "integrity": "sha512-JztXLo59GMe2E6g+kCsyiERYhtZgkcyDYx6CrXoSTE5WaE+RbxRiCCCv8/1+hf406f08pUxJ8G0Ody7M5urtBA==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.88.0.tgz", + "integrity": "sha512-yUmD6BLb01ngw/gy+FcTdsCMFaoONGFYJcy6FhMr2OOcCHNjPVD+HqTF4ZRsLNbwna8PlP6XxHFzjPKzVw18xw==", "cpu": [ "ia32" ], @@ -11986,9 +11986,9 @@ } }, "node_modules/sass-embedded-win32-x64": { - "version": "1.87.0", - "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.87.0.tgz", - "integrity": "sha512-4nQErpauvhgSo+7ClumGdjdf9sGx+U9yBgvhI0+zUw+D5YvraVgvA0Lk8Wuwntx2PqnvKUk8YDr/vxHJostv4Q==", + "version": "1.88.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.88.0.tgz", + "integrity": "sha512-j4pOP/S9vD4enRqbfwno07Xx+j0RkfVYGV31ZxzAIF+a1+3dDBlsbwgDNP68XemJx5SjpP8yM8the6nHAnMUiQ==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index e7fa111ea..0d50e9c51 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "playwright": "^1.52.0", "python": "^0.0.4", "query-command-supported": "1.0.0", - "sass-embedded": "^1.87.0", + "sass-embedded": "^1.88.0", "socket.io-client": "^4.8.1", "textarea-caret": "3.1.0", "vinyl-buffer": "^1.0.1", From 4728ee20e890a9e479961968242ecf10dcfd9f8c Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 13 May 2025 15:35:31 -0700 Subject: [PATCH 08/14] reverting pr 2484 because it breaks personalization --- app/s3_client/s3_csv_client.py | 9 --------- tests/app/s3_client/test_s3_csv_client.py | 10 +--------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/app/s3_client/s3_csv_client.py b/app/s3_client/s3_csv_client.py index 4d8f33a07..195ea3032 100644 --- a/app/s3_client/s3_csv_client.py +++ b/app/s3_client/s3_csv_client.py @@ -28,17 +28,8 @@ def get_csv_upload(service_id, upload_id): return get_s3_object(*get_csv_location(service_id, upload_id)) -def remove_blank_lines(filedata): - # sometimes people upload files with hundreds of blank lines at the end - data = filedata["data"] - cleaned_data = "\n".join(line for line in data.splitlines() if line.strip()) - filedata["data"] = cleaned_data - return filedata - - def s3upload(service_id, filedata): - filedata = remove_blank_lines(filedata) upload_id = str(uuid.uuid4()) bucket_name, file_location, access_key, secret_key, region = get_csv_location( service_id, upload_id diff --git a/tests/app/s3_client/test_s3_csv_client.py b/tests/app/s3_client/test_s3_csv_client.py index 2ea3c43db..dbf26ea47 100644 --- a/tests/app/s3_client/test_s3_csv_client.py +++ b/tests/app/s3_client/test_s3_csv_client.py @@ -1,6 +1,6 @@ from unittest.mock import Mock -from app.s3_client.s3_csv_client import remove_blank_lines, set_metadata_on_csv_upload +from app.s3_client.s3_csv_client import set_metadata_on_csv_upload def test_sets_metadata(client_request, mocker): @@ -21,11 +21,3 @@ def test_sets_metadata(client_request, mocker): MetadataDirective="REPLACE", ServerSideEncryption="AES256", ) - - -def test_removes_blank_lines(): - filedata = { - "data": "phone number\r\n15555555555\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n" - } - file_data = remove_blank_lines(filedata) - assert file_data == {"data": "phone number\n15555555555"} From c5d8792161f56caf44f2a3d05028c4c8134a8b9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 May 2025 13:41:23 +0000 Subject: [PATCH 09/14] Bump redis from 5.2.1 to 6.1.0 Bumps [redis](https://github.com/redis/redis-py) from 5.2.1 to 6.1.0. - [Release notes](https://github.com/redis/redis-py/releases) - [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES) - [Commits](https://github.com/redis/redis-py/compare/v5.2.1...v6.1.0) --- updated-dependencies: - dependency-name: redis dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- poetry.lock | 11 ++++++----- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index b9f9889bf..453fcc318 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3299,19 +3299,20 @@ all = ["numpy"] [[package]] name = "redis" -version = "5.2.1" +version = "6.1.0" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, - {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, + {file = "redis-6.1.0-py3-none-any.whl", hash = "sha256:3b72622f3d3a89df2a6041e82acd896b0e67d9f54e9bcd906d091d23ba5219f6"}, + {file = "redis-6.1.0.tar.gz", hash = "sha256:c928e267ad69d3069af28a9823a07726edf72c7e37764f43dc0123f37928c075"}, ] [package.extras] hiredis = ["hiredis (>=3.0.0)"] -ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"] +jwt = ["pyjwt (>=2.9.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (>=20.0.1)", "requests (>=2.31.0)"] [[package]] name = "regex" @@ -4156,4 +4157,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = "^3.12.2" -content-hash = "ccccb42b7f0cdf453d1bf1ae179c16a000431c22ca189e4ff1cc15f79fd79b37" +content-hash = "11e4f1ccc6f68eee6cbcea9566be68e88c4c2fd3b32dbc980cde2d699ec8e277" diff --git a/pyproject.toml b/pyproject.toml index b5090b037..fc8991734 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ ordered-set = "^4.1.0" phonenumbers = "^9.0.5" pycparser = "^2.22" python-json-logger = "^3.3.0" -redis = "^5.2.1" +redis = "^6.1.0" regex = "^2024.11.6" s3transfer = "^0.10.2" shapely = "^2.0.5" From bd884012d4ba6d27c307db9652110b4a2ec4ba26 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Wed, 14 May 2025 09:58:52 -0700 Subject: [PATCH 10/14] added a second test to preserve the \r\n formatting for line endings --- app/s3_client/s3_csv_client.py | 9 +++++++++ tests/app/s3_client/test_s3_csv_client.py | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/s3_client/s3_csv_client.py b/app/s3_client/s3_csv_client.py index 195ea3032..426191aec 100644 --- a/app/s3_client/s3_csv_client.py +++ b/app/s3_client/s3_csv_client.py @@ -28,8 +28,17 @@ def get_csv_upload(service_id, upload_id): return get_s3_object(*get_csv_location(service_id, upload_id)) +def remove_blank_lines(filedata): + # sometimes people upload files with hundreds of blank lines at the end + data = filedata["data"] + cleaned_data = "\r\n".join(line for line in data.splitlines() if line.strip()) + filedata["data"] = cleaned_data + return filedata + + def s3upload(service_id, filedata): + filedata = remove_blank_lines(filedata) upload_id = str(uuid.uuid4()) bucket_name, file_location, access_key, secret_key, region = get_csv_location( service_id, upload_id diff --git a/tests/app/s3_client/test_s3_csv_client.py b/tests/app/s3_client/test_s3_csv_client.py index dbf26ea47..28186cec4 100644 --- a/tests/app/s3_client/test_s3_csv_client.py +++ b/tests/app/s3_client/test_s3_csv_client.py @@ -1,6 +1,6 @@ from unittest.mock import Mock -from app.s3_client.s3_csv_client import set_metadata_on_csv_upload +from app.s3_client.s3_csv_client import remove_blank_lines, set_metadata_on_csv_upload def test_sets_metadata(client_request, mocker): @@ -21,3 +21,9 @@ def test_sets_metadata(client_request, mocker): MetadataDirective="REPLACE", ServerSideEncryption="AES256", ) + + +def test_removes_blank_lines(): + filedata = { "data": "variable,phone number\r\ntest,+15555555555\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n" } + file_data = remove_blank_lines(filedata) + assert file_data == {"data": "variable,phone number\r\ntest,+15555555555"} From 3f944e244733fa85a2156e2b1e294168e37f440c Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Wed, 14 May 2025 10:07:29 -0700 Subject: [PATCH 11/14] flake8 --- tests/app/s3_client/test_s3_csv_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/app/s3_client/test_s3_csv_client.py b/tests/app/s3_client/test_s3_csv_client.py index 28186cec4..b2671f738 100644 --- a/tests/app/s3_client/test_s3_csv_client.py +++ b/tests/app/s3_client/test_s3_csv_client.py @@ -24,6 +24,6 @@ def test_sets_metadata(client_request, mocker): def test_removes_blank_lines(): - filedata = { "data": "variable,phone number\r\ntest,+15555555555\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n" } + filedata = {"data": "variable,phone number\r\ntest,+15555555555\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"} file_data = remove_blank_lines(filedata) assert file_data == {"data": "variable,phone number\r\ntest,+15555555555"} From 8aa7a63f552c5f5e2296fc23e0383d387180d9b0 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Wed, 14 May 2025 10:08:02 -0700 Subject: [PATCH 12/14] reformatted --- app/main/views/send.py | 36 ++++++++++++----------- app/main/views/tour.py | 12 ++++---- tests/app/main/views/test_send.py | 6 ++-- tests/app/s3_client/test_s3_csv_client.py | 4 ++- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/app/main/views/send.py b/app/main/views/send.py index 61e0c0083..b1b393a90 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -566,18 +566,18 @@ def _check_messages(service_id, template_id, upload_id, preview_row, **kwargs): "url": url_for( "main.send_one_off", service_id=service_id, template_id=template.id ), - "text": "Back to message personalization" + "text": "Back to message personalization", }, - "html": "Back to message personalization" + "html": "Back to message personalization", } back_link_from_preview = { "href": { "url": url_for( "main.send_one_off", service_id=service_id, template_id=template.id ), - "text": "Back to message personalization" + "text": "Back to message personalization", }, - "html": "Back to message personalization" + "html": "Back to message personalization", } choose_time_form = None else: @@ -586,9 +586,9 @@ def _check_messages(service_id, template_id, upload_id, preview_row, **kwargs): "url": url_for( "main.send_messages", service_id=service_id, template_id=template.id ), - "text": "Back to upload a file" + "text": "Back to upload a file", }, - "html": "Back to upload a file" + "html": "Back to upload a file", } back_link_from_preview = { "href": { @@ -598,9 +598,9 @@ def _check_messages(service_id, template_id, upload_id, preview_row, **kwargs): template_id=template.id, upload_id=upload_id, ), - "text": "Back to check messages" + "text": "Back to check messages", }, - "html": "Back to check messages" + "html": "Back to check messages", } choose_time_form = ChooseTimeForm() @@ -786,9 +786,9 @@ def get_back_link( service_id=service_id, template_id=template.id, ), - "text": "Back to select delivery time" + "text": "Back to select delivery time", }, - "html": "Back to select delivery time" + "html": "Back to select delivery time", } if step_index == 0: @@ -799,9 +799,9 @@ def get_back_link( ".choose_template", service_id=service_id, ), - "text": "Back to all templates" + "text": "Back to all templates", }, - "html": "Back to all templates" + "html": "Back to all templates", } else: return { @@ -811,14 +811,16 @@ def get_back_link( service_id=service_id, template_id=template.id, ), - "text": "Back to confirm your template" + "text": "Back to confirm your template", }, - "html": "Back to confirm your template" + "html": "Back to confirm your template", } # fallback for other steps back_to_text = ( - "Back to select recipients" if step_index == 1 else "Back to message personalization" + "Back to select recipients" + if step_index == 1 + else "Back to message personalization" ) return { @@ -829,9 +831,9 @@ def get_back_link( template_id=template.id, step_index=step_index - 1, ), - "text": back_to_text + "text": back_to_text, }, - "html": back_to_text + "html": back_to_text, } diff --git a/app/main/views/tour.py b/app/main/views/tour.py index e253167c8..d42ff1edf 100644 --- a/app/main/views/tour.py +++ b/app/main/views/tour.py @@ -142,13 +142,11 @@ def _get_tour_step_back_link(service_id, template_id, step_index): return { "href": { "url": url_for( - 'main.begin_tour', - service_id=service_id, - template_id=template_id + "main.begin_tour", service_id=service_id, template_id=template_id ), - "text": "Back to tour start" + "text": "Back to tour start", }, - "html": "Back to tour start" + "html": "Back to tour start", } else: return { @@ -207,9 +205,9 @@ def check_tour_notification(service_id, template_id): template_id=template_id, step_index=len(placeholders), ), - "text": "Back to previous step" + "text": "Back to previous step", }, - "html": "Back to previous step" + "html": "Back to previous step", } template.values = get_recipient_and_placeholders_from_session( diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py index a18909070..11104325a 100644 --- a/tests/app/main/views/test_send.py +++ b/tests/app/main/views/test_send.py @@ -1451,7 +1451,7 @@ def test_send_one_off_offers_link_to_upload( assert back_link.text.strip() in { "Back to all templates", - "Back to confirm your template" + "Back to confirm your template", } assert link.text.strip() == "Upload a list of phone numbers" @@ -2288,7 +2288,9 @@ def test_check_messages_back_link( actual_href = page.find_all("a", {"class": "usa-back-link"})[0]["href"] expected_href = expected_url(service_id=SERVICE_ONE_ID, template_id=fake_uuid) - assert actual_href != "#", "Back link href fell back to '#' — missing correct back_link in view" + assert ( + actual_href != "#" + ), "Back link href fell back to '#' — missing correct back_link in view" assert actual_href == expected_href diff --git a/tests/app/s3_client/test_s3_csv_client.py b/tests/app/s3_client/test_s3_csv_client.py index b2671f738..f5a89bee4 100644 --- a/tests/app/s3_client/test_s3_csv_client.py +++ b/tests/app/s3_client/test_s3_csv_client.py @@ -24,6 +24,8 @@ def test_sets_metadata(client_request, mocker): def test_removes_blank_lines(): - filedata = {"data": "variable,phone number\r\ntest,+15555555555\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"} + filedata = { + "data": "variable,phone number\r\ntest,+15555555555\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n" + } file_data = remove_blank_lines(filedata) assert file_data == {"data": "variable,phone number\r\ntest,+15555555555"} From 0e724eda99de14e21839092ce4b40ecc724a84d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 15:36:13 +0000 Subject: [PATCH 13/14] Bump exceptiongroup from 1.2.2 to 1.3.0 Bumps [exceptiongroup](https://github.com/agronholm/exceptiongroup) from 1.2.2 to 1.3.0. - [Release notes](https://github.com/agronholm/exceptiongroup/releases) - [Changelog](https://github.com/agronholm/exceptiongroup/blob/main/CHANGES.rst) - [Commits](https://github.com/agronholm/exceptiongroup/compare/1.2.2...1.3.0) --- updated-dependencies: - dependency-name: exceptiongroup dependency-version: 1.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 11 +++++++---- pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 453fcc318..815e61ab5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -946,16 +946,19 @@ dev = ["black", "build", "commitizen", "isort", "pip-tools", "pre-commit", "twin [[package]] name = "exceptiongroup" -version = "1.2.2" +version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + [package.extras] test = ["pytest (>=6)"] @@ -4157,4 +4160,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = "^3.12.2" -content-hash = "11e4f1ccc6f68eee6cbcea9566be68e88c4c2fd3b32dbc980cde2d699ec8e277" +content-hash = "c76fcbea5f1b4889627d3131e0c4a4a6eea220e98bc48bb3919888e37eab73b1" diff --git a/pyproject.toml b/pyproject.toml index fc8991734..bc8ca8e8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ python = "^3.12.2" ago = "~=0.1.0" beautifulsoup4 = "^4.13.3" blinker = "~=1.8" -exceptiongroup = "==1.2.2" +exceptiongroup = "==1.3.0" flask = "~=3.1" flask-basicauth = "~=0.2" flask-login = "^0.6" From 20fcf8fa3b4c140415619f4d4b92ba1ab4554a8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 15:45:45 +0000 Subject: [PATCH 14/14] Bump click from 8.1.8 to 8.2.0 Bumps [click](https://github.com/pallets/click) from 8.1.8 to 8.2.0. - [Release notes](https://github.com/pallets/click/releases) - [Changelog](https://github.com/pallets/click/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/click/compare/8.1.8...8.2.0) --- updated-dependencies: - dependency-name: click dependency-version: 8.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 815e61ab5..b0156f5ec 100644 --- a/poetry.lock +++ b/poetry.lock @@ -561,14 +561,14 @@ rapidfuzz = ">=3.0.0,<4.0.0" [[package]] name = "click" -version = "8.1.8" +version = "8.2.0" description = "Composable command line interface toolkit" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, + {file = "click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c"}, + {file = "click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d"}, ] [package.dependencies] @@ -4160,4 +4160,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = "^3.12.2" -content-hash = "c76fcbea5f1b4889627d3131e0c4a4a6eea220e98bc48bb3919888e37eab73b1" +content-hash = "2ddadc92879367416e52530f2d4355c5e687d38e8a1cb1088b534f13e7de0513" diff --git a/pyproject.toml b/pyproject.toml index bc8ca8e8a..a28af54d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ shapely = "^2.0.5" smartypants = "^2.0.1" certifi = "^2025.4.26" charset-normalizer = "^3.4.2" -click = "^8.1.8" +click = "^8.2.0" idna = "^3.7" markupsafe = "^3.0.2" python-dateutil = "^2.9.0.post0"