From c6e17bcc3251d90c422f21b8c45ae04fa132764b Mon Sep 17 00:00:00 2001 From: Trey <73353716+TreyWW@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:07:39 +0000 Subject: [PATCH 01/21] Initial Signed-off-by: Trey <73353716+TreyWW@users.noreply.github.com> --- backend/context_processors.py | 2 +- frontend/templates/pages/dashboard.html | 2 +- poetry.lock | 2178 ++++++++++++----------- pyproject.toml | 1 + settings/settings.py | 1 + 5 files changed, 1140 insertions(+), 1044 deletions(-) diff --git a/backend/context_processors.py b/backend/context_processors.py index 6b81f2090..ddf02044f 100644 --- a/backend/context_processors.py +++ b/backend/context_processors.py @@ -64,7 +64,7 @@ def get_git_revision(base_path): data["day_names_monday_first"] = [day for day in calendar.day_name] if hasattr(request, "htmx") and request.htmx.boosted: - data["base"] = "base/htmx.html" + data["base"] = "core/htmx.html" return data diff --git a/frontend/templates/pages/dashboard.html b/frontend/templates/pages/dashboard.html index f3dd59102..45d3b33b1 100644 --- a/frontend/templates/pages/dashboard.html +++ b/frontend/templates/pages/dashboard.html @@ -1,4 +1,4 @@ -{% extends base|default:"base/base.html" %} +{% extends base|default:"core/base.html" %} {% block content %}
diff --git a/poetry.lock b/poetry.lock index 3fd0cb95b..e920c78e3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -70,13 +70,13 @@ files = [ [[package]] name = "async-timeout" -version = "4.0.3" +version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, ] [[package]] @@ -129,33 +129,33 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "black" -version = "24.8.0" +version = "24.10.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, - {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, - {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, - {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, - {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, - {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, - {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, - {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, - {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, - {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, - {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, - {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, - {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, - {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, - {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, - {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, - {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, - {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, - {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, - {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, - {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, - {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, ] [package.dependencies] @@ -169,7 +169,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -213,13 +213,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "boto3-stubs" -version = "1.35.29" -description = "Type annotations for boto3 1.35.29 generated with mypy-boto3-builder 8.1.2" +version = "1.35.76" +description = "Type annotations for boto3 1.35.76 generated with mypy-boto3-builder 8.5.0" optional = false python-versions = ">=3.8" files = [ - {file = "boto3_stubs-1.35.29-py3-none-any.whl", hash = "sha256:048e664389c3fb53e8ab0f810eb280ba02c2f8213a63dc5d3da075ffd54b4504"}, - {file = "boto3_stubs-1.35.29.tar.gz", hash = "sha256:6e5f082f7cd028bdf3bfc57c9db3b784e0f6ec2232b10482859a919d6cd6bfc9"}, + {file = "boto3_stubs-1.35.76-py3-none-any.whl", hash = "sha256:882f69f02cca48176fa3adf7f354fe64a65268423c9696871d0f4d098af35431"}, + {file = "boto3_stubs-1.35.76.tar.gz", hash = "sha256:32109b6a0c9720bf7c2e389655479c6dab4ee33c622e2cf2746c9e5ec527bae3"}, ] [package.dependencies] @@ -238,7 +238,7 @@ accessanalyzer = ["mypy-boto3-accessanalyzer (>=1.35.0,<1.36.0)"] account = ["mypy-boto3-account (>=1.35.0,<1.36.0)"] acm = ["mypy-boto3-acm (>=1.35.0,<1.36.0)"] acm-pca = ["mypy-boto3-acm-pca (>=1.35.0,<1.36.0)"] -all = ["mypy-boto3-accessanalyzer (>=1.35.0,<1.36.0)", "mypy-boto3-account (>=1.35.0,<1.36.0)", "mypy-boto3-acm (>=1.35.0,<1.36.0)", "mypy-boto3-acm-pca (>=1.35.0,<1.36.0)", "mypy-boto3-amp (>=1.35.0,<1.36.0)", "mypy-boto3-amplify (>=1.35.0,<1.36.0)", "mypy-boto3-amplifybackend (>=1.35.0,<1.36.0)", "mypy-boto3-amplifyuibuilder (>=1.35.0,<1.36.0)", "mypy-boto3-apigateway (>=1.35.0,<1.36.0)", "mypy-boto3-apigatewaymanagementapi (>=1.35.0,<1.36.0)", "mypy-boto3-apigatewayv2 (>=1.35.0,<1.36.0)", "mypy-boto3-appconfig (>=1.35.0,<1.36.0)", "mypy-boto3-appconfigdata (>=1.35.0,<1.36.0)", "mypy-boto3-appfabric (>=1.35.0,<1.36.0)", "mypy-boto3-appflow (>=1.35.0,<1.36.0)", "mypy-boto3-appintegrations (>=1.35.0,<1.36.0)", "mypy-boto3-application-autoscaling (>=1.35.0,<1.36.0)", "mypy-boto3-application-insights (>=1.35.0,<1.36.0)", "mypy-boto3-application-signals (>=1.35.0,<1.36.0)", "mypy-boto3-applicationcostprofiler (>=1.35.0,<1.36.0)", "mypy-boto3-appmesh (>=1.35.0,<1.36.0)", "mypy-boto3-apprunner (>=1.35.0,<1.36.0)", "mypy-boto3-appstream (>=1.35.0,<1.36.0)", "mypy-boto3-appsync (>=1.35.0,<1.36.0)", "mypy-boto3-apptest (>=1.35.0,<1.36.0)", "mypy-boto3-arc-zonal-shift (>=1.35.0,<1.36.0)", "mypy-boto3-artifact (>=1.35.0,<1.36.0)", "mypy-boto3-athena (>=1.35.0,<1.36.0)", "mypy-boto3-auditmanager (>=1.35.0,<1.36.0)", "mypy-boto3-autoscaling (>=1.35.0,<1.36.0)", "mypy-boto3-autoscaling-plans (>=1.35.0,<1.36.0)", "mypy-boto3-b2bi (>=1.35.0,<1.36.0)", "mypy-boto3-backup (>=1.35.0,<1.36.0)", "mypy-boto3-backup-gateway (>=1.35.0,<1.36.0)", "mypy-boto3-batch (>=1.35.0,<1.36.0)", "mypy-boto3-bcm-data-exports (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-agent (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-agent-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-billingconductor (>=1.35.0,<1.36.0)", "mypy-boto3-braket (>=1.35.0,<1.36.0)", "mypy-boto3-budgets (>=1.35.0,<1.36.0)", "mypy-boto3-ce (>=1.35.0,<1.36.0)", "mypy-boto3-chatbot (>=1.35.0,<1.36.0)", "mypy-boto3-chime (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-identity (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-meetings (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-messaging (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-voice (>=1.35.0,<1.36.0)", "mypy-boto3-cleanrooms (>=1.35.0,<1.36.0)", "mypy-boto3-cleanroomsml (>=1.35.0,<1.36.0)", "mypy-boto3-cloud9 (>=1.35.0,<1.36.0)", "mypy-boto3-cloudcontrol (>=1.35.0,<1.36.0)", "mypy-boto3-clouddirectory (>=1.35.0,<1.36.0)", "mypy-boto3-cloudformation (>=1.35.0,<1.36.0)", "mypy-boto3-cloudfront (>=1.35.0,<1.36.0)", "mypy-boto3-cloudfront-keyvaluestore (>=1.35.0,<1.36.0)", "mypy-boto3-cloudhsm (>=1.35.0,<1.36.0)", "mypy-boto3-cloudhsmv2 (>=1.35.0,<1.36.0)", "mypy-boto3-cloudsearch (>=1.35.0,<1.36.0)", "mypy-boto3-cloudsearchdomain (>=1.35.0,<1.36.0)", "mypy-boto3-cloudtrail (>=1.35.0,<1.36.0)", "mypy-boto3-cloudtrail-data (>=1.35.0,<1.36.0)", "mypy-boto3-cloudwatch (>=1.35.0,<1.36.0)", "mypy-boto3-codeartifact (>=1.35.0,<1.36.0)", "mypy-boto3-codebuild (>=1.35.0,<1.36.0)", "mypy-boto3-codecatalyst (>=1.35.0,<1.36.0)", "mypy-boto3-codecommit (>=1.35.0,<1.36.0)", "mypy-boto3-codeconnections (>=1.35.0,<1.36.0)", "mypy-boto3-codedeploy (>=1.35.0,<1.36.0)", "mypy-boto3-codeguru-reviewer (>=1.35.0,<1.36.0)", "mypy-boto3-codeguru-security (>=1.35.0,<1.36.0)", "mypy-boto3-codeguruprofiler (>=1.35.0,<1.36.0)", "mypy-boto3-codepipeline (>=1.35.0,<1.36.0)", "mypy-boto3-codestar-connections (>=1.35.0,<1.36.0)", "mypy-boto3-codestar-notifications (>=1.35.0,<1.36.0)", "mypy-boto3-cognito-identity (>=1.35.0,<1.36.0)", "mypy-boto3-cognito-idp (>=1.35.0,<1.36.0)", "mypy-boto3-cognito-sync (>=1.35.0,<1.36.0)", "mypy-boto3-comprehend (>=1.35.0,<1.36.0)", "mypy-boto3-comprehendmedical (>=1.35.0,<1.36.0)", "mypy-boto3-compute-optimizer (>=1.35.0,<1.36.0)", "mypy-boto3-config (>=1.35.0,<1.36.0)", "mypy-boto3-connect (>=1.35.0,<1.36.0)", "mypy-boto3-connect-contact-lens (>=1.35.0,<1.36.0)", "mypy-boto3-connectcampaigns (>=1.35.0,<1.36.0)", "mypy-boto3-connectcases (>=1.35.0,<1.36.0)", "mypy-boto3-connectparticipant (>=1.35.0,<1.36.0)", "mypy-boto3-controlcatalog (>=1.35.0,<1.36.0)", "mypy-boto3-controltower (>=1.35.0,<1.36.0)", "mypy-boto3-cost-optimization-hub (>=1.35.0,<1.36.0)", "mypy-boto3-cur (>=1.35.0,<1.36.0)", "mypy-boto3-customer-profiles (>=1.35.0,<1.36.0)", "mypy-boto3-databrew (>=1.35.0,<1.36.0)", "mypy-boto3-dataexchange (>=1.35.0,<1.36.0)", "mypy-boto3-datapipeline (>=1.35.0,<1.36.0)", "mypy-boto3-datasync (>=1.35.0,<1.36.0)", "mypy-boto3-datazone (>=1.35.0,<1.36.0)", "mypy-boto3-dax (>=1.35.0,<1.36.0)", "mypy-boto3-deadline (>=1.35.0,<1.36.0)", "mypy-boto3-detective (>=1.35.0,<1.36.0)", "mypy-boto3-devicefarm (>=1.35.0,<1.36.0)", "mypy-boto3-devops-guru (>=1.35.0,<1.36.0)", "mypy-boto3-directconnect (>=1.35.0,<1.36.0)", "mypy-boto3-discovery (>=1.35.0,<1.36.0)", "mypy-boto3-dlm (>=1.35.0,<1.36.0)", "mypy-boto3-dms (>=1.35.0,<1.36.0)", "mypy-boto3-docdb (>=1.35.0,<1.36.0)", "mypy-boto3-docdb-elastic (>=1.35.0,<1.36.0)", "mypy-boto3-drs (>=1.35.0,<1.36.0)", "mypy-boto3-ds (>=1.35.0,<1.36.0)", "mypy-boto3-ds-data (>=1.35.0,<1.36.0)", "mypy-boto3-dynamodb (>=1.35.0,<1.36.0)", "mypy-boto3-dynamodbstreams (>=1.35.0,<1.36.0)", "mypy-boto3-ebs (>=1.35.0,<1.36.0)", "mypy-boto3-ec2 (>=1.35.0,<1.36.0)", "mypy-boto3-ec2-instance-connect (>=1.35.0,<1.36.0)", "mypy-boto3-ecr (>=1.35.0,<1.36.0)", "mypy-boto3-ecr-public (>=1.35.0,<1.36.0)", "mypy-boto3-ecs (>=1.35.0,<1.36.0)", "mypy-boto3-efs (>=1.35.0,<1.36.0)", "mypy-boto3-eks (>=1.35.0,<1.36.0)", "mypy-boto3-eks-auth (>=1.35.0,<1.36.0)", "mypy-boto3-elastic-inference (>=1.35.0,<1.36.0)", "mypy-boto3-elasticache (>=1.35.0,<1.36.0)", "mypy-boto3-elasticbeanstalk (>=1.35.0,<1.36.0)", "mypy-boto3-elastictranscoder (>=1.35.0,<1.36.0)", "mypy-boto3-elb (>=1.35.0,<1.36.0)", "mypy-boto3-elbv2 (>=1.35.0,<1.36.0)", "mypy-boto3-emr (>=1.35.0,<1.36.0)", "mypy-boto3-emr-containers (>=1.35.0,<1.36.0)", "mypy-boto3-emr-serverless (>=1.35.0,<1.36.0)", "mypy-boto3-entityresolution (>=1.35.0,<1.36.0)", "mypy-boto3-es (>=1.35.0,<1.36.0)", "mypy-boto3-events (>=1.35.0,<1.36.0)", "mypy-boto3-evidently (>=1.35.0,<1.36.0)", "mypy-boto3-finspace (>=1.35.0,<1.36.0)", "mypy-boto3-finspace-data (>=1.35.0,<1.36.0)", "mypy-boto3-firehose (>=1.35.0,<1.36.0)", "mypy-boto3-fis (>=1.35.0,<1.36.0)", "mypy-boto3-fms (>=1.35.0,<1.36.0)", "mypy-boto3-forecast (>=1.35.0,<1.36.0)", "mypy-boto3-forecastquery (>=1.35.0,<1.36.0)", "mypy-boto3-frauddetector (>=1.35.0,<1.36.0)", "mypy-boto3-freetier (>=1.35.0,<1.36.0)", "mypy-boto3-fsx (>=1.35.0,<1.36.0)", "mypy-boto3-gamelift (>=1.35.0,<1.36.0)", "mypy-boto3-glacier (>=1.35.0,<1.36.0)", "mypy-boto3-globalaccelerator (>=1.35.0,<1.36.0)", "mypy-boto3-glue (>=1.35.0,<1.36.0)", "mypy-boto3-grafana (>=1.35.0,<1.36.0)", "mypy-boto3-greengrass (>=1.35.0,<1.36.0)", "mypy-boto3-greengrassv2 (>=1.35.0,<1.36.0)", "mypy-boto3-groundstation (>=1.35.0,<1.36.0)", "mypy-boto3-guardduty (>=1.35.0,<1.36.0)", "mypy-boto3-health (>=1.35.0,<1.36.0)", "mypy-boto3-healthlake (>=1.35.0,<1.36.0)", "mypy-boto3-iam (>=1.35.0,<1.36.0)", "mypy-boto3-identitystore (>=1.35.0,<1.36.0)", "mypy-boto3-imagebuilder (>=1.35.0,<1.36.0)", "mypy-boto3-importexport (>=1.35.0,<1.36.0)", "mypy-boto3-inspector (>=1.35.0,<1.36.0)", "mypy-boto3-inspector-scan (>=1.35.0,<1.36.0)", "mypy-boto3-inspector2 (>=1.35.0,<1.36.0)", "mypy-boto3-internetmonitor (>=1.35.0,<1.36.0)", "mypy-boto3-iot (>=1.35.0,<1.36.0)", "mypy-boto3-iot-data (>=1.35.0,<1.36.0)", "mypy-boto3-iot-jobs-data (>=1.35.0,<1.36.0)", "mypy-boto3-iot1click-devices (>=1.35.0,<1.36.0)", "mypy-boto3-iot1click-projects (>=1.35.0,<1.36.0)", "mypy-boto3-iotanalytics (>=1.35.0,<1.36.0)", "mypy-boto3-iotdeviceadvisor (>=1.35.0,<1.36.0)", "mypy-boto3-iotevents (>=1.35.0,<1.36.0)", "mypy-boto3-iotevents-data (>=1.35.0,<1.36.0)", "mypy-boto3-iotfleethub (>=1.35.0,<1.36.0)", "mypy-boto3-iotfleetwise (>=1.35.0,<1.36.0)", "mypy-boto3-iotsecuretunneling (>=1.35.0,<1.36.0)", "mypy-boto3-iotsitewise (>=1.35.0,<1.36.0)", "mypy-boto3-iotthingsgraph (>=1.35.0,<1.36.0)", "mypy-boto3-iottwinmaker (>=1.35.0,<1.36.0)", "mypy-boto3-iotwireless (>=1.35.0,<1.36.0)", "mypy-boto3-ivs (>=1.35.0,<1.36.0)", "mypy-boto3-ivs-realtime (>=1.35.0,<1.36.0)", "mypy-boto3-ivschat (>=1.35.0,<1.36.0)", "mypy-boto3-kafka (>=1.35.0,<1.36.0)", "mypy-boto3-kafkaconnect (>=1.35.0,<1.36.0)", "mypy-boto3-kendra (>=1.35.0,<1.36.0)", "mypy-boto3-kendra-ranking (>=1.35.0,<1.36.0)", "mypy-boto3-keyspaces (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-archived-media (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-media (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-signaling (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.35.0,<1.36.0)", "mypy-boto3-kinesisanalytics (>=1.35.0,<1.36.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.35.0,<1.36.0)", "mypy-boto3-kinesisvideo (>=1.35.0,<1.36.0)", "mypy-boto3-kms (>=1.35.0,<1.36.0)", "mypy-boto3-lakeformation (>=1.35.0,<1.36.0)", "mypy-boto3-lambda (>=1.35.0,<1.36.0)", "mypy-boto3-launch-wizard (>=1.35.0,<1.36.0)", "mypy-boto3-lex-models (>=1.35.0,<1.36.0)", "mypy-boto3-lex-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-lexv2-models (>=1.35.0,<1.36.0)", "mypy-boto3-lexv2-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-license-manager (>=1.35.0,<1.36.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.35.0,<1.36.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.35.0,<1.36.0)", "mypy-boto3-lightsail (>=1.35.0,<1.36.0)", "mypy-boto3-location (>=1.35.0,<1.36.0)", "mypy-boto3-logs (>=1.35.0,<1.36.0)", "mypy-boto3-lookoutequipment (>=1.35.0,<1.36.0)", "mypy-boto3-lookoutmetrics (>=1.35.0,<1.36.0)", "mypy-boto3-lookoutvision (>=1.35.0,<1.36.0)", "mypy-boto3-m2 (>=1.35.0,<1.36.0)", "mypy-boto3-machinelearning (>=1.35.0,<1.36.0)", "mypy-boto3-macie2 (>=1.35.0,<1.36.0)", "mypy-boto3-mailmanager (>=1.35.0,<1.36.0)", "mypy-boto3-managedblockchain (>=1.35.0,<1.36.0)", "mypy-boto3-managedblockchain-query (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-agreement (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-catalog (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-deployment (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-entitlement (>=1.35.0,<1.36.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.35.0,<1.36.0)", "mypy-boto3-mediaconnect (>=1.35.0,<1.36.0)", "mypy-boto3-mediaconvert (>=1.35.0,<1.36.0)", "mypy-boto3-medialive (>=1.35.0,<1.36.0)", "mypy-boto3-mediapackage (>=1.35.0,<1.36.0)", "mypy-boto3-mediapackage-vod (>=1.35.0,<1.36.0)", "mypy-boto3-mediapackagev2 (>=1.35.0,<1.36.0)", "mypy-boto3-mediastore (>=1.35.0,<1.36.0)", "mypy-boto3-mediastore-data (>=1.35.0,<1.36.0)", "mypy-boto3-mediatailor (>=1.35.0,<1.36.0)", "mypy-boto3-medical-imaging (>=1.35.0,<1.36.0)", "mypy-boto3-memorydb (>=1.35.0,<1.36.0)", "mypy-boto3-meteringmarketplace (>=1.35.0,<1.36.0)", "mypy-boto3-mgh (>=1.35.0,<1.36.0)", "mypy-boto3-mgn (>=1.35.0,<1.36.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.35.0,<1.36.0)", "mypy-boto3-migrationhub-config (>=1.35.0,<1.36.0)", "mypy-boto3-migrationhuborchestrator (>=1.35.0,<1.36.0)", "mypy-boto3-migrationhubstrategy (>=1.35.0,<1.36.0)", "mypy-boto3-mq (>=1.35.0,<1.36.0)", "mypy-boto3-mturk (>=1.35.0,<1.36.0)", "mypy-boto3-mwaa (>=1.35.0,<1.36.0)", "mypy-boto3-neptune (>=1.35.0,<1.36.0)", "mypy-boto3-neptune-graph (>=1.35.0,<1.36.0)", "mypy-boto3-neptunedata (>=1.35.0,<1.36.0)", "mypy-boto3-network-firewall (>=1.35.0,<1.36.0)", "mypy-boto3-networkmanager (>=1.35.0,<1.36.0)", "mypy-boto3-networkmonitor (>=1.35.0,<1.36.0)", "mypy-boto3-nimble (>=1.35.0,<1.36.0)", "mypy-boto3-oam (>=1.35.0,<1.36.0)", "mypy-boto3-omics (>=1.35.0,<1.36.0)", "mypy-boto3-opensearch (>=1.35.0,<1.36.0)", "mypy-boto3-opensearchserverless (>=1.35.0,<1.36.0)", "mypy-boto3-opsworks (>=1.35.0,<1.36.0)", "mypy-boto3-opsworkscm (>=1.35.0,<1.36.0)", "mypy-boto3-organizations (>=1.35.0,<1.36.0)", "mypy-boto3-osis (>=1.35.0,<1.36.0)", "mypy-boto3-outposts (>=1.35.0,<1.36.0)", "mypy-boto3-panorama (>=1.35.0,<1.36.0)", "mypy-boto3-payment-cryptography (>=1.35.0,<1.36.0)", "mypy-boto3-payment-cryptography-data (>=1.35.0,<1.36.0)", "mypy-boto3-pca-connector-ad (>=1.35.0,<1.36.0)", "mypy-boto3-pca-connector-scep (>=1.35.0,<1.36.0)", "mypy-boto3-pcs (>=1.35.0,<1.36.0)", "mypy-boto3-personalize (>=1.35.0,<1.36.0)", "mypy-boto3-personalize-events (>=1.35.0,<1.36.0)", "mypy-boto3-personalize-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-pi (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint-email (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint-sms-voice (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.35.0,<1.36.0)", "mypy-boto3-pipes (>=1.35.0,<1.36.0)", "mypy-boto3-polly (>=1.35.0,<1.36.0)", "mypy-boto3-pricing (>=1.35.0,<1.36.0)", "mypy-boto3-privatenetworks (>=1.35.0,<1.36.0)", "mypy-boto3-proton (>=1.35.0,<1.36.0)", "mypy-boto3-qapps (>=1.35.0,<1.36.0)", "mypy-boto3-qbusiness (>=1.35.0,<1.36.0)", "mypy-boto3-qconnect (>=1.35.0,<1.36.0)", "mypy-boto3-qldb (>=1.35.0,<1.36.0)", "mypy-boto3-qldb-session (>=1.35.0,<1.36.0)", "mypy-boto3-quicksight (>=1.35.0,<1.36.0)", "mypy-boto3-ram (>=1.35.0,<1.36.0)", "mypy-boto3-rbin (>=1.35.0,<1.36.0)", "mypy-boto3-rds (>=1.35.0,<1.36.0)", "mypy-boto3-rds-data (>=1.35.0,<1.36.0)", "mypy-boto3-redshift (>=1.35.0,<1.36.0)", "mypy-boto3-redshift-data (>=1.35.0,<1.36.0)", "mypy-boto3-redshift-serverless (>=1.35.0,<1.36.0)", "mypy-boto3-rekognition (>=1.35.0,<1.36.0)", "mypy-boto3-repostspace (>=1.35.0,<1.36.0)", "mypy-boto3-resiliencehub (>=1.35.0,<1.36.0)", "mypy-boto3-resource-explorer-2 (>=1.35.0,<1.36.0)", "mypy-boto3-resource-groups (>=1.35.0,<1.36.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.35.0,<1.36.0)", "mypy-boto3-robomaker (>=1.35.0,<1.36.0)", "mypy-boto3-rolesanywhere (>=1.35.0,<1.36.0)", "mypy-boto3-route53 (>=1.35.0,<1.36.0)", "mypy-boto3-route53-recovery-cluster (>=1.35.0,<1.36.0)", "mypy-boto3-route53-recovery-control-config (>=1.35.0,<1.36.0)", "mypy-boto3-route53-recovery-readiness (>=1.35.0,<1.36.0)", "mypy-boto3-route53domains (>=1.35.0,<1.36.0)", "mypy-boto3-route53profiles (>=1.35.0,<1.36.0)", "mypy-boto3-route53resolver (>=1.35.0,<1.36.0)", "mypy-boto3-rum (>=1.35.0,<1.36.0)", "mypy-boto3-s3 (>=1.35.0,<1.36.0)", "mypy-boto3-s3control (>=1.35.0,<1.36.0)", "mypy-boto3-s3outposts (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-edge (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-geospatial (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-metrics (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-savingsplans (>=1.35.0,<1.36.0)", "mypy-boto3-scheduler (>=1.35.0,<1.36.0)", "mypy-boto3-schemas (>=1.35.0,<1.36.0)", "mypy-boto3-sdb (>=1.35.0,<1.36.0)", "mypy-boto3-secretsmanager (>=1.35.0,<1.36.0)", "mypy-boto3-securityhub (>=1.35.0,<1.36.0)", "mypy-boto3-securitylake (>=1.35.0,<1.36.0)", "mypy-boto3-serverlessrepo (>=1.35.0,<1.36.0)", "mypy-boto3-service-quotas (>=1.35.0,<1.36.0)", "mypy-boto3-servicecatalog (>=1.35.0,<1.36.0)", "mypy-boto3-servicecatalog-appregistry (>=1.35.0,<1.36.0)", "mypy-boto3-servicediscovery (>=1.35.0,<1.36.0)", "mypy-boto3-ses (>=1.35.0,<1.36.0)", "mypy-boto3-sesv2 (>=1.35.0,<1.36.0)", "mypy-boto3-shield (>=1.35.0,<1.36.0)", "mypy-boto3-signer (>=1.35.0,<1.36.0)", "mypy-boto3-simspaceweaver (>=1.35.0,<1.36.0)", "mypy-boto3-sms (>=1.35.0,<1.36.0)", "mypy-boto3-sms-voice (>=1.35.0,<1.36.0)", "mypy-boto3-snow-device-management (>=1.35.0,<1.36.0)", "mypy-boto3-snowball (>=1.35.0,<1.36.0)", "mypy-boto3-sns (>=1.35.0,<1.36.0)", "mypy-boto3-sqs (>=1.35.0,<1.36.0)", "mypy-boto3-ssm (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-contacts (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-incidents (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-quicksetup (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-sap (>=1.35.0,<1.36.0)", "mypy-boto3-sso (>=1.35.0,<1.36.0)", "mypy-boto3-sso-admin (>=1.35.0,<1.36.0)", "mypy-boto3-sso-oidc (>=1.35.0,<1.36.0)", "mypy-boto3-stepfunctions (>=1.35.0,<1.36.0)", "mypy-boto3-storagegateway (>=1.35.0,<1.36.0)", "mypy-boto3-sts (>=1.35.0,<1.36.0)", "mypy-boto3-supplychain (>=1.35.0,<1.36.0)", "mypy-boto3-support (>=1.35.0,<1.36.0)", "mypy-boto3-support-app (>=1.35.0,<1.36.0)", "mypy-boto3-swf (>=1.35.0,<1.36.0)", "mypy-boto3-synthetics (>=1.35.0,<1.36.0)", "mypy-boto3-taxsettings (>=1.35.0,<1.36.0)", "mypy-boto3-textract (>=1.35.0,<1.36.0)", "mypy-boto3-timestream-influxdb (>=1.35.0,<1.36.0)", "mypy-boto3-timestream-query (>=1.35.0,<1.36.0)", "mypy-boto3-timestream-write (>=1.35.0,<1.36.0)", "mypy-boto3-tnb (>=1.35.0,<1.36.0)", "mypy-boto3-transcribe (>=1.35.0,<1.36.0)", "mypy-boto3-transfer (>=1.35.0,<1.36.0)", "mypy-boto3-translate (>=1.35.0,<1.36.0)", "mypy-boto3-trustedadvisor (>=1.35.0,<1.36.0)", "mypy-boto3-verifiedpermissions (>=1.35.0,<1.36.0)", "mypy-boto3-voice-id (>=1.35.0,<1.36.0)", "mypy-boto3-vpc-lattice (>=1.35.0,<1.36.0)", "mypy-boto3-waf (>=1.35.0,<1.36.0)", "mypy-boto3-waf-regional (>=1.35.0,<1.36.0)", "mypy-boto3-wafv2 (>=1.35.0,<1.36.0)", "mypy-boto3-wellarchitected (>=1.35.0,<1.36.0)", "mypy-boto3-wisdom (>=1.35.0,<1.36.0)", "mypy-boto3-workdocs (>=1.35.0,<1.36.0)", "mypy-boto3-workmail (>=1.35.0,<1.36.0)", "mypy-boto3-workmailmessageflow (>=1.35.0,<1.36.0)", "mypy-boto3-workspaces (>=1.35.0,<1.36.0)", "mypy-boto3-workspaces-thin-client (>=1.35.0,<1.36.0)", "mypy-boto3-workspaces-web (>=1.35.0,<1.36.0)", "mypy-boto3-xray (>=1.35.0,<1.36.0)"] +all = ["mypy-boto3-accessanalyzer (>=1.35.0,<1.36.0)", "mypy-boto3-account (>=1.35.0,<1.36.0)", "mypy-boto3-acm (>=1.35.0,<1.36.0)", "mypy-boto3-acm-pca (>=1.35.0,<1.36.0)", "mypy-boto3-amp (>=1.35.0,<1.36.0)", "mypy-boto3-amplify (>=1.35.0,<1.36.0)", "mypy-boto3-amplifybackend (>=1.35.0,<1.36.0)", "mypy-boto3-amplifyuibuilder (>=1.35.0,<1.36.0)", "mypy-boto3-apigateway (>=1.35.0,<1.36.0)", "mypy-boto3-apigatewaymanagementapi (>=1.35.0,<1.36.0)", "mypy-boto3-apigatewayv2 (>=1.35.0,<1.36.0)", "mypy-boto3-appconfig (>=1.35.0,<1.36.0)", "mypy-boto3-appconfigdata (>=1.35.0,<1.36.0)", "mypy-boto3-appfabric (>=1.35.0,<1.36.0)", "mypy-boto3-appflow (>=1.35.0,<1.36.0)", "mypy-boto3-appintegrations (>=1.35.0,<1.36.0)", "mypy-boto3-application-autoscaling (>=1.35.0,<1.36.0)", "mypy-boto3-application-insights (>=1.35.0,<1.36.0)", "mypy-boto3-application-signals (>=1.35.0,<1.36.0)", "mypy-boto3-applicationcostprofiler (>=1.35.0,<1.36.0)", "mypy-boto3-appmesh (>=1.35.0,<1.36.0)", "mypy-boto3-apprunner (>=1.35.0,<1.36.0)", "mypy-boto3-appstream (>=1.35.0,<1.36.0)", "mypy-boto3-appsync (>=1.35.0,<1.36.0)", "mypy-boto3-apptest (>=1.35.0,<1.36.0)", "mypy-boto3-arc-zonal-shift (>=1.35.0,<1.36.0)", "mypy-boto3-artifact (>=1.35.0,<1.36.0)", "mypy-boto3-athena (>=1.35.0,<1.36.0)", "mypy-boto3-auditmanager (>=1.35.0,<1.36.0)", "mypy-boto3-autoscaling (>=1.35.0,<1.36.0)", "mypy-boto3-autoscaling-plans (>=1.35.0,<1.36.0)", "mypy-boto3-b2bi (>=1.35.0,<1.36.0)", "mypy-boto3-backup (>=1.35.0,<1.36.0)", "mypy-boto3-backup-gateway (>=1.35.0,<1.36.0)", "mypy-boto3-batch (>=1.35.0,<1.36.0)", "mypy-boto3-bcm-data-exports (>=1.35.0,<1.36.0)", "mypy-boto3-bcm-pricing-calculator (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-agent (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-agent-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-data-automation (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-data-automation-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-billing (>=1.35.0,<1.36.0)", "mypy-boto3-billingconductor (>=1.35.0,<1.36.0)", "mypy-boto3-braket (>=1.35.0,<1.36.0)", "mypy-boto3-budgets (>=1.35.0,<1.36.0)", "mypy-boto3-ce (>=1.35.0,<1.36.0)", "mypy-boto3-chatbot (>=1.35.0,<1.36.0)", "mypy-boto3-chime (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-identity (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-meetings (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-messaging (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-voice (>=1.35.0,<1.36.0)", "mypy-boto3-cleanrooms (>=1.35.0,<1.36.0)", "mypy-boto3-cleanroomsml (>=1.35.0,<1.36.0)", "mypy-boto3-cloud9 (>=1.35.0,<1.36.0)", "mypy-boto3-cloudcontrol (>=1.35.0,<1.36.0)", "mypy-boto3-clouddirectory (>=1.35.0,<1.36.0)", "mypy-boto3-cloudformation (>=1.35.0,<1.36.0)", "mypy-boto3-cloudfront (>=1.35.0,<1.36.0)", "mypy-boto3-cloudfront-keyvaluestore (>=1.35.0,<1.36.0)", "mypy-boto3-cloudhsm (>=1.35.0,<1.36.0)", "mypy-boto3-cloudhsmv2 (>=1.35.0,<1.36.0)", "mypy-boto3-cloudsearch (>=1.35.0,<1.36.0)", "mypy-boto3-cloudsearchdomain (>=1.35.0,<1.36.0)", "mypy-boto3-cloudtrail (>=1.35.0,<1.36.0)", "mypy-boto3-cloudtrail-data (>=1.35.0,<1.36.0)", "mypy-boto3-cloudwatch (>=1.35.0,<1.36.0)", "mypy-boto3-codeartifact (>=1.35.0,<1.36.0)", "mypy-boto3-codebuild (>=1.35.0,<1.36.0)", "mypy-boto3-codecatalyst (>=1.35.0,<1.36.0)", "mypy-boto3-codecommit (>=1.35.0,<1.36.0)", "mypy-boto3-codeconnections (>=1.35.0,<1.36.0)", "mypy-boto3-codedeploy (>=1.35.0,<1.36.0)", "mypy-boto3-codeguru-reviewer (>=1.35.0,<1.36.0)", "mypy-boto3-codeguru-security (>=1.35.0,<1.36.0)", "mypy-boto3-codeguruprofiler (>=1.35.0,<1.36.0)", "mypy-boto3-codepipeline (>=1.35.0,<1.36.0)", "mypy-boto3-codestar-connections (>=1.35.0,<1.36.0)", "mypy-boto3-codestar-notifications (>=1.35.0,<1.36.0)", "mypy-boto3-cognito-identity (>=1.35.0,<1.36.0)", "mypy-boto3-cognito-idp (>=1.35.0,<1.36.0)", "mypy-boto3-cognito-sync (>=1.35.0,<1.36.0)", "mypy-boto3-comprehend (>=1.35.0,<1.36.0)", "mypy-boto3-comprehendmedical (>=1.35.0,<1.36.0)", "mypy-boto3-compute-optimizer (>=1.35.0,<1.36.0)", "mypy-boto3-config (>=1.35.0,<1.36.0)", "mypy-boto3-connect (>=1.35.0,<1.36.0)", "mypy-boto3-connect-contact-lens (>=1.35.0,<1.36.0)", "mypy-boto3-connectcampaigns (>=1.35.0,<1.36.0)", "mypy-boto3-connectcampaignsv2 (>=1.35.0,<1.36.0)", "mypy-boto3-connectcases (>=1.35.0,<1.36.0)", "mypy-boto3-connectparticipant (>=1.35.0,<1.36.0)", "mypy-boto3-controlcatalog (>=1.35.0,<1.36.0)", "mypy-boto3-controltower (>=1.35.0,<1.36.0)", "mypy-boto3-cost-optimization-hub (>=1.35.0,<1.36.0)", "mypy-boto3-cur (>=1.35.0,<1.36.0)", "mypy-boto3-customer-profiles (>=1.35.0,<1.36.0)", "mypy-boto3-databrew (>=1.35.0,<1.36.0)", "mypy-boto3-dataexchange (>=1.35.0,<1.36.0)", "mypy-boto3-datapipeline (>=1.35.0,<1.36.0)", "mypy-boto3-datasync (>=1.35.0,<1.36.0)", "mypy-boto3-datazone (>=1.35.0,<1.36.0)", "mypy-boto3-dax (>=1.35.0,<1.36.0)", "mypy-boto3-deadline (>=1.35.0,<1.36.0)", "mypy-boto3-detective (>=1.35.0,<1.36.0)", "mypy-boto3-devicefarm (>=1.35.0,<1.36.0)", "mypy-boto3-devops-guru (>=1.35.0,<1.36.0)", "mypy-boto3-directconnect (>=1.35.0,<1.36.0)", "mypy-boto3-discovery (>=1.35.0,<1.36.0)", "mypy-boto3-dlm (>=1.35.0,<1.36.0)", "mypy-boto3-dms (>=1.35.0,<1.36.0)", "mypy-boto3-docdb (>=1.35.0,<1.36.0)", "mypy-boto3-docdb-elastic (>=1.35.0,<1.36.0)", "mypy-boto3-drs (>=1.35.0,<1.36.0)", "mypy-boto3-ds (>=1.35.0,<1.36.0)", "mypy-boto3-ds-data (>=1.35.0,<1.36.0)", "mypy-boto3-dsql (>=1.35.0,<1.36.0)", "mypy-boto3-dynamodb (>=1.35.0,<1.36.0)", "mypy-boto3-dynamodbstreams (>=1.35.0,<1.36.0)", "mypy-boto3-ebs (>=1.35.0,<1.36.0)", "mypy-boto3-ec2 (>=1.35.0,<1.36.0)", "mypy-boto3-ec2-instance-connect (>=1.35.0,<1.36.0)", "mypy-boto3-ecr (>=1.35.0,<1.36.0)", "mypy-boto3-ecr-public (>=1.35.0,<1.36.0)", "mypy-boto3-ecs (>=1.35.0,<1.36.0)", "mypy-boto3-efs (>=1.35.0,<1.36.0)", "mypy-boto3-eks (>=1.35.0,<1.36.0)", "mypy-boto3-eks-auth (>=1.35.0,<1.36.0)", "mypy-boto3-elastic-inference (>=1.35.0,<1.36.0)", "mypy-boto3-elasticache (>=1.35.0,<1.36.0)", "mypy-boto3-elasticbeanstalk (>=1.35.0,<1.36.0)", "mypy-boto3-elastictranscoder (>=1.35.0,<1.36.0)", "mypy-boto3-elb (>=1.35.0,<1.36.0)", "mypy-boto3-elbv2 (>=1.35.0,<1.36.0)", "mypy-boto3-emr (>=1.35.0,<1.36.0)", "mypy-boto3-emr-containers (>=1.35.0,<1.36.0)", "mypy-boto3-emr-serverless (>=1.35.0,<1.36.0)", "mypy-boto3-entityresolution (>=1.35.0,<1.36.0)", "mypy-boto3-es (>=1.35.0,<1.36.0)", "mypy-boto3-events (>=1.35.0,<1.36.0)", "mypy-boto3-evidently (>=1.35.0,<1.36.0)", "mypy-boto3-finspace (>=1.35.0,<1.36.0)", "mypy-boto3-finspace-data (>=1.35.0,<1.36.0)", "mypy-boto3-firehose (>=1.35.0,<1.36.0)", "mypy-boto3-fis (>=1.35.0,<1.36.0)", "mypy-boto3-fms (>=1.35.0,<1.36.0)", "mypy-boto3-forecast (>=1.35.0,<1.36.0)", "mypy-boto3-forecastquery (>=1.35.0,<1.36.0)", "mypy-boto3-frauddetector (>=1.35.0,<1.36.0)", "mypy-boto3-freetier (>=1.35.0,<1.36.0)", "mypy-boto3-fsx (>=1.35.0,<1.36.0)", "mypy-boto3-gamelift (>=1.35.0,<1.36.0)", "mypy-boto3-geo-maps (>=1.35.0,<1.36.0)", "mypy-boto3-geo-places (>=1.35.0,<1.36.0)", "mypy-boto3-geo-routes (>=1.35.0,<1.36.0)", "mypy-boto3-glacier (>=1.35.0,<1.36.0)", "mypy-boto3-globalaccelerator (>=1.35.0,<1.36.0)", "mypy-boto3-glue (>=1.35.0,<1.36.0)", "mypy-boto3-grafana (>=1.35.0,<1.36.0)", "mypy-boto3-greengrass (>=1.35.0,<1.36.0)", "mypy-boto3-greengrassv2 (>=1.35.0,<1.36.0)", "mypy-boto3-groundstation (>=1.35.0,<1.36.0)", "mypy-boto3-guardduty (>=1.35.0,<1.36.0)", "mypy-boto3-health (>=1.35.0,<1.36.0)", "mypy-boto3-healthlake (>=1.35.0,<1.36.0)", "mypy-boto3-iam (>=1.35.0,<1.36.0)", "mypy-boto3-identitystore (>=1.35.0,<1.36.0)", "mypy-boto3-imagebuilder (>=1.35.0,<1.36.0)", "mypy-boto3-importexport (>=1.35.0,<1.36.0)", "mypy-boto3-inspector (>=1.35.0,<1.36.0)", "mypy-boto3-inspector-scan (>=1.35.0,<1.36.0)", "mypy-boto3-inspector2 (>=1.35.0,<1.36.0)", "mypy-boto3-internetmonitor (>=1.35.0,<1.36.0)", "mypy-boto3-invoicing (>=1.35.0,<1.36.0)", "mypy-boto3-iot (>=1.35.0,<1.36.0)", "mypy-boto3-iot-data (>=1.35.0,<1.36.0)", "mypy-boto3-iot-jobs-data (>=1.35.0,<1.36.0)", "mypy-boto3-iot1click-devices (>=1.35.0,<1.36.0)", "mypy-boto3-iot1click-projects (>=1.35.0,<1.36.0)", "mypy-boto3-iotanalytics (>=1.35.0,<1.36.0)", "mypy-boto3-iotdeviceadvisor (>=1.35.0,<1.36.0)", "mypy-boto3-iotevents (>=1.35.0,<1.36.0)", "mypy-boto3-iotevents-data (>=1.35.0,<1.36.0)", "mypy-boto3-iotfleethub (>=1.35.0,<1.36.0)", "mypy-boto3-iotfleetwise (>=1.35.0,<1.36.0)", "mypy-boto3-iotsecuretunneling (>=1.35.0,<1.36.0)", "mypy-boto3-iotsitewise (>=1.35.0,<1.36.0)", "mypy-boto3-iotthingsgraph (>=1.35.0,<1.36.0)", "mypy-boto3-iottwinmaker (>=1.35.0,<1.36.0)", "mypy-boto3-iotwireless (>=1.35.0,<1.36.0)", "mypy-boto3-ivs (>=1.35.0,<1.36.0)", "mypy-boto3-ivs-realtime (>=1.35.0,<1.36.0)", "mypy-boto3-ivschat (>=1.35.0,<1.36.0)", "mypy-boto3-kafka (>=1.35.0,<1.36.0)", "mypy-boto3-kafkaconnect (>=1.35.0,<1.36.0)", "mypy-boto3-kendra (>=1.35.0,<1.36.0)", "mypy-boto3-kendra-ranking (>=1.35.0,<1.36.0)", "mypy-boto3-keyspaces (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-archived-media (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-media (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-signaling (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.35.0,<1.36.0)", "mypy-boto3-kinesisanalytics (>=1.35.0,<1.36.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.35.0,<1.36.0)", "mypy-boto3-kinesisvideo (>=1.35.0,<1.36.0)", "mypy-boto3-kms (>=1.35.0,<1.36.0)", "mypy-boto3-lakeformation (>=1.35.0,<1.36.0)", "mypy-boto3-lambda (>=1.35.0,<1.36.0)", "mypy-boto3-launch-wizard (>=1.35.0,<1.36.0)", "mypy-boto3-lex-models (>=1.35.0,<1.36.0)", "mypy-boto3-lex-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-lexv2-models (>=1.35.0,<1.36.0)", "mypy-boto3-lexv2-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-license-manager (>=1.35.0,<1.36.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.35.0,<1.36.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.35.0,<1.36.0)", "mypy-boto3-lightsail (>=1.35.0,<1.36.0)", "mypy-boto3-location (>=1.35.0,<1.36.0)", "mypy-boto3-logs (>=1.35.0,<1.36.0)", "mypy-boto3-lookoutequipment (>=1.35.0,<1.36.0)", "mypy-boto3-lookoutmetrics (>=1.35.0,<1.36.0)", "mypy-boto3-lookoutvision (>=1.35.0,<1.36.0)", "mypy-boto3-m2 (>=1.35.0,<1.36.0)", "mypy-boto3-machinelearning (>=1.35.0,<1.36.0)", "mypy-boto3-macie2 (>=1.35.0,<1.36.0)", "mypy-boto3-mailmanager (>=1.35.0,<1.36.0)", "mypy-boto3-managedblockchain (>=1.35.0,<1.36.0)", "mypy-boto3-managedblockchain-query (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-agreement (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-catalog (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-deployment (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-entitlement (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-reporting (>=1.35.0,<1.36.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.35.0,<1.36.0)", "mypy-boto3-mediaconnect (>=1.35.0,<1.36.0)", "mypy-boto3-mediaconvert (>=1.35.0,<1.36.0)", "mypy-boto3-medialive (>=1.35.0,<1.36.0)", "mypy-boto3-mediapackage (>=1.35.0,<1.36.0)", "mypy-boto3-mediapackage-vod (>=1.35.0,<1.36.0)", "mypy-boto3-mediapackagev2 (>=1.35.0,<1.36.0)", "mypy-boto3-mediastore (>=1.35.0,<1.36.0)", "mypy-boto3-mediastore-data (>=1.35.0,<1.36.0)", "mypy-boto3-mediatailor (>=1.35.0,<1.36.0)", "mypy-boto3-medical-imaging (>=1.35.0,<1.36.0)", "mypy-boto3-memorydb (>=1.35.0,<1.36.0)", "mypy-boto3-meteringmarketplace (>=1.35.0,<1.36.0)", "mypy-boto3-mgh (>=1.35.0,<1.36.0)", "mypy-boto3-mgn (>=1.35.0,<1.36.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.35.0,<1.36.0)", "mypy-boto3-migrationhub-config (>=1.35.0,<1.36.0)", "mypy-boto3-migrationhuborchestrator (>=1.35.0,<1.36.0)", "mypy-boto3-migrationhubstrategy (>=1.35.0,<1.36.0)", "mypy-boto3-mq (>=1.35.0,<1.36.0)", "mypy-boto3-mturk (>=1.35.0,<1.36.0)", "mypy-boto3-mwaa (>=1.35.0,<1.36.0)", "mypy-boto3-neptune (>=1.35.0,<1.36.0)", "mypy-boto3-neptune-graph (>=1.35.0,<1.36.0)", "mypy-boto3-neptunedata (>=1.35.0,<1.36.0)", "mypy-boto3-network-firewall (>=1.35.0,<1.36.0)", "mypy-boto3-networkflowmonitor (>=1.35.0,<1.36.0)", "mypy-boto3-networkmanager (>=1.35.0,<1.36.0)", "mypy-boto3-networkmonitor (>=1.35.0,<1.36.0)", "mypy-boto3-notifications (>=1.35.0,<1.36.0)", "mypy-boto3-notificationscontacts (>=1.35.0,<1.36.0)", "mypy-boto3-oam (>=1.35.0,<1.36.0)", "mypy-boto3-observabilityadmin (>=1.35.0,<1.36.0)", "mypy-boto3-omics (>=1.35.0,<1.36.0)", "mypy-boto3-opensearch (>=1.35.0,<1.36.0)", "mypy-boto3-opensearchserverless (>=1.35.0,<1.36.0)", "mypy-boto3-opsworks (>=1.35.0,<1.36.0)", "mypy-boto3-opsworkscm (>=1.35.0,<1.36.0)", "mypy-boto3-organizations (>=1.35.0,<1.36.0)", "mypy-boto3-osis (>=1.35.0,<1.36.0)", "mypy-boto3-outposts (>=1.35.0,<1.36.0)", "mypy-boto3-panorama (>=1.35.0,<1.36.0)", "mypy-boto3-partnercentral-selling (>=1.35.0,<1.36.0)", "mypy-boto3-payment-cryptography (>=1.35.0,<1.36.0)", "mypy-boto3-payment-cryptography-data (>=1.35.0,<1.36.0)", "mypy-boto3-pca-connector-ad (>=1.35.0,<1.36.0)", "mypy-boto3-pca-connector-scep (>=1.35.0,<1.36.0)", "mypy-boto3-pcs (>=1.35.0,<1.36.0)", "mypy-boto3-personalize (>=1.35.0,<1.36.0)", "mypy-boto3-personalize-events (>=1.35.0,<1.36.0)", "mypy-boto3-personalize-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-pi (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint-email (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint-sms-voice (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.35.0,<1.36.0)", "mypy-boto3-pipes (>=1.35.0,<1.36.0)", "mypy-boto3-polly (>=1.35.0,<1.36.0)", "mypy-boto3-pricing (>=1.35.0,<1.36.0)", "mypy-boto3-privatenetworks (>=1.35.0,<1.36.0)", "mypy-boto3-proton (>=1.35.0,<1.36.0)", "mypy-boto3-qapps (>=1.35.0,<1.36.0)", "mypy-boto3-qbusiness (>=1.35.0,<1.36.0)", "mypy-boto3-qconnect (>=1.35.0,<1.36.0)", "mypy-boto3-qldb (>=1.35.0,<1.36.0)", "mypy-boto3-qldb-session (>=1.35.0,<1.36.0)", "mypy-boto3-quicksight (>=1.35.0,<1.36.0)", "mypy-boto3-ram (>=1.35.0,<1.36.0)", "mypy-boto3-rbin (>=1.35.0,<1.36.0)", "mypy-boto3-rds (>=1.35.0,<1.36.0)", "mypy-boto3-rds-data (>=1.35.0,<1.36.0)", "mypy-boto3-redshift (>=1.35.0,<1.36.0)", "mypy-boto3-redshift-data (>=1.35.0,<1.36.0)", "mypy-boto3-redshift-serverless (>=1.35.0,<1.36.0)", "mypy-boto3-rekognition (>=1.35.0,<1.36.0)", "mypy-boto3-repostspace (>=1.35.0,<1.36.0)", "mypy-boto3-resiliencehub (>=1.35.0,<1.36.0)", "mypy-boto3-resource-explorer-2 (>=1.35.0,<1.36.0)", "mypy-boto3-resource-groups (>=1.35.0,<1.36.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.35.0,<1.36.0)", "mypy-boto3-robomaker (>=1.35.0,<1.36.0)", "mypy-boto3-rolesanywhere (>=1.35.0,<1.36.0)", "mypy-boto3-route53 (>=1.35.0,<1.36.0)", "mypy-boto3-route53-recovery-cluster (>=1.35.0,<1.36.0)", "mypy-boto3-route53-recovery-control-config (>=1.35.0,<1.36.0)", "mypy-boto3-route53-recovery-readiness (>=1.35.0,<1.36.0)", "mypy-boto3-route53domains (>=1.35.0,<1.36.0)", "mypy-boto3-route53profiles (>=1.35.0,<1.36.0)", "mypy-boto3-route53resolver (>=1.35.0,<1.36.0)", "mypy-boto3-rum (>=1.35.0,<1.36.0)", "mypy-boto3-s3 (>=1.35.0,<1.36.0)", "mypy-boto3-s3control (>=1.35.0,<1.36.0)", "mypy-boto3-s3outposts (>=1.35.0,<1.36.0)", "mypy-boto3-s3tables (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-edge (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-geospatial (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-metrics (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-savingsplans (>=1.35.0,<1.36.0)", "mypy-boto3-scheduler (>=1.35.0,<1.36.0)", "mypy-boto3-schemas (>=1.35.0,<1.36.0)", "mypy-boto3-sdb (>=1.35.0,<1.36.0)", "mypy-boto3-secretsmanager (>=1.35.0,<1.36.0)", "mypy-boto3-security-ir (>=1.35.0,<1.36.0)", "mypy-boto3-securityhub (>=1.35.0,<1.36.0)", "mypy-boto3-securitylake (>=1.35.0,<1.36.0)", "mypy-boto3-serverlessrepo (>=1.35.0,<1.36.0)", "mypy-boto3-service-quotas (>=1.35.0,<1.36.0)", "mypy-boto3-servicecatalog (>=1.35.0,<1.36.0)", "mypy-boto3-servicecatalog-appregistry (>=1.35.0,<1.36.0)", "mypy-boto3-servicediscovery (>=1.35.0,<1.36.0)", "mypy-boto3-ses (>=1.35.0,<1.36.0)", "mypy-boto3-sesv2 (>=1.35.0,<1.36.0)", "mypy-boto3-shield (>=1.35.0,<1.36.0)", "mypy-boto3-signer (>=1.35.0,<1.36.0)", "mypy-boto3-simspaceweaver (>=1.35.0,<1.36.0)", "mypy-boto3-sms (>=1.35.0,<1.36.0)", "mypy-boto3-sms-voice (>=1.35.0,<1.36.0)", "mypy-boto3-snow-device-management (>=1.35.0,<1.36.0)", "mypy-boto3-snowball (>=1.35.0,<1.36.0)", "mypy-boto3-sns (>=1.35.0,<1.36.0)", "mypy-boto3-socialmessaging (>=1.35.0,<1.36.0)", "mypy-boto3-sqs (>=1.35.0,<1.36.0)", "mypy-boto3-ssm (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-contacts (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-incidents (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-quicksetup (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-sap (>=1.35.0,<1.36.0)", "mypy-boto3-sso (>=1.35.0,<1.36.0)", "mypy-boto3-sso-admin (>=1.35.0,<1.36.0)", "mypy-boto3-sso-oidc (>=1.35.0,<1.36.0)", "mypy-boto3-stepfunctions (>=1.35.0,<1.36.0)", "mypy-boto3-storagegateway (>=1.35.0,<1.36.0)", "mypy-boto3-sts (>=1.35.0,<1.36.0)", "mypy-boto3-supplychain (>=1.35.0,<1.36.0)", "mypy-boto3-support (>=1.35.0,<1.36.0)", "mypy-boto3-support-app (>=1.35.0,<1.36.0)", "mypy-boto3-swf (>=1.35.0,<1.36.0)", "mypy-boto3-synthetics (>=1.35.0,<1.36.0)", "mypy-boto3-taxsettings (>=1.35.0,<1.36.0)", "mypy-boto3-textract (>=1.35.0,<1.36.0)", "mypy-boto3-timestream-influxdb (>=1.35.0,<1.36.0)", "mypy-boto3-timestream-query (>=1.35.0,<1.36.0)", "mypy-boto3-timestream-write (>=1.35.0,<1.36.0)", "mypy-boto3-tnb (>=1.35.0,<1.36.0)", "mypy-boto3-transcribe (>=1.35.0,<1.36.0)", "mypy-boto3-transfer (>=1.35.0,<1.36.0)", "mypy-boto3-translate (>=1.35.0,<1.36.0)", "mypy-boto3-trustedadvisor (>=1.35.0,<1.36.0)", "mypy-boto3-verifiedpermissions (>=1.35.0,<1.36.0)", "mypy-boto3-voice-id (>=1.35.0,<1.36.0)", "mypy-boto3-vpc-lattice (>=1.35.0,<1.36.0)", "mypy-boto3-waf (>=1.35.0,<1.36.0)", "mypy-boto3-waf-regional (>=1.35.0,<1.36.0)", "mypy-boto3-wafv2 (>=1.35.0,<1.36.0)", "mypy-boto3-wellarchitected (>=1.35.0,<1.36.0)", "mypy-boto3-wisdom (>=1.35.0,<1.36.0)", "mypy-boto3-workdocs (>=1.35.0,<1.36.0)", "mypy-boto3-workmail (>=1.35.0,<1.36.0)", "mypy-boto3-workmailmessageflow (>=1.35.0,<1.36.0)", "mypy-boto3-workspaces (>=1.35.0,<1.36.0)", "mypy-boto3-workspaces-thin-client (>=1.35.0,<1.36.0)", "mypy-boto3-workspaces-web (>=1.35.0,<1.36.0)", "mypy-boto3-xray (>=1.35.0,<1.36.0)"] amp = ["mypy-boto3-amp (>=1.35.0,<1.36.0)"] amplify = ["mypy-boto3-amplify (>=1.35.0,<1.36.0)"] amplifybackend = ["mypy-boto3-amplifybackend (>=1.35.0,<1.36.0)"] @@ -271,12 +271,16 @@ backup = ["mypy-boto3-backup (>=1.35.0,<1.36.0)"] backup-gateway = ["mypy-boto3-backup-gateway (>=1.35.0,<1.36.0)"] batch = ["mypy-boto3-batch (>=1.35.0,<1.36.0)"] bcm-data-exports = ["mypy-boto3-bcm-data-exports (>=1.35.0,<1.36.0)"] +bcm-pricing-calculator = ["mypy-boto3-bcm-pricing-calculator (>=1.35.0,<1.36.0)"] bedrock = ["mypy-boto3-bedrock (>=1.35.0,<1.36.0)"] bedrock-agent = ["mypy-boto3-bedrock-agent (>=1.35.0,<1.36.0)"] bedrock-agent-runtime = ["mypy-boto3-bedrock-agent-runtime (>=1.35.0,<1.36.0)"] +bedrock-data-automation = ["mypy-boto3-bedrock-data-automation (>=1.35.0,<1.36.0)"] +bedrock-data-automation-runtime = ["mypy-boto3-bedrock-data-automation-runtime (>=1.35.0,<1.36.0)"] bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.35.0,<1.36.0)"] +billing = ["mypy-boto3-billing (>=1.35.0,<1.36.0)"] billingconductor = ["mypy-boto3-billingconductor (>=1.35.0,<1.36.0)"] -boto3 = ["boto3 (==1.35.29)", "botocore (==1.35.29)"] +boto3 = ["boto3 (==1.35.76)", "botocore (==1.35.76)"] braket = ["mypy-boto3-braket (>=1.35.0,<1.36.0)"] budgets = ["mypy-boto3-budgets (>=1.35.0,<1.36.0)"] ce = ["mypy-boto3-ce (>=1.35.0,<1.36.0)"] @@ -324,6 +328,7 @@ config = ["mypy-boto3-config (>=1.35.0,<1.36.0)"] connect = ["mypy-boto3-connect (>=1.35.0,<1.36.0)"] connect-contact-lens = ["mypy-boto3-connect-contact-lens (>=1.35.0,<1.36.0)"] connectcampaigns = ["mypy-boto3-connectcampaigns (>=1.35.0,<1.36.0)"] +connectcampaignsv2 = ["mypy-boto3-connectcampaignsv2 (>=1.35.0,<1.36.0)"] connectcases = ["mypy-boto3-connectcases (>=1.35.0,<1.36.0)"] connectparticipant = ["mypy-boto3-connectparticipant (>=1.35.0,<1.36.0)"] controlcatalog = ["mypy-boto3-controlcatalog (>=1.35.0,<1.36.0)"] @@ -350,6 +355,7 @@ docdb-elastic = ["mypy-boto3-docdb-elastic (>=1.35.0,<1.36.0)"] drs = ["mypy-boto3-drs (>=1.35.0,<1.36.0)"] ds = ["mypy-boto3-ds (>=1.35.0,<1.36.0)"] ds-data = ["mypy-boto3-ds-data (>=1.35.0,<1.36.0)"] +dsql = ["mypy-boto3-dsql (>=1.35.0,<1.36.0)"] dynamodb = ["mypy-boto3-dynamodb (>=1.35.0,<1.36.0)"] dynamodbstreams = ["mypy-boto3-dynamodbstreams (>=1.35.0,<1.36.0)"] ebs = ["mypy-boto3-ebs (>=1.35.0,<1.36.0)"] @@ -387,6 +393,9 @@ freetier = ["mypy-boto3-freetier (>=1.35.0,<1.36.0)"] fsx = ["mypy-boto3-fsx (>=1.35.0,<1.36.0)"] full = ["boto3-stubs-full"] gamelift = ["mypy-boto3-gamelift (>=1.35.0,<1.36.0)"] +geo-maps = ["mypy-boto3-geo-maps (>=1.35.0,<1.36.0)"] +geo-places = ["mypy-boto3-geo-places (>=1.35.0,<1.36.0)"] +geo-routes = ["mypy-boto3-geo-routes (>=1.35.0,<1.36.0)"] glacier = ["mypy-boto3-glacier (>=1.35.0,<1.36.0)"] globalaccelerator = ["mypy-boto3-globalaccelerator (>=1.35.0,<1.36.0)"] glue = ["mypy-boto3-glue (>=1.35.0,<1.36.0)"] @@ -405,6 +414,7 @@ inspector = ["mypy-boto3-inspector (>=1.35.0,<1.36.0)"] inspector-scan = ["mypy-boto3-inspector-scan (>=1.35.0,<1.36.0)"] inspector2 = ["mypy-boto3-inspector2 (>=1.35.0,<1.36.0)"] internetmonitor = ["mypy-boto3-internetmonitor (>=1.35.0,<1.36.0)"] +invoicing = ["mypy-boto3-invoicing (>=1.35.0,<1.36.0)"] iot = ["mypy-boto3-iot (>=1.35.0,<1.36.0)"] iot-data = ["mypy-boto3-iot-data (>=1.35.0,<1.36.0)"] iot-jobs-data = ["mypy-boto3-iot-jobs-data (>=1.35.0,<1.36.0)"] @@ -464,6 +474,7 @@ marketplace-agreement = ["mypy-boto3-marketplace-agreement (>=1.35.0,<1.36.0)"] marketplace-catalog = ["mypy-boto3-marketplace-catalog (>=1.35.0,<1.36.0)"] marketplace-deployment = ["mypy-boto3-marketplace-deployment (>=1.35.0,<1.36.0)"] marketplace-entitlement = ["mypy-boto3-marketplace-entitlement (>=1.35.0,<1.36.0)"] +marketplace-reporting = ["mypy-boto3-marketplace-reporting (>=1.35.0,<1.36.0)"] marketplacecommerceanalytics = ["mypy-boto3-marketplacecommerceanalytics (>=1.35.0,<1.36.0)"] mediaconnect = ["mypy-boto3-mediaconnect (>=1.35.0,<1.36.0)"] mediaconvert = ["mypy-boto3-mediaconvert (>=1.35.0,<1.36.0)"] @@ -490,10 +501,13 @@ neptune = ["mypy-boto3-neptune (>=1.35.0,<1.36.0)"] neptune-graph = ["mypy-boto3-neptune-graph (>=1.35.0,<1.36.0)"] neptunedata = ["mypy-boto3-neptunedata (>=1.35.0,<1.36.0)"] network-firewall = ["mypy-boto3-network-firewall (>=1.35.0,<1.36.0)"] +networkflowmonitor = ["mypy-boto3-networkflowmonitor (>=1.35.0,<1.36.0)"] networkmanager = ["mypy-boto3-networkmanager (>=1.35.0,<1.36.0)"] networkmonitor = ["mypy-boto3-networkmonitor (>=1.35.0,<1.36.0)"] -nimble = ["mypy-boto3-nimble (>=1.35.0,<1.36.0)"] +notifications = ["mypy-boto3-notifications (>=1.35.0,<1.36.0)"] +notificationscontacts = ["mypy-boto3-notificationscontacts (>=1.35.0,<1.36.0)"] oam = ["mypy-boto3-oam (>=1.35.0,<1.36.0)"] +observabilityadmin = ["mypy-boto3-observabilityadmin (>=1.35.0,<1.36.0)"] omics = ["mypy-boto3-omics (>=1.35.0,<1.36.0)"] opensearch = ["mypy-boto3-opensearch (>=1.35.0,<1.36.0)"] opensearchserverless = ["mypy-boto3-opensearchserverless (>=1.35.0,<1.36.0)"] @@ -503,6 +517,7 @@ organizations = ["mypy-boto3-organizations (>=1.35.0,<1.36.0)"] osis = ["mypy-boto3-osis (>=1.35.0,<1.36.0)"] outposts = ["mypy-boto3-outposts (>=1.35.0,<1.36.0)"] panorama = ["mypy-boto3-panorama (>=1.35.0,<1.36.0)"] +partnercentral-selling = ["mypy-boto3-partnercentral-selling (>=1.35.0,<1.36.0)"] payment-cryptography = ["mypy-boto3-payment-cryptography (>=1.35.0,<1.36.0)"] payment-cryptography-data = ["mypy-boto3-payment-cryptography-data (>=1.35.0,<1.36.0)"] pca-connector-ad = ["mypy-boto3-pca-connector-ad (>=1.35.0,<1.36.0)"] @@ -553,6 +568,7 @@ rum = ["mypy-boto3-rum (>=1.35.0,<1.36.0)"] s3 = ["mypy-boto3-s3 (>=1.35.0,<1.36.0)"] s3control = ["mypy-boto3-s3control (>=1.35.0,<1.36.0)"] s3outposts = ["mypy-boto3-s3outposts (>=1.35.0,<1.36.0)"] +s3tables = ["mypy-boto3-s3tables (>=1.35.0,<1.36.0)"] sagemaker = ["mypy-boto3-sagemaker (>=1.35.0,<1.36.0)"] sagemaker-a2i-runtime = ["mypy-boto3-sagemaker-a2i-runtime (>=1.35.0,<1.36.0)"] sagemaker-edge = ["mypy-boto3-sagemaker-edge (>=1.35.0,<1.36.0)"] @@ -565,6 +581,7 @@ scheduler = ["mypy-boto3-scheduler (>=1.35.0,<1.36.0)"] schemas = ["mypy-boto3-schemas (>=1.35.0,<1.36.0)"] sdb = ["mypy-boto3-sdb (>=1.35.0,<1.36.0)"] secretsmanager = ["mypy-boto3-secretsmanager (>=1.35.0,<1.36.0)"] +security-ir = ["mypy-boto3-security-ir (>=1.35.0,<1.36.0)"] securityhub = ["mypy-boto3-securityhub (>=1.35.0,<1.36.0)"] securitylake = ["mypy-boto3-securitylake (>=1.35.0,<1.36.0)"] serverlessrepo = ["mypy-boto3-serverlessrepo (>=1.35.0,<1.36.0)"] @@ -582,6 +599,7 @@ sms-voice = ["mypy-boto3-sms-voice (>=1.35.0,<1.36.0)"] snow-device-management = ["mypy-boto3-snow-device-management (>=1.35.0,<1.36.0)"] snowball = ["mypy-boto3-snowball (>=1.35.0,<1.36.0)"] sns = ["mypy-boto3-sns (>=1.35.0,<1.36.0)"] +socialmessaging = ["mypy-boto3-socialmessaging (>=1.35.0,<1.36.0)"] sqs = ["mypy-boto3-sqs (>=1.35.0,<1.36.0)"] ssm = ["mypy-boto3-ssm (>=1.35.0,<1.36.0)"] ssm-contacts = ["mypy-boto3-ssm-contacts (>=1.35.0,<1.36.0)"] @@ -646,13 +664,13 @@ crt = ["awscrt (==0.21.2)"] [[package]] name = "botocore-stubs" -version = "1.35.29" +version = "1.35.76" description = "Type annotations and code completion for botocore" optional = false python-versions = ">=3.8" files = [ - {file = "botocore_stubs-1.35.29-py3-none-any.whl", hash = "sha256:8eff9dc4e6e844baf65beb16eb2c68a173ccd50dc9323dc04d85060cacc36a05"}, - {file = "botocore_stubs-1.35.29.tar.gz", hash = "sha256:40d4cf5fc527fbad381be18cf837400d6f168a880e26ee794c8c04fa0a3e62c5"}, + {file = "botocore_stubs-1.35.76-py3-none-any.whl", hash = "sha256:617508d023e0bc98901e0189b794c4b3f289c1747c7cc410173ad698c819a716"}, + {file = "botocore_stubs-1.35.76.tar.gz", hash = "sha256:c977a049481d50a14bf2db0ef15020b76734ff628d4b8e0e77b8d1c65318369e"}, ] [package.dependencies] @@ -800,101 +818,116 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] @@ -924,83 +957,73 @@ files = [ [[package]] name = "coverage" -version = "7.6.1" +version = "7.6.8" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, - {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, - {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, - {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, - {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, - {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, - {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, - {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, - {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, - {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, - {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, - {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, - {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, - {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, - {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, - {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, - {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, - {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, - {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, - {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, - {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, - {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, - {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, - {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, - {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, - {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, - {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, - {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, - {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, - {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, + {file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"}, + {file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"}, + {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"}, + {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"}, + {file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"}, + {file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"}, + {file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"}, + {file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"}, + {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"}, + {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"}, + {file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"}, + {file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"}, + {file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"}, + {file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"}, + {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"}, + {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"}, + {file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"}, + {file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"}, + {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"}, + {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"}, + {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"}, + {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"}, + {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"}, + {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"}, + {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"}, + {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"}, + {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"}, + {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"}, + {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"}, + {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"}, + {file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"}, + {file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"}, + {file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"}, + {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"}, + {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"}, + {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"}, + {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"}, + {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"}, + {file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"}, + {file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"}, + {file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"}, + {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"}, ] [package.extras] @@ -1008,51 +1031,53 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "43.0.1" +version = "44.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, - {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, - {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, - {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, - {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, - {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, - {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +files = [ + {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, + {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, + {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, + {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, + {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, + {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, + {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] +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)"] +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", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -1091,33 +1116,37 @@ test = ["flake8", "isort", "pytest"] [[package]] name = "debugpy" -version = "1.8.6" +version = "1.8.9" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" files = [ - {file = "debugpy-1.8.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:30f467c5345d9dfdcc0afdb10e018e47f092e383447500f125b4e013236bf14b"}, - {file = "debugpy-1.8.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d73d8c52614432f4215d0fe79a7e595d0dd162b5c15233762565be2f014803b"}, - {file = "debugpy-1.8.6-cp310-cp310-win32.whl", hash = "sha256:e3e182cd98eac20ee23a00653503315085b29ab44ed66269482349d307b08df9"}, - {file = "debugpy-1.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:e3a82da039cfe717b6fb1886cbbe5c4a3f15d7df4765af857f4307585121c2dd"}, - {file = "debugpy-1.8.6-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:67479a94cf5fd2c2d88f9615e087fcb4fec169ec780464a3f2ba4a9a2bb79955"}, - {file = "debugpy-1.8.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fb8653f6cbf1dd0a305ac1aa66ec246002145074ea57933978346ea5afdf70b"}, - {file = "debugpy-1.8.6-cp311-cp311-win32.whl", hash = "sha256:cdaf0b9691879da2d13fa39b61c01887c34558d1ff6e5c30e2eb698f5384cd43"}, - {file = "debugpy-1.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:43996632bee7435583952155c06881074b9a742a86cee74e701d87ca532fe833"}, - {file = "debugpy-1.8.6-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:db891b141fc6ee4b5fc6d1cc8035ec329cabc64bdd2ae672b4550c87d4ecb128"}, - {file = "debugpy-1.8.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:567419081ff67da766c898ccf21e79f1adad0e321381b0dfc7a9c8f7a9347972"}, - {file = "debugpy-1.8.6-cp312-cp312-win32.whl", hash = "sha256:c9834dfd701a1f6bf0f7f0b8b1573970ae99ebbeee68314116e0ccc5c78eea3c"}, - {file = "debugpy-1.8.6-cp312-cp312-win_amd64.whl", hash = "sha256:e4ce0570aa4aca87137890d23b86faeadf184924ad892d20c54237bcaab75d8f"}, - {file = "debugpy-1.8.6-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:df5dc9eb4ca050273b8e374a4cd967c43be1327eeb42bfe2f58b3cdfe7c68dcb"}, - {file = "debugpy-1.8.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a85707c6a84b0c5b3db92a2df685b5230dd8fb8c108298ba4f11dba157a615a"}, - {file = "debugpy-1.8.6-cp38-cp38-win32.whl", hash = "sha256:538c6cdcdcdad310bbefd96d7850be1cd46e703079cc9e67d42a9ca776cdc8a8"}, - {file = "debugpy-1.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:22140bc02c66cda6053b6eb56dfe01bbe22a4447846581ba1dd6df2c9f97982d"}, - {file = "debugpy-1.8.6-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:c1cef65cffbc96e7b392d9178dbfd524ab0750da6c0023c027ddcac968fd1caa"}, - {file = "debugpy-1.8.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e60bd06bb3cc5c0e957df748d1fab501e01416c43a7bdc756d2a992ea1b881"}, - {file = "debugpy-1.8.6-cp39-cp39-win32.whl", hash = "sha256:f7158252803d0752ed5398d291dee4c553bb12d14547c0e1843ab74ee9c31123"}, - {file = "debugpy-1.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3358aa619a073b620cd0d51d8a6176590af24abcc3fe2e479929a154bf591b51"}, - {file = "debugpy-1.8.6-py2.py3-none-any.whl", hash = "sha256:b48892df4d810eff21d3ef37274f4c60d32cdcafc462ad5647239036b0f0649f"}, - {file = "debugpy-1.8.6.zip", hash = "sha256:c931a9371a86784cee25dec8d65bc2dc7a21f3f1552e3833d9ef8f919d22280a"}, + {file = "debugpy-1.8.9-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:cfe1e6c6ad7178265f74981edf1154ffce97b69005212fbc90ca22ddfe3d017e"}, + {file = "debugpy-1.8.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada7fb65102a4d2c9ab62e8908e9e9f12aed9d76ef44880367bc9308ebe49a0f"}, + {file = "debugpy-1.8.9-cp310-cp310-win32.whl", hash = "sha256:c36856343cbaa448171cba62a721531e10e7ffb0abff838004701454149bc037"}, + {file = "debugpy-1.8.9-cp310-cp310-win_amd64.whl", hash = "sha256:17c5e0297678442511cf00a745c9709e928ea4ca263d764e90d233208889a19e"}, + {file = "debugpy-1.8.9-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:b74a49753e21e33e7cf030883a92fa607bddc4ede1aa4145172debc637780040"}, + {file = "debugpy-1.8.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62d22dacdb0e296966d7d74a7141aaab4bec123fa43d1a35ddcb39bf9fd29d70"}, + {file = "debugpy-1.8.9-cp311-cp311-win32.whl", hash = "sha256:8138efff315cd09b8dcd14226a21afda4ca582284bf4215126d87342bba1cc66"}, + {file = "debugpy-1.8.9-cp311-cp311-win_amd64.whl", hash = "sha256:ff54ef77ad9f5c425398efb150239f6fe8e20c53ae2f68367eba7ece1e96226d"}, + {file = "debugpy-1.8.9-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:957363d9a7a6612a37458d9a15e72d03a635047f946e5fceee74b50d52a9c8e2"}, + {file = "debugpy-1.8.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e565fc54b680292b418bb809f1386f17081d1346dca9a871bf69a8ac4071afe"}, + {file = "debugpy-1.8.9-cp312-cp312-win32.whl", hash = "sha256:3e59842d6c4569c65ceb3751075ff8d7e6a6ada209ceca6308c9bde932bcef11"}, + {file = "debugpy-1.8.9-cp312-cp312-win_amd64.whl", hash = "sha256:66eeae42f3137eb428ea3a86d4a55f28da9bd5a4a3d369ba95ecc3a92c1bba53"}, + {file = "debugpy-1.8.9-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:957ecffff80d47cafa9b6545de9e016ae8c9547c98a538ee96ab5947115fb3dd"}, + {file = "debugpy-1.8.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1efbb3ff61487e2c16b3e033bc8595aea578222c08aaf3c4bf0f93fadbd662ee"}, + {file = "debugpy-1.8.9-cp313-cp313-win32.whl", hash = "sha256:7c4d65d03bee875bcb211c76c1d8f10f600c305dbd734beaed4077e902606fee"}, + {file = "debugpy-1.8.9-cp313-cp313-win_amd64.whl", hash = "sha256:e46b420dc1bea64e5bbedd678148be512442bc589b0111bd799367cde051e71a"}, + {file = "debugpy-1.8.9-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:472a3994999fe6c0756945ffa359e9e7e2d690fb55d251639d07208dbc37caea"}, + {file = "debugpy-1.8.9-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365e556a4772d7d0d151d7eb0e77ec4db03bcd95f26b67b15742b88cacff88e9"}, + {file = "debugpy-1.8.9-cp38-cp38-win32.whl", hash = "sha256:54a7e6d3014c408eb37b0b06021366ee985f1539e12fe49ca2ee0d392d9ceca5"}, + {file = "debugpy-1.8.9-cp38-cp38-win_amd64.whl", hash = "sha256:8e99c0b1cc7bf86d83fb95d5ccdc4ad0586d4432d489d1f54e4055bcc795f693"}, + {file = "debugpy-1.8.9-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:7e8b079323a56f719977fde9d8115590cb5e7a1cba2fcee0986ef8817116e7c1"}, + {file = "debugpy-1.8.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6953b335b804a41f16a192fa2e7851bdcfd92173cbb2f9f777bb934f49baab65"}, + {file = "debugpy-1.8.9-cp39-cp39-win32.whl", hash = "sha256:7e646e62d4602bb8956db88b1e72fe63172148c1e25c041e03b103a25f36673c"}, + {file = "debugpy-1.8.9-cp39-cp39-win_amd64.whl", hash = "sha256:3d9755e77a2d680ce3d2c5394a444cf42be4a592caaf246dbfbdd100ffcf7ae5"}, + {file = "debugpy-1.8.9-py2.py3-none-any.whl", hash = "sha256:cc37a6c9987ad743d9c3a14fa1b1a14b7e4e6041f9dd0c8abf8895fe7a97b899"}, + {file = "debugpy-1.8.9.zip", hash = "sha256:1339e14c7d980407248f09824d1b25ff5c5616651689f1e0f0e51bdead3ea13e"}, ] [[package]] @@ -1148,24 +1177,24 @@ profile = ["gprof2dot (>=2022.7.29)"] [[package]] name = "distlib" -version = "0.3.8" +version = "0.3.9" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, + {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, + {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] [[package]] name = "django" -version = "5.1.1" +version = "5.1.4" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.10" files = [ - {file = "Django-5.1.1-py3-none-any.whl", hash = "sha256:71603f27dac22a6533fb38d83072eea9ddb4017fead6f67f2562a40402d61c3f"}, - {file = "Django-5.1.1.tar.gz", hash = "sha256:021ffb7fdab3d2d388bc8c7c2434eb9c1f6f4d09e6119010bbb1694dda286bc2"}, + {file = "Django-5.1.4-py3-none-any.whl", hash = "sha256:236e023f021f5ce7dee5779de7b286565fdea5f4ab86bae5338e3f7b69896cf0"}, + {file = "Django-5.1.4.tar.gz", hash = "sha256:de450c09e91879fa5a307f696e57c851955c910a438a35e6b4c895e86bedc82a"}, ] [package.dependencies] @@ -1286,18 +1315,18 @@ files = [ [[package]] name = "django-silk" -version = "5.2.0" +version = "5.3.1" description = "Silky smooth profiling for the Django Framework" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "django_silk-5.2.0-py3-none-any.whl", hash = "sha256:b3f01ccbf46611073603a6ac2b84f578bde978ad44785f42994c3d6f81398fdc"}, - {file = "django_silk-5.2.0.tar.gz", hash = "sha256:39ddeda80469d5495d1cbb53590a9bdd4ce30a7474dc16ac26b6cbc0d9521f50"}, + {file = "django_silk-5.3.1-py3-none-any.whl", hash = "sha256:7834580fabea5d9e8a32eabb0d9cb061cc37f0ec057d2933b4da761a53ae1bed"}, + {file = "django_silk-5.3.1.tar.gz", hash = "sha256:aa4ae73a90fcbd5159a810f81e15a0ec010619beab37c82957eb4012fd0016f0"}, ] [package.dependencies] autopep8 = "*" -Django = ">=3.2" +Django = ">=4.2" gprof2dot = ">=2017.09.19" sqlparse = "*" @@ -1431,38 +1460,56 @@ markdown = ["types-Markdown (>=0.1.5)"] [[package]] name = "djlint" -version = "1.35.2" +version = "1.36.3" description = "HTML Template Linter and Formatter" optional = false -python-versions = "<4.0,>=3.8" +python-versions = ">=3.9" files = [ - {file = "djlint-1.35.2-py3-none-any.whl", hash = "sha256:4ba995bad378f2afa77c8ea56ba1c14429d9ff26a18e8ae23bc71eedb9152243"}, - {file = "djlint-1.35.2.tar.gz", hash = "sha256:318de9d4b9b0061a111f8f5164ecbacd8215f449dd4bd5a76d2a691c815ee103"}, + {file = "djlint-1.36.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ae7c620b58e16d6bf003bd7de3f71376a7a3daa79dc02e77f3726d5a75243f2"}, + {file = "djlint-1.36.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e155ce0970d4a28d0a2e9f2e106733a2ad05910eee90e056b056d48049e4a97b"}, + {file = "djlint-1.36.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e8bb0406e60cc696806aa6226df137618f3889c72f2dbdfa76c908c99151579"}, + {file = "djlint-1.36.3-cp310-cp310-win_amd64.whl", hash = "sha256:76d32faf988ad58ef2e7a11d04046fc984b98391761bf1b61f9a6044da53d414"}, + {file = "djlint-1.36.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:32f7a5834000fff22e94d1d35f95aaf2e06f2af2cae18af0ed2a4e215d60e730"}, + {file = "djlint-1.36.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3eb1b9c0be499e63e8822a051e7e55f188ff1ab8172a85d338a8ae21c872060e"}, + {file = "djlint-1.36.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c2e0dd1f26eb472b8c84eb70d6482877b6497a1fd031d7534864088f016d5ea"}, + {file = "djlint-1.36.3-cp311-cp311-win_amd64.whl", hash = "sha256:a06b531ab9d049c46ad4d2365d1857004a1a9dd0c23c8eae94aa0d233c6ec00d"}, + {file = "djlint-1.36.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e66361a865e5e5a4bbcb40f56af7f256fd02cbf9d48b763a40172749cc294084"}, + {file = "djlint-1.36.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:36e102b80d83e9ac2e6be9a9ded32fb925945f6dbc7a7156e4415de1b0aa0dba"}, + {file = "djlint-1.36.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ac4b7370d80bd82281e57a470de8923ac494ffb571b89d8787cef57c738c69a"}, + {file = "djlint-1.36.3-cp312-cp312-win_amd64.whl", hash = "sha256:107cc56bbef13d60cc0ae774a4d52881bf98e37c02412e573827a3e549217e3a"}, + {file = "djlint-1.36.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2a9f51971d6e63c41ea9b3831c928e1f21ae6fe57e87a3452cfe672d10232433"}, + {file = "djlint-1.36.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:080c98714b55d8f0fef5c42beaee8247ebb2e3d46b0936473bd6c47808bb6302"}, + {file = "djlint-1.36.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f65a80e0b5cb13d357ea51ca6570b34c2d9d18974c1e57142de760ea27d49ed0"}, + {file = "djlint-1.36.3-cp313-cp313-win_amd64.whl", hash = "sha256:95ef6b67ef7f2b90d9434bba37d572031079001dc8524add85c00ef0386bda1e"}, + {file = "djlint-1.36.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e2317a32094d525bc41cd11c8dc064bf38d1b442c99cc3f7c4a2616b5e6ce6e"}, + {file = "djlint-1.36.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e82266c28793cd15f97b93535d72bfbc77306eaaf6b210dd90910383a814ee6c"}, + {file = "djlint-1.36.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01b2101c2d1b079e8d545e6d9d03487fcca14d2371e44cbfdedee15b0bf4567c"}, + {file = "djlint-1.36.3-cp39-cp39-win_amd64.whl", hash = "sha256:15cde63ef28beb5194ff4137883025f125676ece1b574b64a3e1c6daed734639"}, + {file = "djlint-1.36.3-py3-none-any.whl", hash = "sha256:0c05cd5b76785de2c41a2420c06ffd112800bfc0f9c0f399cc7cea7c42557f4c"}, + {file = "djlint-1.36.3.tar.gz", hash = "sha256:d85735da34bc7ac93ad8ef9b4822cc2a23d5f0ce33f25438737b8dca1d404f78"}, ] [package.dependencies] click = ">=8.0.1" colorama = ">=0.4.4" cssbeautifier = ">=1.14.4" -html-tag-names = ">=0.1.2" -html-void-elements = ">=0.1.0" jsbeautifier = ">=1.14.4" json5 = ">=0.9.11" -pathspec = ">=0.12.0" -PyYAML = ">=6.0" +pathspec = ">=0.12" +pyyaml = ">=6" regex = ">=2023" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} tqdm = ">=4.62.2" [[package]] name = "drf-yasg" -version = "1.21.7" +version = "1.21.8" description = "Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code." optional = false python-versions = ">=3.6" files = [ - {file = "drf-yasg-1.21.7.tar.gz", hash = "sha256:4c3b93068b3dfca6969ab111155e4dd6f7b2d680b98778de8fd460b7837bdb0d"}, - {file = "drf_yasg-1.21.7-py3-none-any.whl", hash = "sha256:f85642072c35e684356475781b7ecf5d218fff2c6185c040664dd49f0a4be181"}, + {file = "drf-yasg-1.21.8.tar.gz", hash = "sha256:cbb7f81c3d140f2207392b4bc5dde65384eeb58e1b7eea1a6d641dec2f7352a9"}, + {file = "drf_yasg-1.21.8-py3-none-any.whl", hash = "sha256:a410b235e7cc2c0f6b9d4f671e8efe6f2d27cba398fbd16064e16ef814998444"}, ] [package.dependencies] @@ -1566,69 +1613,70 @@ files = [ [[package]] name = "grpcio" -version = "1.60.2" +version = "1.66.2" description = "HTTP/2-based RPC framework" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "grpcio-1.60.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:2fe41ed7506725ef9f5b65eab98ff03a42b3faa4ce0cff4a1a951595f517d77d"}, - {file = "grpcio-1.60.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:575f0639baccb8b7fad8b7d25f8d5d712eaed7a070df1600015e3b8865ec0ee8"}, - {file = "grpcio-1.60.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:26a14ef7b5308f1d939984d680f6addaad9582d92888f4e8e0f9de468477e648"}, - {file = "grpcio-1.60.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfa22d8e88f897cea15328beb978ac178aa0212a65b3db318e4aed6600ffa5ca"}, - {file = "grpcio-1.60.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20948a2c1bc6557a8e86733f31800ecfc8217bce80580d736c591deb1d11c902"}, - {file = "grpcio-1.60.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:38de5f564a6a59ef3ef4179beb7d04b0e7d142e70d184144a7c037da42148234"}, - {file = "grpcio-1.60.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4ceef5fa235a3b2cdbad82334afa3842ae64ad1605894fd75f083551be2cae21"}, - {file = "grpcio-1.60.2-cp310-cp310-win32.whl", hash = "sha256:d3634bf34da305f59a02e3447a49ef3b1c5a39de031a0018677b6ff807eeb9b1"}, - {file = "grpcio-1.60.2-cp310-cp310-win_amd64.whl", hash = "sha256:7b1a3775f02e3c5ea09b1eb07b66da9ae6c576e24077e70ddadfe83c69996ad9"}, - {file = "grpcio-1.60.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:8b9d2c0604be5306aa1ea5476432015777c5d57145062e57a3ba2ab76f0fa4f6"}, - {file = "grpcio-1.60.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:cf51071a6f0f9bb654348f8a7d608eafdb75f768af1dc97975daa824f4f341b1"}, - {file = "grpcio-1.60.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:152bab3f33fa162c1aa6b404c48e5c466979b544c2d5ae163297f1d5cdf4a1ba"}, - {file = "grpcio-1.60.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ae6ef779078f9aea2ee65272a52c3e411a30ae427a1c43699741617c32d6279"}, - {file = "grpcio-1.60.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bad53f8e45aa937636afd21b3c6bc6b4e2577ef5f6eb93988881683b43ee8d"}, - {file = "grpcio-1.60.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:94c23eb89dde0a5a0a34a9aedbda039ed26c38d93e460d44dc9990d1ba610115"}, - {file = "grpcio-1.60.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:efbb311e24ce03da402b48ce12b9c73e83e38ea69f54089a122739d027a415ae"}, - {file = "grpcio-1.60.2-cp311-cp311-win32.whl", hash = "sha256:cbebbf033e2d6e8b53fc30876c132d6fb231ff24111af261f31ed85b3bfa7e4b"}, - {file = "grpcio-1.60.2-cp311-cp311-win_amd64.whl", hash = "sha256:0adf7136758b51d2c5816e99f6642f5055118e60b9069cbc65adf9dc6e1405fd"}, - {file = "grpcio-1.60.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:f0a94185bd36ffe037d42650eba01f8d346fdeaea2983bf10286182cf500cf30"}, - {file = "grpcio-1.60.2-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:f48123867cebdb4b23135742e6b7169a1355f3b0fdd582a01048dca83e8dfe3b"}, - {file = "grpcio-1.60.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:19ed5d1771beeb02bf3d9957ccfd62a7d96bc2d6bb2c17608ec7465914d80f06"}, - {file = "grpcio-1.60.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fa0899b6d32e7453f0e40c3a27a89a8638c4b44361872fce7cc4501c1777b46"}, - {file = "grpcio-1.60.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c22253ef0f8b3a0abb43bab65852950517ae476734ba1dac0201bb23ea395498"}, - {file = "grpcio-1.60.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e1db0e5b99ac20111ac2844c58cbdd4fcd55c2d9706a45200f02524858c3bd5"}, - {file = "grpcio-1.60.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b9186981a9e8a2fc1cb9d7767d4e7ef3eef6604fd7429ca14b45daa6c1c41a8a"}, - {file = "grpcio-1.60.2-cp312-cp312-win32.whl", hash = "sha256:380834c7e1c0d747c1b6d36c6f3ccaab5ce018792647fb0e46b0b8af455d56b3"}, - {file = "grpcio-1.60.2-cp312-cp312-win_amd64.whl", hash = "sha256:8f760e7e26b04bef2327a07facc35c6196e5d17a7e9a22aaa5c7f3b83a801dad"}, - {file = "grpcio-1.60.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:cb1115ccaa905d6999b195e351dfe5f17108b6a565c00f50e75576f38409d0b7"}, - {file = "grpcio-1.60.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:6917ce4fcbe2c8594db66a115c22eb86ac7bc82ed60ec824439b126bceda81ac"}, - {file = "grpcio-1.60.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:fd8d5f173bcbdcd2d680d1d102d315ca2ad56453fe9cdbe2136c99fa03690989"}, - {file = "grpcio-1.60.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1e784c047bc4a505a3d24c247eeb9ad4f4e73c6770b57bad7925413a43167a5"}, - {file = "grpcio-1.60.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cbdab576cc530743ad83ac31c6b2d67ebfd81ad1c23a416b8042ca27f3c081"}, - {file = "grpcio-1.60.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f93b9f88a18c72fb9158c02b57f89947b365acf2987cee438f043634f6941a2f"}, - {file = "grpcio-1.60.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:22389727d0d4c0dcbef68ece2b5e0b0f1342b84d48e6d3437369b2eb89769617"}, - {file = "grpcio-1.60.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7b266987e061e40ba3960624fd5ae5b2a23eef8e5f271b862ecbb74bab228e4c"}, - {file = "grpcio-1.60.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:553e9bedaca7b2f60ac88df3e8248d8a7a6c0ec670c2d4654da59f8e43a6a98e"}, - {file = "grpcio-1.60.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:8f8b1e64cf032f664f645510801fde3269bd13d8c455be850bc1c864c8992242"}, - {file = "grpcio-1.60.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:288e3b0d311f1a1e043df674100cedd506272a06a92dfa6e3696fa42adfbbce8"}, - {file = "grpcio-1.60.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccd8490e9e70602cadf34494e20a69b165ab34be67a9138b449ad4a4cc4266b8"}, - {file = "grpcio-1.60.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a53059bd61adf7d02cb459b745735041bba13df6e8cf16e08b078df30dc2804"}, - {file = "grpcio-1.60.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:25fc2858c498d2ec15a45e52013f801ec9ec51e91bdc3b4a30bf512595ab8a1a"}, - {file = "grpcio-1.60.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:48db67dc94a2cb403a2b601190048ecaa1b94565dff4b88d45b4e9fbb58f43d7"}, - {file = "grpcio-1.60.2-cp38-cp38-win32.whl", hash = "sha256:dc3c6d3eb24216f149674d30acaa40d90453acd58d3362d8f036350a4255f715"}, - {file = "grpcio-1.60.2-cp38-cp38-win_amd64.whl", hash = "sha256:a366ba639ef2f0d9647df0dcf331a9c6675330877a020cde001d119b80beeb43"}, - {file = "grpcio-1.60.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:225caeed6a1bfc9986418d4e47a727e22a8c26f13a381390ec8149d9fd4a1a90"}, - {file = "grpcio-1.60.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:d5afeb7cc4ce25974422e24f079f2c745563e3d442e7797b1a4a0d2126d7bfb4"}, - {file = "grpcio-1.60.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:0eee046b7ed6f5b1460dfa4b57713fd1e2f215bbfbf479e22c10d93de4a49f67"}, - {file = "grpcio-1.60.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50c8a675fd9f9643e3fc5a4948b6c0c687039a354da4ea0c9664d5c608baa10b"}, - {file = "grpcio-1.60.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d5df077efcc101eca7accfddb98a847f6b2443a9e56d9205baefdfc9a39d56c"}, - {file = "grpcio-1.60.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0a96a698404b0539c411234a7af779e9b99cf5c6d4c054d22747250955479e56"}, - {file = "grpcio-1.60.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c9c7f7ceb9a61ab86e53a697a8895df536ffc9bc353f6dd698b54dd9181040c7"}, - {file = "grpcio-1.60.2-cp39-cp39-win32.whl", hash = "sha256:96821dac4d58b179d67b27922553fd3b47ba4adf10ba66bdbedb4e6f44c0d018"}, - {file = "grpcio-1.60.2-cp39-cp39-win_amd64.whl", hash = "sha256:5bbecd71d269cd32853af8153426c9eae76be82844ff2e5ae185d2fa95dadd61"}, - {file = "grpcio-1.60.2.tar.gz", hash = "sha256:595264cd4ba6a989d6a56cfa72ac34198ba696ab33d06ee223ad55a7d3c06105"}, + {file = "grpcio-1.66.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:fe96281713168a3270878255983d2cb1a97e034325c8c2c25169a69289d3ecfa"}, + {file = "grpcio-1.66.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:73fc8f8b9b5c4a03e802b3cd0c18b2b06b410d3c1dcbef989fdeb943bd44aff7"}, + {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:03b0b307ba26fae695e067b94cbb014e27390f8bc5ac7a3a39b7723fed085604"}, + {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d69ce1f324dc2d71e40c9261d3fdbe7d4c9d60f332069ff9b2a4d8a257c7b2b"}, + {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05bc2ceadc2529ab0b227b1310d249d95d9001cd106aa4d31e8871ad3c428d73"}, + {file = "grpcio-1.66.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ac475e8da31484efa25abb774674d837b343afb78bb3bcdef10f81a93e3d6bf"}, + {file = "grpcio-1.66.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0be4e0490c28da5377283861bed2941d1d20ec017ca397a5df4394d1c31a9b50"}, + {file = "grpcio-1.66.2-cp310-cp310-win32.whl", hash = "sha256:4e504572433f4e72b12394977679161d495c4c9581ba34a88d843eaf0f2fbd39"}, + {file = "grpcio-1.66.2-cp310-cp310-win_amd64.whl", hash = "sha256:2018b053aa15782db2541ca01a7edb56a0bf18c77efed975392583725974b249"}, + {file = "grpcio-1.66.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:2335c58560a9e92ac58ff2bc5649952f9b37d0735608242973c7a8b94a6437d8"}, + {file = "grpcio-1.66.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45a3d462826f4868b442a6b8fdbe8b87b45eb4f5b5308168c156b21eca43f61c"}, + {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a9539f01cb04950fd4b5ab458e64a15f84c2acc273670072abe49a3f29bbad54"}, + {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce89f5876662f146d4c1f695dda29d4433a5d01c8681fbd2539afff535da14d4"}, + {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25a14af966438cddf498b2e338f88d1c9706f3493b1d73b93f695c99c5f0e2a"}, + {file = "grpcio-1.66.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6001e575b8bbd89eee11960bb640b6da6ae110cf08113a075f1e2051cc596cae"}, + {file = "grpcio-1.66.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ea1d062c9230278793820146c95d038dc0f468cbdd172eec3363e42ff1c7d01"}, + {file = "grpcio-1.66.2-cp311-cp311-win32.whl", hash = "sha256:38b68498ff579a3b1ee8f93a05eb48dc2595795f2f62716e797dc24774c1aaa8"}, + {file = "grpcio-1.66.2-cp311-cp311-win_amd64.whl", hash = "sha256:6851de821249340bdb100df5eacfecfc4e6075fa85c6df7ee0eb213170ec8e5d"}, + {file = "grpcio-1.66.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:802d84fd3d50614170649853d121baaaa305de7b65b3e01759247e768d691ddf"}, + {file = "grpcio-1.66.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:80fd702ba7e432994df208f27514280b4b5c6843e12a48759c9255679ad38db8"}, + {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:12fda97ffae55e6526825daf25ad0fa37483685952b5d0f910d6405c87e3adb6"}, + {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:950da58d7d80abd0ea68757769c9db0a95b31163e53e5bb60438d263f4bed7b7"}, + {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e636ce23273683b00410f1971d209bf3689238cf5538d960adc3cdfe80dd0dbd"}, + {file = "grpcio-1.66.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a917d26e0fe980b0ac7bfcc1a3c4ad6a9a4612c911d33efb55ed7833c749b0ee"}, + {file = "grpcio-1.66.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49f0ca7ae850f59f828a723a9064cadbed90f1ece179d375966546499b8a2c9c"}, + {file = "grpcio-1.66.2-cp312-cp312-win32.whl", hash = "sha256:31fd163105464797a72d901a06472860845ac157389e10f12631025b3e4d0453"}, + {file = "grpcio-1.66.2-cp312-cp312-win_amd64.whl", hash = "sha256:ff1f7882e56c40b0d33c4922c15dfa30612f05fb785074a012f7cda74d1c3679"}, + {file = "grpcio-1.66.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:3b00efc473b20d8bf83e0e1ae661b98951ca56111feb9b9611df8efc4fe5d55d"}, + {file = "grpcio-1.66.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1caa38fb22a8578ab8393da99d4b8641e3a80abc8fd52646f1ecc92bcb8dee34"}, + {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c408f5ef75cfffa113cacd8b0c0e3611cbfd47701ca3cdc090594109b9fcbaed"}, + {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c806852deaedee9ce8280fe98955c9103f62912a5b2d5ee7e3eaa284a6d8d8e7"}, + {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f145cc21836c332c67baa6fc81099d1d27e266401565bf481948010d6ea32d46"}, + {file = "grpcio-1.66.2-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:73e3b425c1e155730273f73e419de3074aa5c5e936771ee0e4af0814631fb30a"}, + {file = "grpcio-1.66.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c509a4f78114cbc5f0740eb3d7a74985fd2eff022971bc9bc31f8bc93e66a3b"}, + {file = "grpcio-1.66.2-cp313-cp313-win32.whl", hash = "sha256:20657d6b8cfed7db5e11b62ff7dfe2e12064ea78e93f1434d61888834bc86d75"}, + {file = "grpcio-1.66.2-cp313-cp313-win_amd64.whl", hash = "sha256:fb70487c95786e345af5e854ffec8cb8cc781bcc5df7930c4fbb7feaa72e1cdf"}, + {file = "grpcio-1.66.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:a18e20d8321c6400185b4263e27982488cb5cdd62da69147087a76a24ef4e7e3"}, + {file = "grpcio-1.66.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:02697eb4a5cbe5a9639f57323b4c37bcb3ab2d48cec5da3dc2f13334d72790dd"}, + {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:99a641995a6bc4287a6315989ee591ff58507aa1cbe4c2e70d88411c4dcc0839"}, + {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ed71e81782966ffead60268bbda31ea3f725ebf8aa73634d5dda44f2cf3fb9c"}, + {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbd27c24a4cc5e195a7f56cfd9312e366d5d61b86e36d46bbe538457ea6eb8dd"}, + {file = "grpcio-1.66.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d9a9724a156c8ec6a379869b23ba3323b7ea3600851c91489b871e375f710bc8"}, + {file = "grpcio-1.66.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d8d4732cc5052e92cea2f78b233c2e2a52998ac40cd651f40e398893ad0d06ec"}, + {file = "grpcio-1.66.2-cp38-cp38-win32.whl", hash = "sha256:7b2c86457145ce14c38e5bf6bdc19ef88e66c5fee2c3d83285c5aef026ba93b3"}, + {file = "grpcio-1.66.2-cp38-cp38-win_amd64.whl", hash = "sha256:e88264caad6d8d00e7913996030bac8ad5f26b7411495848cc218bd3a9040b6c"}, + {file = "grpcio-1.66.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:c400ba5675b67025c8a9f48aa846f12a39cf0c44df5cd060e23fda5b30e9359d"}, + {file = "grpcio-1.66.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:66a0cd8ba6512b401d7ed46bb03f4ee455839957f28b8d61e7708056a806ba6a"}, + {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:06de8ec0bd71be123eec15b0e0d457474931c2c407869b6c349bd9bed4adbac3"}, + {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb57870449dfcfac428afbb5a877829fcb0d6db9d9baa1148705739e9083880e"}, + {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b672abf90a964bfde2d0ecbce30f2329a47498ba75ce6f4da35a2f4532b7acbc"}, + {file = "grpcio-1.66.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ad2efdbe90c73b0434cbe64ed372e12414ad03c06262279b104a029d1889d13e"}, + {file = "grpcio-1.66.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c3a99c519f4638e700e9e3f83952e27e2ea10873eecd7935823dab0c1c9250e"}, + {file = "grpcio-1.66.2-cp39-cp39-win32.whl", hash = "sha256:78fa51ebc2d9242c0fc5db0feecc57a9943303b46664ad89921f5079e2e4ada7"}, + {file = "grpcio-1.66.2-cp39-cp39-win_amd64.whl", hash = "sha256:728bdf36a186e7f51da73be7f8d09457a03061be848718d0edf000e709418987"}, + {file = "grpcio-1.66.2.tar.gz", hash = "sha256:563588c587b75c34b928bc428548e5b00ea38c46972181a4d8b75ba7e3f24231"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.60.2)"] +protobuf = ["grpcio-tools (>=1.66.2)"] [[package]] name = "gunicorn" @@ -1653,127 +1701,120 @@ tornado = ["tornado (>=0.2)"] [[package]] name = "hiredis" -version = "3.0.0" +version = "3.1.0" description = "Python wrapper for hiredis" optional = false python-versions = ">=3.8" files = [ - {file = "hiredis-3.0.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:4b182791c41c5eb1d9ed736f0ff81694b06937ca14b0d4dadde5dadba7ff6dae"}, - {file = "hiredis-3.0.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:13c275b483a052dd645eb2cb60d6380f1f5215e4c22d6207e17b86be6dd87ffa"}, - {file = "hiredis-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1018cc7f12824506f165027eabb302735b49e63af73eb4d5450c66c88f47026"}, - {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83a29cc7b21b746cb6a480189e49f49b2072812c445e66a9e38d2004d496b81c"}, - {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e241fab6332e8fb5f14af00a4a9c6aefa22f19a336c069b7ddbf28ef8341e8d6"}, - {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1fb8de899f0145d6c4d5d4bd0ee88a78eb980a7ffabd51e9889251b8f58f1785"}, - {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b23291951959141173eec10f8573538e9349fa27f47a0c34323d1970bf891ee5"}, - {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e421ac9e4b5efc11705a0d5149e641d4defdc07077f748667f359e60dc904420"}, - {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:77c8006c12154c37691b24ff293c077300c22944018c3ff70094a33e10c1d795"}, - {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:41afc0d3c18b59eb50970479a9c0e5544fb4b95e3a79cf2fbaece6ddefb926fe"}, - {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:04ccae6dcd9647eae6025425ab64edb4d79fde8b9e6e115ebfabc6830170e3b2"}, - {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fe91d62b0594db5ea7d23fc2192182b1a7b6973f628a9b8b2e0a42a2be721ac6"}, - {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:99516d99316062824a24d145d694f5b0d030c80da693ea6f8c4ecf71a251d8bb"}, - {file = "hiredis-3.0.0-cp310-cp310-win32.whl", hash = "sha256:562eaf820de045eb487afaa37e6293fe7eceb5b25e158b5a1974b7e40bf04543"}, - {file = "hiredis-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1c81c89ed765198da27412aa21478f30d54ef69bf5e4480089d9c3f77b8f882"}, - {file = "hiredis-3.0.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:4664dedcd5933364756d7251a7ea86d60246ccf73a2e00912872dacbfcef8978"}, - {file = "hiredis-3.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:47de0bbccf4c8a9f99d82d225f7672b9dd690d8fd872007b933ef51a302c9fa6"}, - {file = "hiredis-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e43679eca508ba8240d016d8cca9d27342d70184773c15bea78a23c87a1922f1"}, - {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13c345e7278c210317e77e1934b27b61394fee0dec2e8bd47e71570900f75823"}, - {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00018f22f38530768b73ea86c11f47e8d4df65facd4e562bd78773bd1baef35e"}, - {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ea3a86405baa8eb0d3639ced6926ad03e07113de54cb00fd7510cb0db76a89d"}, - {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c073848d2b1d5561f3903879ccf4e1a70c9b1e7566c7bdcc98d082fa3e7f0a1d"}, - {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a8dffb5f5b3415a4669d25de48b617fd9d44b0bccfc4c2ab24b06406ecc9ecb"}, - {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:22c17c96143c2a62dfd61b13803bc5de2ac526b8768d2141c018b965d0333b66"}, - {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c3ece960008dab66c6b8bb3a1350764677ee7c74ccd6270aaf1b1caf9ccebb46"}, - {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f75999ae00a920f7dce6ecae76fa5e8674a3110e5a75f12c7a2c75ae1af53396"}, - {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e069967cbd5e1900aafc4b5943888f6d34937fc59bf8918a1a546cb729b4b1e4"}, - {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0aacc0a78e1d94d843a6d191f224a35893e6bdfeb77a4a89264155015c65f126"}, - {file = "hiredis-3.0.0-cp311-cp311-win32.whl", hash = "sha256:719c32147ba29528cb451f037bf837dcdda4ff3ddb6cdb12c4216b0973174718"}, - {file = "hiredis-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:bdc144d56333c52c853c31b4e2e52cfbdb22d3da4374c00f5f3d67c42158970f"}, - {file = "hiredis-3.0.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:484025d2eb8f6348f7876fc5a2ee742f568915039fcb31b478fd5c242bb0fe3a"}, - {file = "hiredis-3.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:fcdb552ffd97151dab8e7bc3ab556dfa1512556b48a367db94b5c20253a35ee1"}, - {file = "hiredis-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bb6f9fd92f147ba11d338ef5c68af4fd2908739c09e51f186e1d90958c68cc1"}, - {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa86bf9a0ed339ec9e8a9a9d0ae4dccd8671625c83f9f9f2640729b15e07fbfd"}, - {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e194a0d5df9456995d8f510eab9f529213e7326af6b94770abf8f8b7952ddcaa"}, - {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a1df39d74ec507d79c7a82c8063eee60bf80537cdeee652f576059b9cdd15c"}, - {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f91456507427ba36fd81b2ca11053a8e112c775325acc74e993201ea912d63e9"}, - {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9862db92ef67a8a02e0d5370f07d380e14577ecb281b79720e0d7a89aedb9ee5"}, - {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d10fcd9e0eeab835f492832b2a6edb5940e2f1230155f33006a8dfd3bd2c94e4"}, - {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:48727d7d405d03977d01885f317328dc21d639096308de126c2c4e9950cbd3c9"}, - {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e0bb6102ebe2efecf8a3292c6660a0e6fac98176af6de67f020bea1c2343717"}, - {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:df274e3abb4df40f4c7274dd3e587dfbb25691826c948bc98d5fead019dfb001"}, - {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:034925b5fb514f7b11aac38cd55b3fd7e9d3af23bd6497f3f20aa5b8ba58e232"}, - {file = "hiredis-3.0.0-cp312-cp312-win32.whl", hash = "sha256:120f2dda469b28d12ccff7c2230225162e174657b49cf4cd119db525414ae281"}, - {file = "hiredis-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:e584fe5f4e6681d8762982be055f1534e0170f6308a7a90f58d737bab12ff6a8"}, - {file = "hiredis-3.0.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:122171ff47d96ed8dd4bba6c0e41d8afaba3e8194949f7720431a62aa29d8895"}, - {file = "hiredis-3.0.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:ba9fc605ac558f0de67463fb588722878641e6fa1dabcda979e8e69ff581d0bd"}, - {file = "hiredis-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a631e2990b8be23178f655cae8ac6c7422af478c420dd54e25f2e26c29e766f1"}, - {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63482db3fadebadc1d01ad33afa6045ebe2ea528eb77ccaabd33ee7d9c2bad48"}, - {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f669212c390eebfbe03c4e20181f5970b82c5d0a0ad1df1785f7ffbe7d61150"}, - {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a49ef161739f8018c69b371528bdb47d7342edfdee9ddc75a4d8caddf45a6e"}, - {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98a152052b8878e5e43a2e3a14075218adafc759547c98668a21e9485882696c"}, - {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50a196af0ce657fcde9bf8a0bbe1032e22c64d8fcec2bc926a35e7ff68b3a166"}, - {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f2f312eef8aafc2255e3585dcf94d5da116c43ef837db91db9ecdc1bc930072d"}, - {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:6ca41fa40fa019cde42c21add74aadd775e71458051a15a352eabeb12eb4d084"}, - {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:6eecb343c70629f5af55a8b3e53264e44fa04e155ef7989de13668a0cb102a90"}, - {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:c3fdad75e7837a475900a1d3a5cc09aa024293c3b0605155da2d42f41bc0e482"}, - {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8854969e7480e8d61ed7549eb232d95082a743e94138d98d7222ba4e9f7ecacd"}, - {file = "hiredis-3.0.0-cp38-cp38-win32.whl", hash = "sha256:f114a6c86edbf17554672b050cce72abf489fe58d583c7921904d5f1c9691605"}, - {file = "hiredis-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:7d99b91e42217d7b4b63354b15b41ce960e27d216783e04c4a350224d55842a4"}, - {file = "hiredis-3.0.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:4c6efcbb5687cf8d2aedcc2c3ed4ac6feae90b8547427d417111194873b66b06"}, - {file = "hiredis-3.0.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5b5cff42a522a0d81c2ae7eae5e56d0ee7365e0c4ad50c4de467d8957aff4414"}, - {file = "hiredis-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:82f794d564f4bc76b80c50b03267fe5d6589e93f08e66b7a2f674faa2fa76ebc"}, - {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7a4c1791d7aa7e192f60fe028ae409f18ccdd540f8b1e6aeb0df7816c77e4a4"}, - {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2537b2cd98192323fce4244c8edbf11f3cac548a9d633dbbb12b48702f379f4"}, - {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fed69bbaa307040c62195a269f82fc3edf46b510a17abb6b30a15d7dab548df"}, - {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869f6d5537d243080f44253491bb30aa1ec3c21754003b3bddeadedeb65842b0"}, - {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d435ae89073d7cd51e6b6bf78369c412216261c9c01662e7008ff00978153729"}, - {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:204b79b30a0e6be0dc2301a4d385bb61472809f09c49f400497f1cdd5a165c66"}, - {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ea635101b739c12effd189cc19b2671c268abb03013fd1f6321ca29df3ca625"}, - {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f359175197fd833c8dd7a8c288f1516be45415bb5c939862ab60c2918e1e1943"}, - {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac6d929cb33dd12ad3424b75725975f0a54b5b12dbff95f2a2d660c510aa106d"}, - {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:100431e04d25a522ef2c3b94f294c4219c4de3bfc7d557b6253296145a144c11"}, - {file = "hiredis-3.0.0-cp39-cp39-win32.whl", hash = "sha256:e1a9c14ae9573d172dc050a6f63a644457df5d01ec4d35a6a0f097f812930f83"}, - {file = "hiredis-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:54a6dd7b478e6eb01ce15b3bb5bf771e108c6c148315bf194eb2ab776a3cac4d"}, - {file = "hiredis-3.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:50da7a9edf371441dfcc56288d790985ee9840d982750580710a9789b8f4a290"}, - {file = "hiredis-3.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9b285ef6bf1581310b0d5e8f6ce64f790a1c40e89c660e1320b35f7515433672"}, - {file = "hiredis-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dcfa684966f25b335072115de2f920228a3c2caf79d4bfa2b30f6e4f674a948"}, - {file = "hiredis-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a41be8af1fd78ca97bc948d789a09b730d1e7587d07ca53af05758f31f4b985d"}, - {file = "hiredis-3.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:038756db735e417ab36ee6fd7725ce412385ed2bd0767e8179a4755ea11b804f"}, - {file = "hiredis-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fcecbd39bd42cef905c0b51c9689c39d0cc8b88b1671e7f40d4fb213423aef3a"}, - {file = "hiredis-3.0.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a131377493a59fb0f5eaeb2afd49c6540cafcfba5b0b3752bed707be9e7c4eaf"}, - {file = "hiredis-3.0.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d22c53f0ec5c18ecb3d92aa9420563b1c5d657d53f01356114978107b00b860"}, - {file = "hiredis-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8a91e9520fbc65a799943e5c970ffbcd67905744d8becf2e75f9f0a5e8414f0"}, - {file = "hiredis-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dc8043959b50141df58ab4f398e8ae84c6f9e673a2c9407be65fc789138f4a6"}, - {file = "hiredis-3.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51b99cfac514173d7b8abdfe10338193e8a0eccdfe1870b646009d2fb7cbe4b5"}, - {file = "hiredis-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fa1fcad89d8a41d8dc10b1e54951ec1e161deabd84ed5a2c95c3c7213bdb3514"}, - {file = "hiredis-3.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:898636a06d9bf575d2c594129085ad6b713414038276a4bfc5db7646b8a5be78"}, - {file = "hiredis-3.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:466f836dbcf86de3f9692097a7a01533dc9926986022c6617dc364a402b265c5"}, - {file = "hiredis-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23142a8af92a13fc1e3f2ca1d940df3dcf2af1d176be41fe8d89e30a837a0b60"}, - {file = "hiredis-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:793c80a3d6b0b0e8196a2d5de37a08330125668c8012922685e17aa9108c33ac"}, - {file = "hiredis-3.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:467d28112c7faa29b7db743f40803d927c8591e9da02b6ce3d5fadc170a542a2"}, - {file = "hiredis-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:dc384874a719c767b50a30750f937af18842ee5e288afba95a5a3ed703b1515a"}, - {file = "hiredis-3.0.0.tar.gz", hash = "sha256:fed8581ae26345dea1f1e0d1a96e05041a727a45e7d8d459164583e23c6ac441"}, -] - -[[package]] -name = "html-tag-names" -version = "0.1.2" -description = "List of known HTML tag names" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "html-tag-names-0.1.2.tar.gz", hash = "sha256:04924aca48770f36b5a41c27e4d917062507be05118acb0ba869c97389084297"}, - {file = "html_tag_names-0.1.2-py3-none-any.whl", hash = "sha256:eeb69ef21078486b615241f0393a72b41352c5219ee648e7c61f5632d26f0420"}, -] - -[[package]] -name = "html-void-elements" -version = "0.1.0" -description = "List of HTML void tag names." -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "html-void-elements-0.1.0.tar.gz", hash = "sha256:931b88f84cd606fee0b582c28fcd00e41d7149421fb673e1e1abd2f0c4f231f0"}, - {file = "html_void_elements-0.1.0-py3-none-any.whl", hash = "sha256:784cf39db03cdeb017320d9301009f8f3480f9d7b254d0974272e80e0cb5e0d2"}, + {file = "hiredis-3.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:2892db9db21f0cf7cc298d09f85d3e1f6dc4c4c24463ab67f79bc7a006d51867"}, + {file = "hiredis-3.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:93cfa6cc25ee2ceb0be81dc61eca9995160b9e16bdb7cca4a00607d57e998918"}, + {file = "hiredis-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2af62070aa9433802cae7be7364d5e82f76462c6a2ae34e53008b637aaa9a156"}, + {file = "hiredis-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:072c162260ebb1d892683107da22d0d5da7a1414739eae4e185cac22fe89627f"}, + {file = "hiredis-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b232c43e89755ba332c2745ddab059c0bc1a0f01448a3a14d506f8448b1ce6"}, + {file = "hiredis-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb5316c9a65c4dde80796aa245b76011bab64eb84461a77b0a61c1bf2970bcc9"}, + {file = "hiredis-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e812a4e656bbd1c1c15c844b28259c49e26bb384837e44e8d2aa55412c91d2f7"}, + {file = "hiredis-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93a6c9230e5a5565847130c0e1005c8d3aa5ca681feb0ed542c4651323d32feb"}, + {file = "hiredis-3.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a5f65e89ce50a94d9490d5442a649c6116f53f216c8c14eb37cf9637956482b2"}, + {file = "hiredis-3.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b2d6e33601c67c074c367fdccdd6033e642284e7a56adc130f18f724c378ca8"}, + {file = "hiredis-3.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bad3b1e0c83849910f28c95953417106f539277035a4b515d1425f93947bc28f"}, + {file = "hiredis-3.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9646de31f5994e6218311dcf216e971703dbf804c510fd3f84ddb9813c495824"}, + {file = "hiredis-3.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59a9230f3aa38a33d09d8171400de202f575d7a38869e5ce2947829bca6fe359"}, + {file = "hiredis-3.1.0-cp310-cp310-win32.whl", hash = "sha256:0322d70f3328b97da14b6e98b18f0090a12ed8a8bf7ae20932e2eb9d1bb0aa2c"}, + {file = "hiredis-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:802474c18e878b3f9905e160a8b7df87d57885758083eda76c5978265acb41aa"}, + {file = "hiredis-3.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:c339ff4b4739b2a40da463763dd566129762f72926bca611ad9a457a9fe64abd"}, + {file = "hiredis-3.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:0ffa2552f704a45954627697a378fc2f559004e53055b82f00daf30bd4305330"}, + {file = "hiredis-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9acf7f0e7106f631cd618eb60ec9bbd6e43045addd5310f66ba1177209567e59"}, + {file = "hiredis-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea4f5ecf9dbea93c827486f59c606684c3496ea71c7ba9a8131932780696e61a"}, + {file = "hiredis-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39efab176fca3d5111075f6ba56cd864f18db46d858289d39360c5672e0e5c3e"}, + {file = "hiredis-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1110eae007f30e70a058d743e369c24430327cd01fd97d99519d6794a58dd587"}, + {file = "hiredis-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b390f63191bcccbb6044d4c118acdf4fa55f38e5658ac4cfd5a33a6f0c07659"}, + {file = "hiredis-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72a98ccc7b8ec9ce0100ecf59f45f05d2023606e8e3676b07a316d1c1c364072"}, + {file = "hiredis-3.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7c76e751fd1e2f221dec09cdc24040ee486886e943d5d7ffc256e8cf15c75e51"}, + {file = "hiredis-3.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7d3880f213b6f14e9c69ce52beffd1748eecc8669698c4782761887273b6e1bd"}, + {file = "hiredis-3.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:87c2b3fe7e7c96eba376506a76e11514e07e848f737b254e0973e4b5c3a491e9"}, + {file = "hiredis-3.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d3cfb4089e96f8f8ee9554da93148a9261aa6612ad2cc202c1a494c7b712e31f"}, + {file = "hiredis-3.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f12018e5c5f866a1c3f7017cb2d88e5c6f9440df2281e48865a2b6c40f247f4"}, + {file = "hiredis-3.1.0-cp311-cp311-win32.whl", hash = "sha256:107b66ce977bb2dff8f2239e68344360a75d05fed3d9fa0570ac4d3020ce2396"}, + {file = "hiredis-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:8f1240bde53d3d1676f0aba61b3661560dc9a681cae24d9de33e650864029aa4"}, + {file = "hiredis-3.1.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:f7c7f89e0bc4246115754e2eda078a111282f6d6ecc6fb458557b724fe6f2aac"}, + {file = "hiredis-3.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:3dbf9163296fa45fbddcfc4c5900f10e9ddadda37117dbfb641e327e536b53e0"}, + {file = "hiredis-3.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af46a4be0e82df470f68f35316fa16cd1e134d1c5092fc1082e1aad64cce716d"}, + {file = "hiredis-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc63d698c43aea500a84d8b083f830c03808b6cf3933ae4d35a27f0a3d881652"}, + {file = "hiredis-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:676b3d88674134bfaaf70dac181d1790b0f33b3187bfb9da9221e17e0e624f83"}, + {file = "hiredis-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aed10d9df1e2fb0011db2713ac64497462e9c2c0208b648c97569da772b959ca"}, + {file = "hiredis-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b5bd8adfe8742e331a94cccd782bffea251fa70d9a709e71f4510f50794d700"}, + {file = "hiredis-3.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fc4e35b4afb0af6da55495dd0742ad32ab88150428a6ecdbb3085cbd60714e8"}, + {file = "hiredis-3.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:89b83e76eb00ab0464e7b0752a3ffcb02626e742e9509bc141424a9c3202e8dc"}, + {file = "hiredis-3.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:98ebf08c907836b70a8f40e030df8ab6f174dc7f6fa765251d813e89f14069d8"}, + {file = "hiredis-3.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6c840b9cec086328f2ee2cfee0038b5d6bbb514bac7b5e579da6e346eaac056c"}, + {file = "hiredis-3.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c5c44e9fa6f4462d0330cb5f5d46fa652512fc86b41d4d1974d0356f263e9105"}, + {file = "hiredis-3.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e665b14ab50aa175cfa306fcb00fffd4e3ff02ceb36ca6a4df00b1246d6a73c4"}, + {file = "hiredis-3.1.0-cp312-cp312-win32.whl", hash = "sha256:bd33db977ac7af97e8d035ffadb163b00546be22e5f1297b2123f5f9bf0f8a21"}, + {file = "hiredis-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:37aed4aa9348600145e2d019c7be27855e503ecc4906c6976ff2f3b52e3d5d97"}, + {file = "hiredis-3.1.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:b87cddd8107487863fed6994de51e5594a0be267b0b19e213694e99cdd614623"}, + {file = "hiredis-3.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:d302deff8cb63a7feffc1844e4dafc8076e566bbf10c5aaaf0f4fe791b8a6bd0"}, + {file = "hiredis-3.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a018340c073cf88cb635b2bedff96619df2f666018c655e7911f46fa2c1c178"}, + {file = "hiredis-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1e8ba6414ac1ae536129e18c069f3eb497df5a74e136e3566471620a4fa5f95"}, + {file = "hiredis-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a86b9fef256c2beb162244791fdc025aa55f936d6358e86e2020e512fe2e4972"}, + {file = "hiredis-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7acdc68e29a446ad17aadaff19c981a36b3bd8c894c3520412c8a7ab1c3e0de7"}, + {file = "hiredis-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7e06baea05de57e1e7548064f505a6964e992674fe61b8f274afe2ac93b6371"}, + {file = "hiredis-3.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35b5fc061c8a0dbfdb440053280504d6aaa8d9726bd4d1d0e1cfcbbdf0d60b73"}, + {file = "hiredis-3.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c89d2dcb271d24c44f02264233b75d5db8c58831190fa92456a90b87fa17b748"}, + {file = "hiredis-3.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:aa36688c10a08f626fddcf68c2b1b91b0e90b070c26e550a4151a877f5c2d431"}, + {file = "hiredis-3.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3982a9c16c1c4bc05a00b65d01ffb8d80ea1a7b6b533be2f1a769d3e989d2c0"}, + {file = "hiredis-3.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d1a6f889514ee2452300c9a06862fceedef22a2891f1c421a27b1ba52ef130b2"}, + {file = "hiredis-3.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8a45ff7915392a55d9386bb235ea1d1eb9960615f301979f02143fc20036b699"}, + {file = "hiredis-3.1.0-cp313-cp313-win32.whl", hash = "sha256:539e5bb725b62b76a5319a4e68fc7085f01349abc2316ef3df608ea0883c51d2"}, + {file = "hiredis-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9020fd7e58f489fda6a928c31355add0e665fd6b87b21954e675cf9943eafa32"}, + {file = "hiredis-3.1.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:b621a89fc29b3f4b01be6640ec81a6a94b5382bc78fecb876408d57a071e45aa"}, + {file = "hiredis-3.1.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:363e21fba55e1a26349dc9ca7da6b14332123879b6359bcee4a9acecb40ca33b"}, + {file = "hiredis-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c156156798729eadc9ab76ffee96c88b93cc1c3b493f4dd0a4341f53939194ee"}, + {file = "hiredis-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e38d8a325f9a6afac1b1c72d996d1add9e1b99696ce9410538ba5e9aa8fdba02"}, + {file = "hiredis-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3004ef7436feb7bfa61c0b36d422b8fb8c29aaa1a514c9405f0fdee5e9694dd3"}, + {file = "hiredis-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13f5b16f97d0bbd1c04ce367c49097d1214d60e11f9fee7ef2a9b54e0a6645c8"}, + {file = "hiredis-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:230dd0e77cb0f525f58a1306a7b4aaf078037fc5229110922332ca46f90821bb"}, + {file = "hiredis-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d968116caddd19d63120d1298e62b1bbc694db3360ed0d5df8c3a97edbc12552"}, + {file = "hiredis-3.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:511e36a6fa41d3efab3cd5cd70ac388ed825993b9e66fa3b0e47cf27a2f5ffee"}, + {file = "hiredis-3.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c5cd20804e3cb0d31e7d899d8dd091f569c33fe40d4bade670a067ab7d31c2ac"}, + {file = "hiredis-3.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:09e89e7d34cfe5ca8f7a869fca827d1af0afe8aaddb26b38c01058730edb79ad"}, + {file = "hiredis-3.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:570cbf31413c77fe5e7c157f2943ca4400493ddd9cf2184731cfcafc753becd7"}, + {file = "hiredis-3.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b9b4da8162cf289781732d6a5ba01d820c42c05943fcdb7de307d03639961db3"}, + {file = "hiredis-3.1.0-cp38-cp38-win32.whl", hash = "sha256:bc117a04bcb461d3bb1b2c5b417aee3442e1e8aa33ebc800481431f4c09fe0c5"}, + {file = "hiredis-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:34f3f5f0354db2d6797a6fb08d2c036a50af62a1d919d122c1c784304ef49347"}, + {file = "hiredis-3.1.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:a26fa888025badb5563f283cc19594c215a413e905729e59a5f7cf3f46d66c32"}, + {file = "hiredis-3.1.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:f50763cd819d4a52a47b5966d4bb47dee34b637c5fa6402509800eee6ecb61e6"}, + {file = "hiredis-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6d1c9e1fce5e0a94072667ae2bf0142b89ebbb1917d3531184e060a43f3ee11"}, + {file = "hiredis-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e38d7a56b1a79ed0bbb9e6fe376d82e3f4dcc646ae47472f2c858e19a597c112"}, + {file = "hiredis-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ef5ad8b91530e4d10a68562b0a380ea22705a60e88cecee086d7c63a38564ce"}, + {file = "hiredis-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf3d2299b054e57a9f97ca08704c2843e44f29b57dc69b76a2592ecd212efe1a"}, + {file = "hiredis-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93811d60b0f73d0f049c86f4373a3833b4a38fce374ab151074d929553eb4304"}, + {file = "hiredis-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e703ff860c1d83abbcf57012b309ead02b56b60e85150c6c3bfb37cbb16ebf"}, + {file = "hiredis-3.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f9ea0678806c53d96758e74c6a898f9d506a2e3367a344757f768bef9e069366"}, + {file = "hiredis-3.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cf6844035abf47d52a1c3f4257255af3bf3b0f14d559b08eaa45885418c6c55d"}, + {file = "hiredis-3.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7acf35cfa7ec9e1e7559c04e7095628f7d06049b5f24dcb58c1a55ef6dc689f8"}, + {file = "hiredis-3.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b885695dce7a39b1fd9a609ed9c4cf312e53df2ec028d5a78af7a891b5fbea4d"}, + {file = "hiredis-3.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1c22fa74ddd063396b19fe8445a1ae8b4190eff755d5750dda48e860a45b2ee7"}, + {file = "hiredis-3.1.0-cp39-cp39-win32.whl", hash = "sha256:0614e16339f1784df3bbd2800322e20b4127d3f3a3509f00a5562efddb2521aa"}, + {file = "hiredis-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:c2bc713ee73ab9de4a0d68b0ab0f29612342b63173714742437b977584adb2d8"}, + {file = "hiredis-3.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:07ab990d0835f36bf358dbb84db4541ac0a8f533128ec09af8f80a576eef2e88"}, + {file = "hiredis-3.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c54a88eb9d8ebc4e5eefaadbe2102a4f7499f9e413654172f40aefd25350959"}, + {file = "hiredis-3.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8095ef159896e5999a795b0f80e4d64281301a109e442a8d29cd750ca6bd8303"}, + {file = "hiredis-3.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f8ca13e2476ffd6d5be4763f5868133506ddcfa5ce54b4dac231ebdc19be6c6"}, + {file = "hiredis-3.1.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d25aa25c10f966d5415795ed271da84605044dbf436c054966cea5442451b3"}, + {file = "hiredis-3.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4180dc5f646b426e5fa1212e1348c167ee2a864b3a70d56579163d64a847dd1e"}, + {file = "hiredis-3.1.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d92144e0cd6e6e841a6ad343e9d58631626eeb4ac96b0322649379b5d4527447"}, + {file = "hiredis-3.1.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb91ba42903de637b94a1b64477f381f94ad82c0742c264f9245be76a7a3cbc"}, + {file = "hiredis-3.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ce71a797b5bc02c51da082428c00251ed6a7a67a03acbda5fbf9e8d028725f6"}, + {file = "hiredis-3.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e04c7feb9467e3170cd4d5bee381775783d81bbc45d6147c1c0ce3b50dc04f9"}, + {file = "hiredis-3.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a31806306a60f3565c04c964d6bee0e9d4a5120e1da589e41976b53972edf635"}, + {file = "hiredis-3.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:bc51f594c2c0863ded6501642dc96701ca8bbea9ced4fa3af0a1aeda8aa634cb"}, + {file = "hiredis-3.1.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4663a319ab7d22c597b9421e5ea384fd583e044f2f1ca9a1b98d4fef8a0fea2f"}, + {file = "hiredis-3.1.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:8060fa256862b0c3de64a73ab45bc1ccf381caca464f2647af9075b200828948"}, + {file = "hiredis-3.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e9445b7f117a9c8c8ccad97cb44daa55ddccff3cbc9079984eac56d982ba01f"}, + {file = "hiredis-3.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:732cf1c5cf1324f7bf3b6086976fe62a2ca98f0bf6316f31063c2c67be8797bc"}, + {file = "hiredis-3.1.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2102a94063d878c40df92f55199637a74f535e3a0b79ceba4a00538853a21be3"}, + {file = "hiredis-3.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d968dde69e3fe903bf9ef00667669dcf04a3e096e33aaf138775106ead138bc8"}, + {file = "hiredis-3.1.0.tar.gz", hash = "sha256:51d40ac3611091020d7dea6b05ed62cb152bff595fa4f931e7b6479d777acf7c"}, ] [[package]] @@ -1799,13 +1840,13 @@ lxml = ["lxml"] [[package]] name = "identify" -version = "2.6.1" +version = "2.6.3" description = "File identification library for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, - {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, + {file = "identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd"}, + {file = "identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02"}, ] [package.extras] @@ -1880,15 +1921,18 @@ six = ">=1.13.0" [[package]] name = "json5" -version = "0.9.25" +version = "0.10.0" description = "A Python implementation of the JSON5 data format." optional = false -python-versions = ">=3.8" +python-versions = ">=3.8.0" files = [ - {file = "json5-0.9.25-py3-none-any.whl", hash = "sha256:34ed7d834b1341a86987ed52f3f76cd8ee184394906b6e22a1e0deb9ab294e8f"}, - {file = "json5-0.9.25.tar.gz", hash = "sha256:548e41b9be043f9426776f05df8635a00fe06104ea51ed24b67f908856e151ae"}, + {file = "json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa"}, + {file = "json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559"}, ] +[package.extras] +dev = ["build (==1.2.2.post1)", "coverage (==7.5.3)", "mypy (==1.13.0)", "pip (==24.3.1)", "pylint (==3.2.3)", "ruff (==0.7.3)", "twine (==5.1.1)", "uv (==0.5.1)"] + [[package]] name = "lxml" version = "5.3.0" @@ -2107,71 +2151,72 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, + {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"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] [[package]] @@ -2244,13 +2289,13 @@ pyyaml = ">=5.1" [[package]] name = "mkdocs-git-committers-plugin-2" -version = "2.3.0" +version = "2.4.1" description = "An MkDocs plugin to create a list of contributors on the page. The git-committers plugin will seed the template context with a list of GitHub or GitLab committers and other useful GIT info such as last modified date" optional = false -python-versions = ">=3.8,<4" +python-versions = "<4,>=3.8" files = [ - {file = "mkdocs-git-committers-plugin-2-2.3.0.tar.gz", hash = "sha256:d6baca1ae04db8120640038eda8142f2d081c27b53f3b566c83c75717e4ed81a"}, - {file = "mkdocs_git_committers_plugin_2-2.3.0-py3-none-any.whl", hash = "sha256:7b3434af3be525c12858eb3b44b4c6b695b7c7b7760482ea8de1c6e292e84f0f"}, + {file = "mkdocs_git_committers_plugin_2-2.4.1-py3-none-any.whl", hash = "sha256:ec9c1d81445606c471337d1c4a1782c643b7377077b545279dc18b86b7362c6d"}, + {file = "mkdocs_git_committers_plugin_2-2.4.1.tar.gz", hash = "sha256:ea1f80a79cedc42289e0b8e973276df04fb94f56e0ae3efc5385fb28547cf5cb"}, ] [package.dependencies] @@ -2260,13 +2305,13 @@ requests = "*" [[package]] name = "mkdocs-material" -version = "9.5.39" +version = "9.5.47" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.39-py3-none-any.whl", hash = "sha256:0f2f68c8db89523cb4a59705cd01b4acd62b2f71218ccb67e1e004e560410d2b"}, - {file = "mkdocs_material-9.5.39.tar.gz", hash = "sha256:25faa06142afa38549d2b781d475a86fb61de93189f532b88e69bf11e5e5c3be"}, + {file = "mkdocs_material-9.5.47-py3-none-any.whl", hash = "sha256:53fb9c9624e7865da6ec807d116cd7be24b3cb36ab31b1d1d1a9af58c56009a2"}, + {file = "mkdocs_material-9.5.47.tar.gz", hash = "sha256:fc3b7a8e00ad896660bd3a5cc12ca0cb28bdc2bcbe2a946b5714c23ac91b0ede"}, ] [package.dependencies] @@ -2365,13 +2410,13 @@ reports = ["lxml"] [[package]] name = "mypy-boto3-dynamodb" -version = "1.35.24" -description = "Type annotations for boto3.DynamoDB 1.35.24 service generated with mypy-boto3-builder 8.1.1" +version = "1.35.74" +description = "Type annotations for boto3 DynamoDB 1.35.74 service generated with mypy-boto3-builder 8.5.0" optional = false python-versions = ">=3.8" files = [ - {file = "mypy_boto3_dynamodb-1.35.24-py3-none-any.whl", hash = "sha256:022859543c5314f14fb03ef4e445e34b97b9bc0cecb003c14c10943a2eaa3ff7"}, - {file = "mypy_boto3_dynamodb-1.35.24.tar.gz", hash = "sha256:55bf897a1d0e354579edb05001f4bc4f472b9452badd9db24876c31bdf3f72a1"}, + {file = "mypy_boto3_dynamodb-1.35.74-py3-none-any.whl", hash = "sha256:b693b459abb1910cbb28f3a478ced8c6e6515f1bf136b45aca1a76b6146b5adb"}, + {file = "mypy_boto3_dynamodb-1.35.74.tar.gz", hash = "sha256:a815d044b8f5f4ba308ea3114916565fbd932fcaf218f8d0288b2840415f9c46"}, ] [package.dependencies] @@ -2379,13 +2424,13 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} [[package]] name = "mypy-boto3-events" -version = "1.35.0" -description = "Type annotations for boto3.EventBridge 1.35.0 service generated with mypy-boto3-builder 7.26.0" +version = "1.35.72" +description = "Type annotations for boto3 EventBridge 1.35.72 service generated with mypy-boto3-builder 8.5.0" optional = false python-versions = ">=3.8" files = [ - {file = "mypy_boto3_events-1.35.0-py3-none-any.whl", hash = "sha256:7490d9720cef3a0b475d8ef7171886cc688ef3efd37cd8a467a3dac5f01aac02"}, - {file = "mypy_boto3_events-1.35.0.tar.gz", hash = "sha256:2172465ddfc9f84c0dd45707a1f3eaf406ad632b03e8546b9f2dfd333a85eb26"}, + {file = "mypy_boto3_events-1.35.72-py3-none-any.whl", hash = "sha256:4aacd448d2dae96acdbff0f91ed9548f4e94fe3be3b0536e3a417ad6521a5876"}, + {file = "mypy_boto3_events-1.35.72.tar.gz", hash = "sha256:866f48720b5cac92c9e30ab90454ce407cc2916a7423c22c6e2ed7ee042b9ce6"}, ] [package.dependencies] @@ -2393,13 +2438,13 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} [[package]] name = "mypy-boto3-iam" -version = "1.35.0" -description = "Type annotations for boto3.IAM 1.35.0 service generated with mypy-boto3-builder 7.26.0" +version = "1.35.61" +description = "Type annotations for boto3.IAM 1.35.61 service generated with mypy-boto3-builder 8.2.1" optional = false python-versions = ">=3.8" files = [ - {file = "mypy_boto3_iam-1.35.0-py3-none-any.whl", hash = "sha256:aaa7608799500e2a2ee241d8c3c123f6d1c2ef2d29025c5dff3ac2720a555ccc"}, - {file = "mypy_boto3_iam-1.35.0.tar.gz", hash = "sha256:b379a01c3ca17a367cb7a460905f9ce1ab7830a9abb8c8a56f28a5ff1087657f"}, + {file = "mypy_boto3_iam-1.35.61-py3-none-any.whl", hash = "sha256:2b26756fbf1ea3ad57546731b3f2c23780aa5448d1cb05dd80ba30bfc29ad581"}, + {file = "mypy_boto3_iam-1.35.61.tar.gz", hash = "sha256:cf307f7fb2404ceda7fda455f6d4cf3bdf57e12a3b16d27101db6223c59e6fe7"}, ] [package.dependencies] @@ -2421,13 +2466,13 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} [[package]] name = "mypy-boto3-sesv2" -version = "1.35.29" -description = "Type annotations for boto3.SESV2 1.35.29 service generated with mypy-boto3-builder 8.1.2" +version = "1.35.53" +description = "Type annotations for boto3.SESV2 1.35.53 service generated with mypy-boto3-builder 8.1.4" optional = false python-versions = ">=3.8" files = [ - {file = "mypy_boto3_sesv2-1.35.29-py3-none-any.whl", hash = "sha256:bd95898cac4e4e0e44413fba04cebb24ff6c4b5f45b00669fe054753970d2bf3"}, - {file = "mypy_boto3_sesv2-1.35.29.tar.gz", hash = "sha256:ac63fe2daa1e3ba94e32beb90b6ef8431e846dfc2a4dabbd43f3609b1229a57d"}, + {file = "mypy_boto3_sesv2-1.35.53-py3-none-any.whl", hash = "sha256:7fdb2cf32dad7e5283d83c75e5a330c1eca3e76a2bab5ded0bea3e5c0d334178"}, + {file = "mypy_boto3_sesv2-1.35.53.tar.gz", hash = "sha256:8c8c4515c5d8260c311fbf209cc1f121a416785f3c0d3eed1f5a6e9141290dce"}, ] [package.dependencies] @@ -2435,13 +2480,13 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} [[package]] name = "mypy-boto3-stepfunctions" -version = "1.35.9" -description = "Type annotations for boto3.SFN 1.35.9 service generated with mypy-boto3-builder 7.26.1" +version = "1.35.68" +description = "Type annotations for boto3 SFN 1.35.68 service generated with mypy-boto3-builder 8.3.1" optional = false python-versions = ">=3.8" files = [ - {file = "mypy_boto3_stepfunctions-1.35.9-py3-none-any.whl", hash = "sha256:c0da4f9cd7bd2cf981fcee0bd8c5509d5841f27303771450d656ebf8eac4c977"}, - {file = "mypy_boto3_stepfunctions-1.35.9.tar.gz", hash = "sha256:c088ab67751e837c5db40593d6cbb70505c8e56e0e04fb32a0241e79bb22812b"}, + {file = "mypy_boto3_stepfunctions-1.35.68-py3-none-any.whl", hash = "sha256:ceea974dd9f779f19042dc6ce6ac64955036be9b825512a6a33eecb7d682f43c"}, + {file = "mypy_boto3_stepfunctions-1.35.68.tar.gz", hash = "sha256:4abef0d339463ebe612836f42154a092e95eed025baca9a15be1286cc9a90434"}, ] [package.dependencies] @@ -2517,13 +2562,13 @@ asn1crypto = ">=1.5.1" [[package]] name = "packaging" -version = "24.1" +version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] @@ -2751,18 +2796,18 @@ files = [ [[package]] name = "pulumi" -version = "3.134.1" +version = "3.142.0" description = "Pulumi's Python SDK" optional = false python-versions = ">=3.8" files = [ - {file = "pulumi-3.134.1-py3-none-any.whl", hash = "sha256:74ac0d67454256cea6b2cd338ba64cf3067265299dd5212f740638bb7ecdd2b0"}, + {file = "pulumi-3.142.0-py3-none-any.whl", hash = "sha256:659a58a26aa36f585046750f01c3f20113c71fa065ee5a4487c775a9ef98b9b2"}, ] [package.dependencies] -debugpy = ">=1.8.5,<1.9.0" +debugpy = ">=1.8.7,<1.9.0" dill = ">=0.3,<1.0" -grpcio = ">=1.60.1,<1.61.0" +grpcio = ">=1.66.2,<1.67.0" protobuf = ">=4.21,<5.0" pyyaml = ">=6.0,<7.0" semver = ">=2.13,<3.0" @@ -2770,18 +2815,18 @@ six = ">=1.12,<2.0" [[package]] name = "pulumi-aws" -version = "6.54.0" +version = "6.63.0" description = "A Pulumi package for creating and managing Amazon Web Services (AWS) cloud resources." optional = false python-versions = ">=3.8" files = [ - {file = "pulumi_aws-6.54.0-py3-none-any.whl", hash = "sha256:56146b25d4d7d8c6b10bc05dcdadf7cfc6a55bd02444232b83692b98d5055ac5"}, - {file = "pulumi_aws-6.54.0.tar.gz", hash = "sha256:ee4dcdb56e84db19795d80c88d664351064f9b8f4351da6cb92674f8bef27c9a"}, + {file = "pulumi_aws-6.63.0-py3-none-any.whl", hash = "sha256:5d4f3b6034cc3fc2ca2b0137d4cb668a52116b37215ab79d5872a66ffae0e664"}, + {file = "pulumi_aws-6.63.0.tar.gz", hash = "sha256:51b36a36c2bf9876f088e4ed7283b01f56e2d5a38d5a809e61992a2617030ff2"}, ] [package.dependencies] parver = ">=0.2.1" -pulumi = ">=3.0.0,<4.0.0" +pulumi = ">=3.136.0,<4.0.0" semver = ">=2.8.1" typing-extensions = {version = ">=4.11", markers = "python_version < \"3.11\""} @@ -2809,22 +2854,19 @@ files = [ [[package]] name = "pydantic" -version = "2.9.2" +version = "2.10.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, - {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, + {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, + {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.23.4" -typing-extensions = [ - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, -] +pydantic-core = "2.27.1" +typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -2832,100 +2874,111 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.27.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, - {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, - {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, - {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, - {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, - {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, - {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, - {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, - {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, - {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, - {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, - {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, - {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, - {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, + {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, + {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, + {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, + {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, + {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, + {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, + {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, + {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, + {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, + {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, + {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, + {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"}, + {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"}, + {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"}, + {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, + {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, + {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, + {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, ] [package.dependencies] @@ -2933,13 +2986,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-settings" -version = "2.5.2" +version = "2.6.1" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_settings-2.5.2-py3-none-any.whl", hash = "sha256:2c912e55fd5794a59bf8c832b9de832dcfdf4778d79ff79b708744eed499a907"}, - {file = "pydantic_settings-2.5.2.tar.gz", hash = "sha256:f90b139682bee4d2065273d5185d71d37ea46cfe57e1b5ae184fc6a0b2484ca0"}, + {file = "pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87"}, + {file = "pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0"}, ] [package.dependencies] @@ -2967,48 +3020,48 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyhanko" -version = "0.25.1" +version = "0.25.3" description = "Tools for stamping and signing PDF files" optional = false python-versions = ">=3.8" files = [ - {file = "pyHanko-0.25.1-py3-none-any.whl", hash = "sha256:045a0999c5e3b22caad86e4fa11ef488c3fd7f5b5886c045ca11ffa24254c33c"}, - {file = "pyhanko-0.25.1.tar.gz", hash = "sha256:8718d9046d442589eef6dd6973110fa5e385555cc4a6b2b1aeca3c2f3b6742e9"}, + {file = "pyHanko-0.25.3-py3-none-any.whl", hash = "sha256:d66ec499f057191df100f322c2fd22949057a9b0d981f4e75bc077c1a817497f"}, + {file = "pyhanko-0.25.3.tar.gz", hash = "sha256:e879fd44e20f4b7726e75c62e8c7b0c41ea41f8fa5bda626bc7d206ae3d30dec"}, ] [package.dependencies] asn1crypto = ">=1.5.1" click = ">=8.1.3" -cryptography = ">=42.0.1" -pyhanko-certvalidator = ">=0.26.2,<0.27" +cryptography = ">=43.0.3" +pyhanko-certvalidator = ">=0.26.5,<0.27" pyyaml = ">=6.0" qrcode = ">=7.3.1" requests = ">=2.31.0" tzlocal = ">=4.3" [package.extras] -async-http = ["aiohttp (>=3.9.0,<3.10.0)"] +async-http = ["aiohttp (>=3.9,<3.12)"] docs = ["sphinx", "sphinx-rtd-theme"] etsi = ["xsdata (>=24.4,<25.0)"] extra-pubkey-algs = ["oscrypto (>=1.2.1)"] image-support = ["Pillow (>=7.2.0)", "python-barcode (==0.15.1)"] -live-test = ["certomancer-csc-dummy (==0.3.0)", "certomancer[web-api] (==0.12.*)", "pyHanko[async-http,extra-pubkey-algs,testing-basic,xmp]", "pytest-aiohttp (>=1.0.4,<1.1.0)", "pytest-cov (>=4.0,<5.1)"] +live-test = ["certomancer-csc-dummy (==0.3.0)", "certomancer[web-api] (>=0.12.3,<0.13)", "pyHanko[async-http,extra-pubkey-algs,testing-basic,xmp]", "pytest-aiohttp (>=1.0.4,<1.1.0)", "pytest-cov (>=4.0,<6.1)"] mypy = ["pyHanko[async-http,etsi,extra-pubkey-algs,image-support,opentype,pkcs11,xmp]", "types-PyYAML", "types-python-dateutil", "types-requests", "types-tzlocal"] -opentype = ["fonttools (>=4.33.3)", "uharfbuzz (>=0.25.0,<0.40.0)"] +opentype = ["fonttools (>=4.33.3)", "uharfbuzz (>=0.25.0,<0.43.0)"] pkcs11 = ["python-pkcs11 (>=0.7.0,<0.8.0)"] testing = ["certomancer-csc-dummy (==0.3.0)", "pyHanko[async-http,extra-pubkey-algs,image-support,opentype,pkcs11,testing-basic,xmp]", "pyHanko[etsi]", "pytest-aiohttp (>=1.0.4,<1.1.0)"] -testing-basic = ["backports.zoneinfo[tzdata]", "certomancer (==0.12.*)", "freezegun (>=1.1.0)", "pytest (>=6.1.1)", "pytest-asyncio (==0.23.8)", "pytest-cov (>=4.0,<5.1)", "requests-mock (>=1.8.0)"] +testing-basic = ["backports.zoneinfo[tzdata]", "certomancer (>=0.12.3,<0.13)", "freezegun (>=1.1.0)", "pytest (>=6.1.1)", "pytest-asyncio (==0.24.0)", "pytest-cov (>=4.0,<6.1)", "requests-mock (>=1.8.0)"] xmp = ["defusedxml (>=0.7.1,<0.8.0)"] [[package]] name = "pyhanko-certvalidator" -version = "0.26.3" +version = "0.26.5" description = "Validates X.509 certificates and paths; forked from wbond/certvalidator" optional = false python-versions = ">=3.7" files = [ - {file = "pyhanko-certvalidator-0.26.3.tar.gz", hash = "sha256:47fba8e9dbf846d766f2e0a453572dd4b25b2f1397847a31fe892c8eb00391f5"}, - {file = "pyhanko_certvalidator-0.26.3-py3-none-any.whl", hash = "sha256:e386c87e202ff1caacf5fd941da6c3509e79db54dbd7b43c6550ceebe5e67077"}, + {file = "pyhanko_certvalidator-0.26.5-py3-none-any.whl", hash = "sha256:86a56df420bfb273ba881826b76245a53b2bd039fea7a7826231dbe76d761a8a"}, + {file = "pyhanko_certvalidator-0.26.5.tar.gz", hash = "sha256:800f5a7744d23870a5203cb38007689902c79c44e7374dab0c9b02e1b1a89bd4"}, ] [package.dependencies] @@ -3019,19 +3072,19 @@ requests = ">=2.31.0" uritools = ">=3.0.1" [package.extras] -async-http = ["aiohttp (>=3.8,<3.10)"] +async-http = ["aiohttp (>=3.8,<3.11)"] mypy = ["pyhanko-certvalidator[testing]", "types-requests"] -testing = ["aiohttp (>=3.8,<3.10)", "freezegun (>=1.1.0)", "pyhanko-certvalidator[async-http]", "pytest (>=6.1.1)", "pytest-aiohttp (>=1.0.4,<1.1.0)", "pytest-cov (>=4.0,<4.2)"] +testing = ["aiohttp (>=3.8,<3.11)", "freezegun (>=1.1.0)", "pyhanko-certvalidator[async-http]", "pytest (>=6.1.1)", "pytest-aiohttp (>=1.0.4,<1.1.0)", "pytest-cov (>=4.0,<6.1)"] [[package]] name = "pyjwt" -version = "2.9.0" +version = "2.10.1" description = "JSON Web Token implementation in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, - {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, ] [package.extras] @@ -3042,13 +3095,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pymdown-extensions" -version = "10.11.1" +version = "10.12" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.11.1-py3-none-any.whl", hash = "sha256:a2b28f5786e041f19cb5bb30a1c2c853668a7099da8e3dd822a5ad05f2e855e3"}, - {file = "pymdown_extensions-10.11.1.tar.gz", hash = "sha256:a8836e955851542fa2625d04d59fdf97125ca001377478ed5618e04f9183a59a"}, + {file = "pymdown_extensions-10.12-py3-none-any.whl", hash = "sha256:49f81412242d3527b8b4967b990df395c89563043bc51a3d2d7d500e52123b77"}, + {file = "pymdown_extensions-10.12.tar.gz", hash = "sha256:b0ee1e0b2bef1071a47891ab17003bfe5bf824a398e13f49f8ed653b699369a7"}, ] [package.dependencies] @@ -3060,160 +3113,154 @@ extra = ["pygments (>=2.12)"] [[package]] name = "pypdf" -version = "5.0.1" +version = "5.1.0" description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" optional = false python-versions = ">=3.8" files = [ - {file = "pypdf-5.0.1-py3-none-any.whl", hash = "sha256:ff8a32da6c7a63fea9c32fa4dd837cdd0db7966adf6c14f043e3f12592e992db"}, - {file = "pypdf-5.0.1.tar.gz", hash = "sha256:a361c3c372b4a659f9c8dd438d5ce29a753c79c620dc6e1fd66977651f5547ea"}, + {file = "pypdf-5.1.0-py3-none-any.whl", hash = "sha256:3bd4f503f4ebc58bae40d81e81a9176c400cbbac2ba2d877367595fb524dfdfc"}, + {file = "pypdf-5.1.0.tar.gz", hash = "sha256:425a129abb1614183fd1aca6982f650b47f8026867c0ce7c4b9f281c443d2740"}, ] [package.dependencies] typing_extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] -crypto = ["PyCryptodome", "cryptography"] +crypto = ["cryptography"] +cryptodome = ["PyCryptodome"] dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "pytest-socket", "pytest-timeout", "pytest-xdist", "wheel"] docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"] -full = ["Pillow (>=8.0.0)", "PyCryptodome", "cryptography"] +full = ["Pillow (>=8.0.0)", "cryptography"] image = ["Pillow (>=8.0.0)"] -[[package]] -name = "pypng" -version = "0.20220715.0" -description = "Pure Python library for saving and loading PNG images" -optional = false -python-versions = "*" -files = [ - {file = "pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c"}, - {file = "pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"}, -] - [[package]] name = "python-bidi" -version = "0.6.0" +version = "0.6.3" description = "Python Bidi layout wrapping the Rust crate unicode-bidi" optional = false python-versions = "*" files = [ - {file = "python_bidi-0.6.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:032b16f70c5d4f48c8dc5a4ade071826a0fb64172e0435d49deba6ea66fc5d42"}, - {file = "python_bidi-0.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:53b50f6ad3e633dcc74fc96bb959bf375a84db36db380d76f9c189ce33099ede"}, - {file = "python_bidi-0.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d2928ae4aedff4f49ac2e334d176b9488762276bae8b32045c3b91f41c447e4"}, - {file = "python_bidi-0.6.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f3e5ef9093699300868f9c92975e4d3472131e9da1125501b1950faa0eec62a"}, - {file = "python_bidi-0.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0eb05fc7115f296e09e06d47648b032a2dff4322b363b8b7f88d4695be452951"}, - {file = "python_bidi-0.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fdf72111aed1e30bb89989f55e167411d5fb7a94ee412a3116b9a9b257516f4"}, - {file = "python_bidi-0.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9daa84f8f8138521a5971d38c92d918bdb0a899268d83d9daa5eba7dce641ce"}, - {file = "python_bidi-0.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2e5bafacba56696712ea2284c27f8a3d3b4ee94684b7dcd06af8775cf650dea"}, - {file = "python_bidi-0.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7214a175dd09a4da5f755dbf19d767261d2087686dfff321b4a3967d09096081"}, - {file = "python_bidi-0.6.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9f6fe4d9b86c123a960c7506ffb31ebba0c7c465a364b344f96858679bf54401"}, - {file = "python_bidi-0.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ddf12de3ce92bbfcc3f0cdcc4591f9dbbdf3f83388da22646dbf0ba56d66844d"}, - {file = "python_bidi-0.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:550565296af0e3e938cedc87c78a6ba02e38dab7c4bb2fbbf3717f2412e2a6e1"}, - {file = "python_bidi-0.6.0-cp310-none-win32.whl", hash = "sha256:b6958bc4a27e2854c1e9a3d6a2dac0cfd09451834c64f96738c3365d8a053358"}, - {file = "python_bidi-0.6.0-cp310-none-win_amd64.whl", hash = "sha256:712d666331e813f498ad6f16e23b6c9795f21e7a231b7047f32f2843e303ec92"}, - {file = "python_bidi-0.6.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b3984f4d91b75f19c1e1c2e5a52f4263f4c4a11de2c1f5bfb7b8fceb7960d8d8"}, - {file = "python_bidi-0.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:603b4485b7dc588bc58f80f1271f103b859a45b19024b90686c639a451e50b0a"}, - {file = "python_bidi-0.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d4f2a41ba306fd2827a1e5f153e856f5e79176abf4f0ae41def5255113548cc"}, - {file = "python_bidi-0.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb50d50809508f0f9907973e8c99fd663d7d3b2bb124218c7f9d9abe374527c5"}, - {file = "python_bidi-0.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd5d2c89acc022cfd3b2d87b09f3ac8503beb6ca45af2ee31df9bd0fbbbe85ce"}, - {file = "python_bidi-0.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08c7ebd084312706868df172fb46f635ee437344181c0c55302f0da221f3bf75"}, - {file = "python_bidi-0.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f8689da1893a5a70e5bb6b47fff4571b8ab6a3653b8f9e3d3555ddaaabb607f"}, - {file = "python_bidi-0.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7cac60293dddbe6307bfb15f8a227f614afa882999ff669b5af795dca7db97dc"}, - {file = "python_bidi-0.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4019165cb8e9e73bebec839156ff25e02b499da5f3e849e44c5c76fd487be967"}, - {file = "python_bidi-0.6.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6684e0f1cb2d769281b6b8bbb51c69a700b63b18a6cb6088d3f34a9eb544620a"}, - {file = "python_bidi-0.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7203782c888fcffc0e20e1e6b2d8bfe947ce356796a6709c09c1d751943b6ed7"}, - {file = "python_bidi-0.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7d98736704548a001fb072becdfbda9d67dde8fd993320494c2370243c92f87"}, - {file = "python_bidi-0.6.0-cp311-none-win32.whl", hash = "sha256:205d885944f929e93283b88a45fe57ae0102c39ba0576ab856f9e5dd50d7a049"}, - {file = "python_bidi-0.6.0-cp311-none-win_amd64.whl", hash = "sha256:b7847f882442179fd67608958c1ce8af9ee4b051a921342c7a3bc071e2ba0fc4"}, - {file = "python_bidi-0.6.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:694373c087f2d5067289832070a21e84fc648ac087163723ccd0759dac3a7161"}, - {file = "python_bidi-0.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd2ae8876412974b8959520688a271c1b3dbb65ef57306e3bf745115147d05b8"}, - {file = "python_bidi-0.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d748eccc2c40ce5b56bde1b7eef72f7b6037e289fb34a38335cd05e3b5f7cd6"}, - {file = "python_bidi-0.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:22b6866cf18e2e8189cdbc5ede22b843c15c8aaef5eb8438fb02f8197fb29bf9"}, - {file = "python_bidi-0.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e056f602e634b8cd3c8c5497f52d43674f5de088df4f1a8d73e99cd97735fb3f"}, - {file = "python_bidi-0.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3adf383d7e1bc50c8357f78ec3591c483066f9b7744a0c2c89d1ef501c75f693"}, - {file = "python_bidi-0.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db9981b2d05ef108a540424dabfa157dff20ec4adb909e5a6d2938cac6cf3987"}, - {file = "python_bidi-0.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7dcf1d8834e1db2f4d3372c607fe2a12acbeeb4a9aba1bf0014cc37474ef08d1"}, - {file = "python_bidi-0.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:58b9628fa1d15b30686fb6196cc2b3d6c1546bfe7e5fbdd9b758d69a76411cd4"}, - {file = "python_bidi-0.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7fa5babd7d89a541d6507bddd4839271db1504a54f46a5ee64c959dde41c0596"}, - {file = "python_bidi-0.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0b5c33ad97ad7bb2fa335a0ce63d0a887e99dbc86ce2684f7622c0fb1b25873e"}, - {file = "python_bidi-0.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:48034cc54c792dfeeb017505293600bc4ece89fca1fc28d6fb24f932d7ef1bcf"}, - {file = "python_bidi-0.6.0-cp312-none-win32.whl", hash = "sha256:8483de08f3b41063f1819a397aa6686ae88ac908192e448b72e4bf7caa91a655"}, - {file = "python_bidi-0.6.0-cp312-none-win_amd64.whl", hash = "sha256:a82ee4b48e9b192d4ff3873f2fd063efae063b904b6283119b8cef7165a54084"}, - {file = "python_bidi-0.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e080e4bf367c3761fc9a430a6a0375dcb10a541721a6b688142a9bbee883e576"}, - {file = "python_bidi-0.6.0-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1002a9b65deae763b236d7d4ea6f046acdb778c85932053ce0d4607f691a1a93"}, - {file = "python_bidi-0.6.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9418a69c7189907cd27ebe783708572dff979be77e1a7d2b646ff0a456f4f59b"}, - {file = "python_bidi-0.6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61c05401d8ff120221b53938e7576a330fde22b22e22bf9243e8fffa225cd35f"}, - {file = "python_bidi-0.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5e8382835ae85a4bacd188e563fcd5c90d73be7406f7e4ee1148f7a3fa61ff1"}, - {file = "python_bidi-0.6.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19d07a95ebab603849a639d649a07474dc54485ad822aa045309a12ec0f7d388"}, - {file = "python_bidi-0.6.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:e8695ad198e816b42df61ea2a315f7d84189f69a3aa608c0fa71bb5d9105790e"}, - {file = "python_bidi-0.6.0-cp37-cp37m-musllinux_1_2_armv7l.whl", hash = "sha256:13956919a2bcf0d5f240d66acd99256996abec10ae235d328d93433480dac62a"}, - {file = "python_bidi-0.6.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:88ee27d0ce129b51e0aedcf88a7961e300f0e3b5d0e707ca813e2af33b46e8a1"}, - {file = "python_bidi-0.6.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:50ec1353643a03c8c324968e1216624e0bba57b77af465675932ce6cc5505015"}, - {file = "python_bidi-0.6.0-cp37-none-win32.whl", hash = "sha256:9ec06a91c64fb6832351dc8ab1dc9f970b505e09ccb83b8ff2c91bd04ce31417"}, - {file = "python_bidi-0.6.0-cp37-none-win_amd64.whl", hash = "sha256:4425879da7b1ca6257759ace9277506d9d6cf0fc13820bfa1e779931a6bb9795"}, - {file = "python_bidi-0.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5985ed1f85b8a2853c73976d259e3095122559646956c5bf8f1c6c4eb2bd7ebd"}, - {file = "python_bidi-0.6.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:afb77402c6d79daa1715a8a5d6d0eff13387e0db1a34c0f91a09b40ca6a60972"}, - {file = "python_bidi-0.6.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c6d339b94efde502286a8f80f130db6014762c8218e6f3af23bfe446217b10e"}, - {file = "python_bidi-0.6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afec9e08733d6754ef309eef617ba324eacf4bdf0081c3ec34758cbfb964e889"}, - {file = "python_bidi-0.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2901c29525e8c6071781bd796658f7934143fe6bcfb4f998a11cc80372f756fc"}, - {file = "python_bidi-0.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c71bb7e5fbd67fd64dc7b07c0a69a1b1daffdae0839d543e6e48dbfa82509208"}, - {file = "python_bidi-0.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:782b166b22cdd1738dc557acf7311a1d85565b9f58c48eb4004e4f770854c9d2"}, - {file = "python_bidi-0.6.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1000815b42e9eba8d4e28e8d6f9558f055d54b9ec746875117d8b8150c86511c"}, - {file = "python_bidi-0.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:dfae0ea2444833acab3f7c62fd38b965f7332617993ef09098672ca9279bb27d"}, - {file = "python_bidi-0.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:65fcf6afe02d64b3ec9a6b97513fad50b858f88b83f785e4c0416a9acac5bc63"}, - {file = "python_bidi-0.6.0-cp38-none-win32.whl", hash = "sha256:0fc9ad821600a0bd4c9bd6327f5cac3c1494f0d291173bf41e655f2ec80f1cee"}, - {file = "python_bidi-0.6.0-cp38-none-win_amd64.whl", hash = "sha256:09cd618b42b6e042140c3c15792942c4a2fd259ed68cd68f224dfe00ff312f1d"}, - {file = "python_bidi-0.6.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:332506db671bc2ba8ae2591ba48c617a25dd2924a0ae185bc970f9f4e386a55f"}, - {file = "python_bidi-0.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c066bffffcb66e13fff3e4cc4a9570d744f4b48e54caa0308faf98a0a8dc4570"}, - {file = "python_bidi-0.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36fe41cc095045b23bfddc5c48a6aef1674dc32b1d1a52ece3b302a5fb28f33a"}, - {file = "python_bidi-0.6.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:018747edf332240e6400335a10d34c66d7d27e096d05ea0761afb61dc4e750b0"}, - {file = "python_bidi-0.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6aae20bb48ff0ccc4bd3ede085bfb781918c938f2cc09867c879d23252d18775"}, - {file = "python_bidi-0.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:600236fbce5e43348c71de7327dd88f1484358cdad04ee742752a289569f1d82"}, - {file = "python_bidi-0.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9dc0ac10d7728b17ce9ac22d09918bd6f8b64e9b92a5199cb9a9cf29016dd41"}, - {file = "python_bidi-0.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6cbccc3484f6c30a682960fdde2ede944d9bc6b9ed8b8ead683e97af066ebe07"}, - {file = "python_bidi-0.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:de1b836e0d8134b8331423a5c76c886b5c25eed8d6f9e5dcac7767feba3d052c"}, - {file = "python_bidi-0.6.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cfb45fecb08bb070dc0e62e6ee8249bbb6b6622181756fae2cff60c8eb5850e9"}, - {file = "python_bidi-0.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fb6b3545296ff4c1a6cef465359f6ed9b32cfc4bd3d8a6633f0234476414e387"}, - {file = "python_bidi-0.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e511838eeff5b76f053afe0b936920e5aca91ea597c43caa196e0c6b5cd0d623"}, - {file = "python_bidi-0.6.0-cp39-none-win32.whl", hash = "sha256:de171a2e7671dc5d19f957054e9f8fba997a98caebfcd3d386c4189d23e0d73f"}, - {file = "python_bidi-0.6.0-cp39-none-win_amd64.whl", hash = "sha256:75243e17201831d8f626be57a1ba52fe4f62594eb8bc777e2a81785a93745466"}, - {file = "python_bidi-0.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75ca41875c8829295931eb2f0f380da50c1448d64e3c28c3db4966afdfbc53f"}, - {file = "python_bidi-0.6.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4700f71dc553cb65dc8b132de1ee542ae6c518fa8e942b5e0d3ba07bca054a42"}, - {file = "python_bidi-0.6.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13b7a10fa19b949132581dfd621ca800030cf21dc06a13366371a6e71309c6f6"}, - {file = "python_bidi-0.6.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef186911e27c968be69cdbaccaa378f0fa129b224a2854ec491963632ea37ff1"}, - {file = "python_bidi-0.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1af301fe73f76c46e3ddd21b0a3c4467b01e0c3a94f69cd185a78db6810300e8"}, - {file = "python_bidi-0.6.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf6e718dbbf15f4fb24fd199ebe089c8a28ed712aedc7757d7730741e28cff27"}, - {file = "python_bidi-0.6.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:30214494b84c49247624c162d9141e7fc8dbc3957d21959feb92703cb87b474d"}, - {file = "python_bidi-0.6.0-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:5753d3204d13189a12a298c82c8c23eba94c252ee9aab3dddb7014b0cd4f37b1"}, - {file = "python_bidi-0.6.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:959fcb0554ca4136044bbb308654aa88c3ffa9031a6c6b074b29221dbb6d553f"}, - {file = "python_bidi-0.6.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6ca12af1e09355d6296730bd44adf5023a8b696ce77a9a04f35f56b10cd60428"}, - {file = "python_bidi-0.6.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f13deaa892d5dbc742b4ca4f96e9f6255d5f33b4bbfb04c4c77afc4c1b36378a"}, - {file = "python_bidi-0.6.0-pp37-pypy37_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96b47ad6492fc3f17a8f9335ae76bafec6ae4769138da34c58f493618f653e78"}, - {file = "python_bidi-0.6.0-pp37-pypy37_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56cb6986f7fe97a425c6914d465f7098223263a498a3e48c49dbffc9ebe46ee3"}, - {file = "python_bidi-0.6.0-pp37-pypy37_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b90235b0c665483821fd5ab4a0d4db59025f12769dbd4fa1e2d6b0616e1178d3"}, - {file = "python_bidi-0.6.0-pp37-pypy37_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:9ca5506abe6b3a03f139703deec86852e88c13ad32d6b66109b5630539f9f386"}, - {file = "python_bidi-0.6.0-pp37-pypy37_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:5f6f04dbd30a667a3dd61356ca9e97d33cbdd8fbbe953c5ad3ab86b6901c73e7"}, - {file = "python_bidi-0.6.0-pp37-pypy37_pp73-musllinux_1_2_i686.whl", hash = "sha256:d63cf8bd056c4ec14ff9d8ee7181543cd758c1f4ce0eea0710fa854e1fede644"}, - {file = "python_bidi-0.6.0-pp37-pypy37_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:17a9db493051792e71a813dd09a4c555e475cd874bf7594429be9c0cf16e270f"}, - {file = "python_bidi-0.6.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:618a70c0183372e23756766db135930507093e95f386c429187f9ae29c4d965f"}, - {file = "python_bidi-0.6.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b880e8e75bea3136c5a44960365665d32abdee204024fd77e9a9975809c72ae"}, - {file = "python_bidi-0.6.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f20e5592534f3b06b2beb0a38f1df8ce1fb2c8f628573381637ca53083dd4648"}, - {file = "python_bidi-0.6.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16c8a3284bf874b6c38f8cb10f0f48fd1d7c198cf0a4937d39e73e460096c652"}, - {file = "python_bidi-0.6.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e079ac4ece790fcc4f1a4fcd0b4bfaa290482f2f04bd69936a93aff6a0ce9719"}, - {file = "python_bidi-0.6.0-pp38-pypy38_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:f48486bc16d37361cd21b32a27b2109cb45372cf8e1b4cc59809f2ae4634ad22"}, - {file = "python_bidi-0.6.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:895fe7e1d67acff2d313184148a1414b50fbbf2148df272a5e9a84f8196f2d3e"}, - {file = "python_bidi-0.6.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:18c2d0bffafa590629a5e95ee079c491954ee2249350d62db4497164f7d3f4cf"}, - {file = "python_bidi-0.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3dbde6d205610653d17cc5bb785c5d5da5af6ae634e5daf92a7a6e75a50f94a"}, - {file = "python_bidi-0.6.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:132954f67e3af38ca7c7cd85bde6a49c89bd470ba01603acbd0baf8048acbab5"}, - {file = "python_bidi-0.6.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2c8c79faafbc5852db896f8d488090530cb1421765528305a6678694a1961f0"}, - {file = "python_bidi-0.6.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5c0191270c2438953329af2116fdee021c20da3a33f418303f1bf9859984eb"}, - {file = "python_bidi-0.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c652bab5b2d978f9abf324e9c1de50cb175599402b5ec14b7553780f68af597d"}, - {file = "python_bidi-0.6.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:657ac6ddf02d40de633d616d8d052e616169787d535902e3a4240738ab902a0c"}, - {file = "python_bidi-0.6.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8096c8a8d7498750cf54a55de44eb689a236ae8d3b47b642e25e55cfbcff6e4e"}, - {file = "python_bidi-0.6.0-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:0458e2f2d24c115f5f103aa54d9fe8b98c5197b85b616b0db68aaba32908c28c"}, - {file = "python_bidi-0.6.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:ce855e6be84e0b6e00286c62e2dd1ccb505beaeb78f969e270aec5998e53e4fb"}, - {file = "python_bidi-0.6.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:bb3c0dfc5131e706d46df50547ce51ff92722431b6d8d81142ea208374550b3e"}, - {file = "python_bidi-0.6.0.tar.gz", hash = "sha256:0665a0826074a9ff8d29640c0c405a2710b671db14fcc8b1c3ee6615ff10b837"}, + {file = "python_bidi-0.6.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7e2a62d7ebb4af9831c85921063154ab4067c73768ad04f466dff1359e6f2650"}, + {file = "python_bidi-0.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b8035f02c3fcb52d372bfe51db00a0c95a3fdd6f0504a32e70d4f799809070d"}, + {file = "python_bidi-0.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:854edec3ef1ef50c49f689b44900fb6c51d35f277e10b4749755d053f405a44a"}, + {file = "python_bidi-0.6.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe4c4ab61701a5e3b916c6b63811c6fd708539a3f189ec6ca6bd22948a125af0"}, + {file = "python_bidi-0.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:855a4dc2d237587a734babc6179130f9e7b7c028651cdead6ec5b162115ac112"}, + {file = "python_bidi-0.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c0635bf46ddd56cf3f71d0711fbc160fd90c36fd3176b3e91b0bf7447e549f1"}, + {file = "python_bidi-0.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a4b7b6e458173614348db8e4a4406e468338c13ecc7b74d1e208d38d0d1d264"}, + {file = "python_bidi-0.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25a39a3b61851506ed489867c69f3580ba75063195bf4b00f1983de88e02bf30"}, + {file = "python_bidi-0.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24ea5c9f5cf9f3919d81669d24a1405709f4d66c82c3ffa7f982fcece856b325"}, + {file = "python_bidi-0.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:631d32fd1414d4795348122b820dadbff1ddaa6e53a70c1ee9d5a84911cc3c2d"}, + {file = "python_bidi-0.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:205aac547f8166005e041b33069da2c8a345171b0d7c8177c3d16408acde9acd"}, + {file = "python_bidi-0.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a05249eac27e983a103babb9a2812726312bd8f685fdc3264f78b8ff8124d09a"}, + {file = "python_bidi-0.6.3-cp310-none-win32.whl", hash = "sha256:44023d51ae78ae119ef11043b5fb8f3dfc5de5ec04d937d7c5abc4da8cba1770"}, + {file = "python_bidi-0.6.3-cp310-none-win_amd64.whl", hash = "sha256:866865bbbc97a144e74508e2513373bb590d38fca3b6e52b6905de54b34ddbd9"}, + {file = "python_bidi-0.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a656b91c74b77a5b005e6dac092947f00d546cce5d0ca70b6b6741b93f7705bf"}, + {file = "python_bidi-0.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4cb80856ce1e3f24c0d878fc85ab767c201ab8891a68f41d8da87eaf39c827de"}, + {file = "python_bidi-0.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ad3f50925a5943d244c6ca05e0553922e917b3cc415580460d86af6a385ee23"}, + {file = "python_bidi-0.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:22f293338ec7d44e02991787d306d39e02f0b145810eef60802abd7833b6c2d0"}, + {file = "python_bidi-0.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b1d522cbd0af85094ccce8ae95c57a6a9d4f98e85f3e7c1ad1fb5d1c2cd09e"}, + {file = "python_bidi-0.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da00726ebf17f857d458b310e868cae4b3bac668396cd5e874e17809894417e5"}, + {file = "python_bidi-0.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1204f2aa62ac6226f11dd1bee250d428abb128046cf1999317b3f303c70ea2"}, + {file = "python_bidi-0.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7c99881440b2a4d8de7c2d7f3ac23e5f0a0ee0c5ae652f53188a21e9b0911f2d"}, + {file = "python_bidi-0.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:10f7c23dbb23dd0d2b0f67f7d4c2ba59eb42f777e1749ed9e13dbc8c4d28ea75"}, + {file = "python_bidi-0.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d7527247a9d8e0aa9d2d4ecd24cbd8216bc4e3e89e77f9c833eedf278d9761cc"}, + {file = "python_bidi-0.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5d6829865ff94925280af066c8536ff9595a6e40d300f9fc0e6ca4ebbf3bc306"}, + {file = "python_bidi-0.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e0d574c22fbab1ea996ddb1ebb3eabae521f5d129d7c699445cad81e81bc351"}, + {file = "python_bidi-0.6.3-cp311-none-win32.whl", hash = "sha256:8c5fc9f065c24bd8058d7e9a5d42415134de3cc1aa480eebc27e2ca132919dd8"}, + {file = "python_bidi-0.6.3-cp311-none-win_amd64.whl", hash = "sha256:46ee694cf5a632a8d47cc35de6926581e586425b582216962d3e6d913aea0b88"}, + {file = "python_bidi-0.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4bdc9dc1143c558ca6931d6712339a30470959f2b7eecb3d0687db7075c20a87"}, + {file = "python_bidi-0.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0775499b8037103278f05b2bf92d25bf04f40a9f77884ec3d42b01a1e52a40fe"}, + {file = "python_bidi-0.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb3091aa5efbfc4da6fd52a2fccbf7853c6dc253ddaf9a189bcf3c4345865aa9"}, + {file = "python_bidi-0.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75a9b68b3f5a8da9a33fe37607d9b267a8a3c5806d283a4a47365256773dd1e"}, + {file = "python_bidi-0.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:208e09819ee0485c2ed4dc1932c39fc073dac3f2cb70b6d2ae0b7296e86831e6"}, + {file = "python_bidi-0.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e17b67d86cd38f2bebc4a46090f83cabb0d1da3a3c920c68efe8093ae1a8d0d1"}, + {file = "python_bidi-0.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933a17938f767fa64a8365732eba787a81c26214d89e1b3abe87912325ba26a9"}, + {file = "python_bidi-0.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:772efb3e0ef17396bfd9d47da4805c74ed6c04f27cac08d7757f76602837fb9d"}, + {file = "python_bidi-0.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a99114f33f8c0273a61b4afe7d4d715e098318ee4e5ce8f6bb5da8dcd3f95c7"}, + {file = "python_bidi-0.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b30e620d39e85a30bb42f460fd8b5274caf261517edeb853b975d9ea1939b6bd"}, + {file = "python_bidi-0.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bee94e3152a6c9ba731e086c9cc6203904290506ba52c505a2e59abab481eb13"}, + {file = "python_bidi-0.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:926164ec594e9ea9a64faf54273c711d5e3233bcc6ef8966c6eeaddfb3b3075f"}, + {file = "python_bidi-0.6.3-cp312-none-win32.whl", hash = "sha256:cea395a7daee14c7d50a7e20890d12b9ff1938d81b23eb564f1707a175c37202"}, + {file = "python_bidi-0.6.3-cp312-none-win_amd64.whl", hash = "sha256:350e6c76f942465871f2b473a2076f5002f1df06e4c7abee3029ccca5f006786"}, + {file = "python_bidi-0.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:617d4391b19969de725922a256599e8218fc9c1ef0ff85884f1698fff482a977"}, + {file = "python_bidi-0.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:81f418d54948542b21c03cd8ce622a480ead85fc53175a124c4562bdf55cec49"}, + {file = "python_bidi-0.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0999b77af58396cfd789c8d068bac78d2d51363265aaf1369622099be9e0eb32"}, + {file = "python_bidi-0.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f5a0e852e8451147d96876f8233a9db6ed28c914d9767a6696cbc899e7df00c2"}, + {file = "python_bidi-0.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905e212b12c9edfaa3a916a3acd11426b89507ed0f31641257ad586467602e8d"}, + {file = "python_bidi-0.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:144adab8dc3a8560e294461114ce6dafec1a986cde6297994c1d31b3252f3298"}, + {file = "python_bidi-0.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abdbd5c265d64251798243d97228bb78441a1320fe3cf51c9a31191c56407839"}, + {file = "python_bidi-0.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f824a878a593121570ce3da847d3b9ac50521782c433996d7f81f770d3ed00"}, + {file = "python_bidi-0.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c7dcbc7eb70a0c7c66ed5219213ee2afcc815988cb9e4b134631579c4ae46980"}, + {file = "python_bidi-0.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ccbf53bc71a0a1b7f77524d1c2e51b245ae23a4f16afb80728071e21c187a768"}, + {file = "python_bidi-0.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:702527506ca97bf549710ce03d89a2577ebe35e34c42eaecfbacb0862ba06dc6"}, + {file = "python_bidi-0.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1563a8d9cfaeeeb5b4fc806f52a500b19893c63652bbd497dd6ed9def7b9ee8e"}, + {file = "python_bidi-0.6.3-cp313-none-win32.whl", hash = "sha256:f9b8e024eeaddecb4ca189e3199181985fab20c224db9a1f08db48b905c9905a"}, + {file = "python_bidi-0.6.3-cp313-none-win_amd64.whl", hash = "sha256:36b3fb05ef990613a81a23822246eaf6eef29af5182f8d8cdd174be13c92d1cc"}, + {file = "python_bidi-0.6.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3ef3c351c19348133e78aa4a05bc939f9f11d53c6733c5e8ec160a9fd78c902f"}, + {file = "python_bidi-0.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f482f205a62c958273e40c20405141f18c2d0529abb22ba6aa440602655f43a7"}, + {file = "python_bidi-0.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:982f805714a5ee83b034b8ad6a27f37db994483b72657c7898053333737a5fe3"}, + {file = "python_bidi-0.6.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ee410954d7dc7591106f9526c3ce9893a64345e69edf86d084fe8841e62bfa0"}, + {file = "python_bidi-0.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4d4a8917804a1c749e92aafb152d239cd25127cea0bb8710b99315266022009"}, + {file = "python_bidi-0.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:984cb68c5bc7980f9cc66ae2f9d06b7426445b7dfcce4d555ff04333c34d01a6"}, + {file = "python_bidi-0.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bedcbab3867227519e2dfee6c3ac26d7722ce6a048f5c72585cf83779b8e61f8"}, + {file = "python_bidi-0.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3e04c6f54e0e44e55433da0fabab6776c69bcfb1965f09e4bb5b5b4446846"}, + {file = "python_bidi-0.6.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2825a720d9dbd8ff6158a458edfbdc55bfd3de3f8181a59c7126f78ef3e27b7b"}, + {file = "python_bidi-0.6.3-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6b5f60865b4d10de024c35d9900efe371844da4a0cda1cb2a4bd35746ba69097"}, + {file = "python_bidi-0.6.3-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9f859236e69250a0cc857968b8beef749ab4f7b29164cb9a8a3150d094c318fc"}, + {file = "python_bidi-0.6.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:282c60f86f30ba6263e10427fec528ab17dde97c7a6653c0187d8e2412dec6f4"}, + {file = "python_bidi-0.6.3-cp38-none-win32.whl", hash = "sha256:5d33d011d334795ff4d5d0de57457a980f76055a338ebabe558e795f9e0fbe63"}, + {file = "python_bidi-0.6.3-cp38-none-win_amd64.whl", hash = "sha256:535069329c12ea08ad6a3b38c48cba2d912a704dee25566e7a37f2b67be9fece"}, + {file = "python_bidi-0.6.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d0945a3f116700ebc791911797096afe3e24953927b335c9c818f56475915aef"}, + {file = "python_bidi-0.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8eb231d9ab92bfd4a9b7e7282210f02d130d7935ec8cfb1d82d6d53fa858a3de"}, + {file = "python_bidi-0.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce288cb2af08d5242b034ce4e6936e540046a4c5fbccda72610ac67d8b06b5cc"}, + {file = "python_bidi-0.6.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:304094c931a9ca45a347fa31db9e01b9cbefd48a194950b1441f20ba24ff0d17"}, + {file = "python_bidi-0.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3df42de54d0f0daea931439abefc97da6c642d3665bcde510cd31689230777ff"}, + {file = "python_bidi-0.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc5a29d459deb7ce923cf33283de15776fa8008c55b42e1eed5ba76980cc01f3"}, + {file = "python_bidi-0.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae862fc829ee747b30cce23353b2266c706376c9b1ebfea943d63731eb1a0cbd"}, + {file = "python_bidi-0.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9419c87a7657b1b39497302461c7e501bbfd03442186083007e9a1627656871"}, + {file = "python_bidi-0.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d352154a636a243235260ecd8dcbd2b73e3e7d1f42c280fdb6802876152f1435"}, + {file = "python_bidi-0.6.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:04448d5355db4233da49c9f656b43b34fa0467f6b0c8ff766c1543eaed52f974"}, + {file = "python_bidi-0.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07eb504fcefc9e9f416f03c089dce23b1ba79d0dd38e976f6f00944d8c708461"}, + {file = "python_bidi-0.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7e083f0e7391b5672f9e76393d8d111e1bf875f784ad3659776a4881440ebf1c"}, + {file = "python_bidi-0.6.3-cp39-none-win32.whl", hash = "sha256:a153364706cacaea4f97a63b3d5db780d56b66c0a64f1d202065d3863f782075"}, + {file = "python_bidi-0.6.3-cp39-none-win_amd64.whl", hash = "sha256:4be0d628b84c2a524d080c653726fba6e518432f33ac970db25c6366b9b71303"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:28cd25ef6141a77e04a7fb6fef0a19cc307106f84a891777fcdd3306ae8cfc20"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:e4eab3736a14b8d9daea3e8e638ca5a24051497152ba32fb08db9259dd77b858"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78d12927cab0f6b8304f04c9ed72bc1a2880df8974d8596e40e7e596c6a98b2e"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:440be542b334da05673bd94d53ba4922482b06fa3f4daca6c8fa7434afb33e8a"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a9635ae0c9ee71b69f11cb6ab9523165c79fdb82ca53afb5afb0d401616fef80"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ebac008620916b0c02623926fd80719f2e61e4fa9b626ed1e309a6818b57486"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bb5fd4d9ccad52584ce8ad1468ec2e5b535519840ab1debe05c7fe4d32b800"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1caacb766872c54742cdb8a5c042bec1282c5a3144e4aeba6f8650ab8911d7f3"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:584dd7c4617ea0ef39900ef7b06b8c61e6ce3ccb4b90c28ed28fa3bf770c5124"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:a3bdc284cc4a1d70942ba0582b91853403c5ca7df79909b755be69089ecc5e17"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:995ed295f2d9095facbef3025d79e209ec7ae1be0d1f385a49818edb2cb4421e"}, + {file = "python_bidi-0.6.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:a50d62f4f1e10682babd529d46e9e62236ff202d3025a223c17ead32035cb410"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ea2898279afde47dcfec7a821abb54f7476e5584b655389aa731a50b90f8ea52"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:fe9b6a33f281814dfbf12fe27f35b8780edd6da62ce2a034994f006d6d0184e7"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435579d0bf2a7e2f872bb5c7254fe89cddfdea6909ed6dc3e8af4ffe1f3f1f18"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8d76a8452c9fa1ece0a70a7be15a516861c3875bb621e125305d0141ceac8e3"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbf64f5e3f19913f63f34832c0ddef5ea6a772c5dda54907a949e804c20021e3"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e226a888e13c9cf1e9f0de0a2ff5c98a50561cada19c0b0c69c76343685ee54"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308ee73ffaa771048b1ccec37145a2735da9d67df55583c2fc2cb73d78e86a91"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d85744ddcfeb207bbf6774b7e1a29af6e5e208ed5dbecc5853ec60ed8bc8242f"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:40ab8bf2f528a28a70231ce4015d81aea6d8f0a0cdd2bdaf024e9e7849a5ee55"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:aa857199f9d797c615a92ae1dec90d443a50373caf7af2cf4e791714afc31b2a"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:197fcd6cb88f021333622f83d7d68e842ab9e2df492ab04e1e84b6de8f15c698"}, + {file = "python_bidi-0.6.3-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:f832a45f05c15072edf473c6c3b9b164b25a2515f723d42c7400db848c299e59"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3b2ee6cf7cadb5d5cc05eca4b8b55a433dab922633faf85b0d19ec2aeed9ad5b"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:835b1d72364618fc8313bfdba2f65ce8e11bd9c1eab01fe9a3c3ec93063cb5b1"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f92e0d6771d184bbb7b06645edb069c023f695de312bf78e35efe45e0da7f66"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7a6a3e0e130a0db20c4808242470277045e921b414cd9f545cba67a8c17bb785"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2b2ee73e0f799ed234ed52af258f77a72aca216477d3ef072c59303f1a938c9"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:042d0ee4bb9286b605b252488501bdae6f5b249fe2422fb12e4884aa4dc316d1"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa68ef8e955d0d63fe2d9aac4f8b8b9f47869bf98a8773c7322918312dbdd109"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d07c491c2cede5b022356003070bc8e452a0dcf1d884db4a384e9a3383b9efd3"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:6132d661a3bfac2b8cf0d301bcdd59c7cc3e2145ea090b75505816604d8118d5"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:61b8a3dc229617b2f8c15165001babf4a199af9001087cad10ded14ec0a028d4"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:5bcab7ac734c5102e90b0f41274b81bdcf55009b05aaa6a653320d63304f20a7"}, + {file = "python_bidi-0.6.3-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:44560c6abcf3a512e618b52b33bce3d053eaf020c0677d3b4512167715900e66"}, + {file = "python_bidi-0.6.3.tar.gz", hash = "sha256:e12114969001a328aea859f79efc30ab9c15241befb86e07029d8961d97fae36"}, ] [package.extras] @@ -3365,26 +3412,22 @@ pyyaml = "*" [[package]] name = "qrcode" -version = "7.4.2" +version = "8.0" description = "QR Code image generator" optional = false -python-versions = ">=3.7" +python-versions = "<4.0,>=3.9" files = [ - {file = "qrcode-7.4.2-py3-none-any.whl", hash = "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a"}, - {file = "qrcode-7.4.2.tar.gz", hash = "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845"}, + {file = "qrcode-8.0-py3-none-any.whl", hash = "sha256:9fc05f03305ad27a709eb742cf3097fa19e6f6f93bb9e2f039c0979190f6f1b1"}, + {file = "qrcode-8.0.tar.gz", hash = "sha256:025ce2b150f7fe4296d116ee9bad455a6643ab4f6e7dce541613a4758cbce347"}, ] [package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -pypng = "*" -typing-extensions = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] -all = ["pillow (>=9.1.0)", "pytest", "pytest-cov", "tox", "zest.releaser[recommended]"] -dev = ["pytest", "pytest-cov", "tox"] -maintainer = ["zest.releaser[recommended]"] +all = ["pillow (>=9.1.0)", "pypng"] pil = ["pillow (>=9.1.0)"] -test = ["coverage", "pytest"] +png = ["pypng"] [[package]] name = "questionary" @@ -3402,13 +3445,13 @@ prompt_toolkit = ">=2.0,<=3.0.36" [[package]] name = "redis" -version = "5.1.0" +version = "5.2.0" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" files = [ - {file = "redis-5.1.0-py3-none-any.whl", hash = "sha256:fd4fccba0d7f6aa48c58a78d76ddb4afc698f5da4a2c1d03d916e4fd7ab88cdd"}, - {file = "redis-5.1.0.tar.gz", hash = "sha256:b756df1e4a3858fcc0ef861f3fc53623a96c41e2b1f5304e09e0fe758d333d40"}, + {file = "redis-5.2.0-py3-none-any.whl", hash = "sha256:ae174f2bb3b1bf2b09d54bf3e51fbc1469cf6c10aa03e21141f51969801a7897"}, + {file = "redis-5.2.0.tar.gz", hash = "sha256:0b1087665a771b1ff2e003aa5bdd354f15a70c9e25d5a7dbf9c722c16528a7b0"}, ] [package.dependencies] @@ -3421,116 +3464,116 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)" [[package]] name = "regex" -version = "2024.9.11" +version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" files = [ - {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"}, - {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"}, - {file = "regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a"}, - {file = "regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0"}, - {file = "regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1"}, - {file = "regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9"}, - {file = "regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"}, - {file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"}, - {file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8"}, - {file = "regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8"}, - {file = "regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd"}, - {file = "regex-2024.9.11-cp38-cp38-win32.whl", hash = "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771"}, - {file = "regex-2024.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35"}, - {file = "regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142"}, - {file = "regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919"}, - {file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"}, + {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"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, + {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, + {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, + {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, + {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, + {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, + {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, + {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, + {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, + {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, + {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, + {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, + {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, + {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, ] [[package]] name = "reportlab" -version = "4.2.4" +version = "4.2.5" description = "The Reportlab Toolkit" optional = false python-versions = "<4,>=3.7" files = [ - {file = "reportlab-4.2.4-py3-none-any.whl", hash = "sha256:6e4d86647b8bfd772f475a58f9b0dcba4b340b1969f0db36333089f6ca9ab362"}, - {file = "reportlab-4.2.4.tar.gz", hash = "sha256:a00b57292e156a7bda84edf31d60c25578153076c8fb96331d0c59eddda052c8"}, + {file = "reportlab-4.2.5-py3-none-any.whl", hash = "sha256:eb2745525a982d9880babb991619e97ac3f661fae30571b7d50387026ca765ee"}, + {file = "reportlab-4.2.5.tar.gz", hash = "sha256:5cf35b8fd609b68080ac7bbb0ae1e376104f7d5f7b2d3914c7adc63f2593941f"}, ] [package.dependencies] @@ -3583,51 +3626,52 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] [[package]] name = "rich" -version = "13.8.1" +version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" files = [ - {file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"}, - {file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"}, + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rich-click" -version = "1.8.3" +version = "1.8.5" description = "Format click help output nicely with rich" optional = false python-versions = ">=3.7" files = [ - {file = "rich_click-1.8.3-py3-none-any.whl", hash = "sha256:636d9c040d31c5eee242201b5bf4f2d358bfae4db14bb22ec1cafa717cfd02cd"}, - {file = "rich_click-1.8.3.tar.gz", hash = "sha256:6d75bdfa7aa9ed2c467789a0688bc6da23fbe3a143e19aa6ad3f8bac113d2ab3"}, + {file = "rich_click-1.8.5-py3-none-any.whl", hash = "sha256:0fab7bb5b66c15da17c210b4104277cd45f3653a7322e0098820a169880baee0"}, + {file = "rich_click-1.8.5.tar.gz", hash = "sha256:a3eebe81da1c9da3c32f3810017c79bd687ff1b3fa35bfc9d8a3338797f1d1a1"}, ] [package.dependencies] click = ">=7" rich = ">=10.7" -typing-extensions = "*" +typing_extensions = ">=4" [package.extras] dev = ["mypy", "packaging", "pre-commit", "pytest", "pytest-cov", "rich-codex", "ruff", "types-setuptools"] -docs = ["markdown-include", "mkdocs", "mkdocs-glightbox", "mkdocs-material-extensions", "mkdocs-material[imaging] (>=9.5.18,<9.6.0)", "mkdocs-rss-plugin", "mkdocstrings[python]", "rich-codex"] +docs = ["markdown_include", "mkdocs", "mkdocs-glightbox", "mkdocs-material-extensions", "mkdocs-material[imaging] (>=9.5.18,<9.6.0)", "mkdocs-rss-plugin", "mkdocstrings[python]", "rich-codex"] [[package]] name = "s3transfer" -version = "0.10.2" +version = "0.10.4" description = "An Amazon S3 Transfer Manager" optional = false python-versions = ">=3.8" files = [ - {file = "s3transfer-0.10.2-py3-none-any.whl", hash = "sha256:eca1c20de70a39daee580aef4986996620f365c4e0fda6a86100231d62f1bf69"}, - {file = "s3transfer-0.10.2.tar.gz", hash = "sha256:0711534e9356d3cc692fdde846b4a1e4b0cb6519971860796e6bc4c7aea00ef6"}, + {file = "s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e"}, + {file = "s3transfer-0.10.4.tar.gz", hash = "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7"}, ] [package.dependencies] @@ -3675,13 +3719,13 @@ files = [ [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] @@ -3738,19 +3782,38 @@ saml = ["python3-saml (>=1.5.0)"] [[package]] name = "sqlparse" -version = "0.5.1" +version = "0.5.2" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" files = [ - {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"}, - {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"}, + {file = "sqlparse-0.5.2-py3-none-any.whl", hash = "sha256:e99bc85c78160918c3e1d9230834ab8d80fc06c59d03f8db2618f65f65dda55e"}, + {file = "sqlparse-0.5.2.tar.gz", hash = "sha256:9e37b35e16d1cc652a2545f0997c1deb23ea28fa1f3eefe609eee3063c3b105f"}, ] [package.extras] dev = ["build", "hatch"] doc = ["sphinx"] +[[package]] +name = "strelix-core" +version = "0.0.1" +description = "github.com/Strelix/Core" +optional = false +python-versions = ">=3.10,<4.0" +files = [] +develop = true + +[package.dependencies] +bleach = "6.1.0" +django = "^5.0.7" +setuptools = "^70.1.1" +tblib = "^3.0.0" + +[package.source] +type = "directory" +url = "../core" + [[package]] name = "stripe" version = "10.12.0" @@ -3813,13 +3876,43 @@ test = ["flake8", "isort", "pytest"] [[package]] name = "tomli" -version = "2.0.1" +version = "2.2.1" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] @@ -3835,33 +3928,34 @@ files = [ [[package]] name = "tqdm" -version = "4.66.5" +version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, - {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] [[package]] name = "types-awscrt" -version = "0.21.5" +version = "0.23.3" description = "Type annotations and code completion for awscrt" optional = false python-versions = ">=3.8" files = [ - {file = "types_awscrt-0.21.5-py3-none-any.whl", hash = "sha256:117ff2b1bb657f09d01b7e0ce3fe3fa6e039be12d30b826896182725c9ce85b1"}, - {file = "types_awscrt-0.21.5.tar.gz", hash = "sha256:9f7f47de68799cb2bcb9e486f48d77b9f58962b92fba43cb8860da70b3c57d1b"}, + {file = "types_awscrt-0.23.3-py3-none-any.whl", hash = "sha256:cc0057885cb7ce1e66856123a4c2861b051e9f0716b1767ad72bfe4ca26bbcd4"}, + {file = "types_awscrt-0.23.3.tar.gz", hash = "sha256:043c0ae0fe5d272618294cbeaf1c349a654a9f7c00121be64d27486933ac4a26"}, ] [[package]] @@ -3880,46 +3974,46 @@ types-setuptools = "*" [[package]] name = "types-docutils" -version = "0.21.0.20240907" +version = "0.21.0.20241128" description = "Typing stubs for docutils" optional = false python-versions = ">=3.8" files = [ - {file = "types-docutils-0.21.0.20240907.tar.gz", hash = "sha256:5dd2aa5e2e06fcfa090020bc4115479b4dd28da3329ab708563ee29894bd3c0d"}, - {file = "types_docutils-0.21.0.20240907-py3-none-any.whl", hash = "sha256:9c8ed6d90583944af00f6b5fa3aecc2101e20672f6b1a4a299c6bf7d1e47084d"}, + {file = "types_docutils-0.21.0.20241128-py3-none-any.whl", hash = "sha256:e0409204009639e9b0bf4521eeabe58b5e574ce9c0db08421c2ac26c32be0039"}, + {file = "types_docutils-0.21.0.20241128.tar.gz", hash = "sha256:4dd059805b83ac6ec5a223699195c4e9eeb0446a4f7f2aeff1759a4a7cc17473"}, ] [[package]] name = "types-markdown" -version = "3.7.0.20240822" +version = "3.7.0.20241204" description = "Typing stubs for Markdown" optional = false python-versions = ">=3.8" files = [ - {file = "types-Markdown-3.7.0.20240822.tar.gz", hash = "sha256:183557c9f4f865bdefd8f5f96a38145c31819271cde111d35557c3bd2069e78d"}, - {file = "types_Markdown-3.7.0.20240822-py3-none-any.whl", hash = "sha256:bec91c410aaf2470ffdb103e38438fbcc53689b00133f19e64869eb138432ad7"}, + {file = "types_Markdown-3.7.0.20241204-py3-none-any.whl", hash = "sha256:f96146c367ea9c82bfe9903559d72706555cc2a1a3474c58ebba03b418ab18da"}, + {file = "types_markdown-3.7.0.20241204.tar.gz", hash = "sha256:ecca2b25cd23163fd28ed5ba34d183d731da03e8a5ed3a20b60daded304c5410"}, ] [[package]] name = "types-protobuf" -version = "5.28.0.20240924" +version = "5.28.3.20241203" description = "Typing stubs for protobuf" optional = false python-versions = ">=3.8" files = [ - {file = "types-protobuf-5.28.0.20240924.tar.gz", hash = "sha256:d181af8a256e5a91ce8d5adb53496e880efd9144c7d54483e3653332b60296f0"}, - {file = "types_protobuf-5.28.0.20240924-py3-none-any.whl", hash = "sha256:5cecf612ccdefb7dc95f7a51fb502902f20fc2e6681cd500184aaa1b3931d6a7"}, + {file = "types_protobuf-5.28.3.20241203-py3-none-any.whl", hash = "sha256:5367632a4785394b0504e7c1e4d54a2dceeae9cd4f73a705d0f6499fc99cecb1"}, + {file = "types_protobuf-5.28.3.20241203.tar.gz", hash = "sha256:2e1c962bdf76c576506b5fc0678d28efa6652a54c04ae46562a6209e777bd789"}, ] [[package]] name = "types-psycopg2" -version = "2.9.21.20240819" +version = "2.9.21.20241019" description = "Typing stubs for psycopg2" optional = false python-versions = ">=3.8" files = [ - {file = "types-psycopg2-2.9.21.20240819.tar.gz", hash = "sha256:4ed6b47464d6374fa64e5e3b234cea0f710e72123a4596d67ab50b7415a84666"}, - {file = "types_psycopg2-2.9.21.20240819-py3-none-any.whl", hash = "sha256:c9192311c27d7ad561eef705f1b2df1074f2cdcf445a98a6a2fcaaaad43278cf"}, + {file = "types-psycopg2-2.9.21.20241019.tar.gz", hash = "sha256:bca89b988d2ebd19bcd08b177d22a877ea8b841decb10ed130afcf39404612fa"}, + {file = "types_psycopg2-2.9.21.20241019-py3-none-any.whl", hash = "sha256:44d091e67732d16a941baae48cd7b53bf91911bc36888652447cf1ef0c1fb3f6"}, ] [[package]] @@ -3950,24 +4044,24 @@ types-setuptools = "*" [[package]] name = "types-python-dateutil" -version = "2.9.0.20240906" +version = "2.9.0.20241003" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.9.0.20240906.tar.gz", hash = "sha256:9706c3b68284c25adffc47319ecc7947e5bb86b3773f843c73906fd598bc176e"}, - {file = "types_python_dateutil-2.9.0.20240906-py3-none-any.whl", hash = "sha256:27c8cc2d058ccb14946eebcaaa503088f4f6dbc4fb6093d3d456a49aef2753f6"}, + {file = "types-python-dateutil-2.9.0.20241003.tar.gz", hash = "sha256:58cb85449b2a56d6684e41aeefb4c4280631246a0da1a719bdbe6f3fb0317446"}, + {file = "types_python_dateutil-2.9.0.20241003-py3-none-any.whl", hash = "sha256:250e1d8e80e7bbc3a6c99b907762711d1a1cdd00e978ad39cb5940f6f0a87f3d"}, ] [[package]] name = "types-pytz" -version = "2024.2.0.20240913" +version = "2024.2.0.20241003" description = "Typing stubs for pytz" optional = false python-versions = ">=3.8" files = [ - {file = "types-pytz-2024.2.0.20240913.tar.gz", hash = "sha256:4433b5df4a6fc587bbed41716d86a5ba5d832b4378e506f40d34bc9c81df2c24"}, - {file = "types_pytz-2024.2.0.20240913-py3-none-any.whl", hash = "sha256:a1eebf57ebc6e127a99d2fa2ba0a88d2b173784ef9b3defcc2004ab6855a44df"}, + {file = "types-pytz-2024.2.0.20241003.tar.gz", hash = "sha256:575dc38f385a922a212bac00a7d6d2e16e141132a3c955078f4a4fd13ed6cb44"}, + {file = "types_pytz-2024.2.0.20241003-py3-none-any.whl", hash = "sha256:3e22df1336c0c6ad1d29163c8fda82736909eb977281cb823c57f8bae07118b7"}, ] [[package]] @@ -3983,13 +4077,13 @@ files = [ [[package]] name = "types-requests" -version = "2.32.0.20240914" +version = "2.32.0.20241016" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" files = [ - {file = "types-requests-2.32.0.20240914.tar.gz", hash = "sha256:2850e178db3919d9bf809e434eef65ba49d0e7e33ac92d588f4a5e295fffd405"}, - {file = "types_requests-2.32.0.20240914-py3-none-any.whl", hash = "sha256:59c2f673eb55f32a99b2894faf6020e1a9f4a402ad0f192bfee0b64469054310"}, + {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"}, + {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"}, ] [package.dependencies] @@ -3997,35 +4091,35 @@ urllib3 = ">=2" [[package]] name = "types-s3transfer" -version = "0.10.2" +version = "0.10.4" description = "Type annotations and code completion for s3transfer" optional = false python-versions = ">=3.8" files = [ - {file = "types_s3transfer-0.10.2-py3-none-any.whl", hash = "sha256:7a3fec8cd632e2b5efb665a355ef93c2a87fdd5a45b74a949f95a9e628a86356"}, - {file = "types_s3transfer-0.10.2.tar.gz", hash = "sha256:60167a3bfb5c536ec6cdb5818f7f9a28edca9dc3e0b5ff85ae374526fc5e576e"}, + {file = "types_s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:22ac1aabc98f9d7f2928eb3fb4d5c02bf7435687f0913345a97dd3b84d0c217d"}, + {file = "types_s3transfer-0.10.4.tar.gz", hash = "sha256:03123477e3064c81efe712bf9d372c7c72f2790711431f9baa59cf96ea607267"}, ] [[package]] name = "types-setuptools" -version = "75.1.0.20240917" +version = "75.6.0.20241126" description = "Typing stubs for setuptools" optional = false python-versions = ">=3.8" files = [ - {file = "types-setuptools-75.1.0.20240917.tar.gz", hash = "sha256:12f12a165e7ed383f31def705e5c0fa1c26215dd466b0af34bd042f7d5331f55"}, - {file = "types_setuptools-75.1.0.20240917-py3-none-any.whl", hash = "sha256:06f78307e68d1bbde6938072c57b81cf8a99bc84bd6dc7e4c5014730b097dc0c"}, + {file = "types_setuptools-75.6.0.20241126-py3-none-any.whl", hash = "sha256:aaae310a0e27033c1da8457d4d26ac673b0c8a0de7272d6d4708e263f2ea3b9b"}, + {file = "types_setuptools-75.6.0.20241126.tar.gz", hash = "sha256:7bf25ad4be39740e469f9268b6beddda6e088891fa5a27e985c6ce68bf62ace0"}, ] [[package]] name = "types-six" -version = "1.16.21.20240513" +version = "1.17.0.20241205" description = "Typing stubs for six" optional = false python-versions = ">=3.8" files = [ - {file = "types-six-1.16.21.20240513.tar.gz", hash = "sha256:cdf445b5161bf17753500713a475ab79a45bd0d87728b8bfcecd86e2fbf66402"}, - {file = "types_six-1.16.21.20240513-py3-none-any.whl", hash = "sha256:af2a105be6d504339bfed81319cc8e8697865f0ee5c6baa63658f127b33b9e63"}, + {file = "types_six-1.17.0.20241205-py3-none-any.whl", hash = "sha256:a4947c2bdcd9ab69d44466a533a15839ff48ddc27223615cb8145d73ab805bc2"}, + {file = "types_six-1.17.0.20241205.tar.gz", hash = "sha256:1f662347a8f3b2bf30517d629d82f591420df29811794b0bf3804e14d716f6e0"}, ] [[package]] @@ -4041,21 +4135,21 @@ files = [ [[package]] name = "typos" -version = "1.24.6" +version = "1.28.2" description = "Source Code Spelling Correction" optional = false python-versions = ">=3.7" files = [ - {file = "typos-1.24.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be576cd0afcbf72bd0fa4129d457b146627c837db189eae7ee83b9fc311dacef"}, - {file = "typos-1.24.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:49fc10b7d28a6a016678c92a5b3d091ea46a2a7e09d5d1122045e8509378f785"}, - {file = "typos-1.24.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfbd7c40af229d680c2b9bc90e846eea70626bde9608f77a57c4e72145a5aa5f"}, - {file = "typos-1.24.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8eb05826d6ff1f8747e1c7d9991a10e13b644b2eb7e2855cc79a37ebb1104f1"}, - {file = "typos-1.24.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2abbe9dc208f6da9fddbf9bb281a3944d66188df9b3d43ad6f2f99721713446"}, - {file = "typos-1.24.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7291555c82e81e305ab3e10cb04d0f7d49ccecc1ced322c60f4619f6a14c7225"}, - {file = "typos-1.24.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7e27c307c26549a7986f2701f161358df29543e818bf9d6d81f0a81ca5ddeff5"}, - {file = "typos-1.24.6-py3-none-win32.whl", hash = "sha256:cd725db3823c319f7e97b4e8e9fa4af143568b1c7d834f66c584bf86b9691f94"}, - {file = "typos-1.24.6-py3-none-win_amd64.whl", hash = "sha256:12972e7a8be14fe5e7f0392de0b228a0098748959d1fecc35c4e8eab3efc04c0"}, - {file = "typos-1.24.6.tar.gz", hash = "sha256:0feda2aab59fc1c32cd1f382ea8676b4ef0921086ab172a43e69e5bb19206993"}, + {file = "typos-1.28.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2ce911fabe95b4900aa063c159aee3bb883c7412c058b161a7f15d3ae293dcc8"}, + {file = "typos-1.28.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8643a81aec9d36a719b421350e7ab14c3db2441b4527a1ab146b7a1e98f7da27"}, + {file = "typos-1.28.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8299d3b89d44c0b544dc37edd419c2424ce743d6aaaa1df7bccac4f98fb1383"}, + {file = "typos-1.28.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b3441c505f31baff0fd8ed3938c6d26102b8842a3321b2eddd139e6563f77a5"}, + {file = "typos-1.28.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cadf5c68d4818cedb0dde7ab8a07a7f02c4f184859a869871ec14ab4d710ad1"}, + {file = "typos-1.28.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0d1151b202ab11c0a75bd986a0e2afa28e516a7d6180e474a642ddc5d8437091"}, + {file = "typos-1.28.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:91eb18d188f9f62dba5c98d4d8d799b7160a98f2dd09d15153be8c993d17d1a9"}, + {file = "typos-1.28.2-py3-none-win32.whl", hash = "sha256:8af5d31dd79c3f84c161f8f0245224ed8523fee67107a13651a2257a7fc95ce8"}, + {file = "typos-1.28.2-py3-none-win_amd64.whl", hash = "sha256:74948f6c264181ee2881336dbb944dcbf2d168bee661d8d133fa9946f74a5098"}, + {file = "typos-1.28.2.tar.gz", hash = "sha256:c9adf7d20605fd59c27852050a4cb483dc2004fb61bca3f09768ca368d0308d6"}, ] [[package]] @@ -4127,13 +4221,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.6" +version = "20.28.0" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, - {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, + {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, + {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, ] [package.dependencies] @@ -4147,41 +4241,41 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "watchdog" -version = "5.0.3" +version = "6.0.0" description = "Filesystem events monitoring" optional = false python-versions = ">=3.9" files = [ - {file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:85527b882f3facda0579bce9d743ff7f10c3e1e0db0a0d0e28170a7d0e5ce2ea"}, - {file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53adf73dcdc0ef04f7735066b4a57a4cd3e49ef135daae41d77395f0b5b692cb"}, - {file = "watchdog-5.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e25adddab85f674acac303cf1f5835951345a56c5f7f582987d266679979c75b"}, - {file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f01f4a3565a387080dc49bdd1fefe4ecc77f894991b88ef927edbfa45eb10818"}, - {file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91b522adc25614cdeaf91f7897800b82c13b4b8ac68a42ca959f992f6990c490"}, - {file = "watchdog-5.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d52db5beb5e476e6853da2e2d24dbbbed6797b449c8bf7ea118a4ee0d2c9040e"}, - {file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d11b07c64f63f49876e0ab8042ae034674c8653bfcdaa8c4b32e71cfff87e8"}, - {file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:349c9488e1d85d0a58e8cb14222d2c51cbc801ce11ac3936ab4c3af986536926"}, - {file = "watchdog-5.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:53a3f10b62c2d569e260f96e8d966463dec1a50fa4f1b22aec69e3f91025060e"}, - {file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:950f531ec6e03696a2414b6308f5c6ff9dab7821a768c9d5788b1314e9a46ca7"}, - {file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6deb336cba5d71476caa029ceb6e88047fc1dc74b62b7c4012639c0b563906"}, - {file = "watchdog-5.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1021223c08ba8d2d38d71ec1704496471ffd7be42cfb26b87cd5059323a389a1"}, - {file = "watchdog-5.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:752fb40efc7cc8d88ebc332b8f4bcbe2b5cc7e881bccfeb8e25054c00c994ee3"}, - {file = "watchdog-5.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a2e8f3f955d68471fa37b0e3add18500790d129cc7efe89971b8a4cc6fdeb0b2"}, - {file = "watchdog-5.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b8ca4d854adcf480bdfd80f46fdd6fb49f91dd020ae11c89b3a79e19454ec627"}, - {file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:90a67d7857adb1d985aca232cc9905dd5bc4803ed85cfcdcfcf707e52049eda7"}, - {file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:720ef9d3a4f9ca575a780af283c8fd3a0674b307651c1976714745090da5a9e8"}, - {file = "watchdog-5.0.3-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:223160bb359281bb8e31c8f1068bf71a6b16a8ad3d9524ca6f523ac666bb6a1e"}, - {file = "watchdog-5.0.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:560135542c91eaa74247a2e8430cf83c4342b29e8ad4f520ae14f0c8a19cfb5b"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dd021efa85970bd4824acacbb922066159d0f9e546389a4743d56919b6758b91"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:78864cc8f23dbee55be34cc1494632a7ba30263951b5b2e8fc8286b95845f82c"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_i686.whl", hash = "sha256:1e9679245e3ea6498494b3028b90c7b25dbb2abe65c7d07423ecfc2d6218ff7c"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:9413384f26b5d050b6978e6fcd0c1e7f0539be7a4f1a885061473c5deaa57221"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:294b7a598974b8e2c6123d19ef15de9abcd282b0fbbdbc4d23dfa812959a9e05"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:26dd201857d702bdf9d78c273cafcab5871dd29343748524695cecffa44a8d97"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:0f9332243355643d567697c3e3fa07330a1d1abf981611654a1f2bf2175612b7"}, - {file = "watchdog-5.0.3-py3-none-win32.whl", hash = "sha256:c66f80ee5b602a9c7ab66e3c9f36026590a0902db3aea414d59a2f55188c1f49"}, - {file = "watchdog-5.0.3-py3-none-win_amd64.whl", hash = "sha256:f00b4cf737f568be9665563347a910f8bdc76f88c2970121c86243c8cfdf90e9"}, - {file = "watchdog-5.0.3-py3-none-win_ia64.whl", hash = "sha256:49f4d36cb315c25ea0d946e018c01bb028048023b9e103d3d3943f58e109dd45"}, - {file = "watchdog-5.0.3.tar.gz", hash = "sha256:108f42a7f0345042a854d4d0ad0834b741d421330d5f575b81cb27b883500176"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, ] [package.extras] @@ -4241,4 +4335,4 @@ test = ["coverage (>=5.3)", "tomli (>=2.0.1)", "tox"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "e2bd211471c70f947c64f5d1944b763a09db833837c36b6875564220bbc615e6" +content-hash = "a41b52ec2a03574be2144fdf7e433f5202f60749c3694dab85677b6fd6a2beca" diff --git a/pyproject.toml b/pyproject.toml index 199ed487c..f26ca8305 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ authors = ["TreyWW"] readme = "README.md" [tool.poetry.dependencies] +strelix-core = { path = "../core/", develop = true } python = ">=3.10,<4.0" bleach = "6.1.0" # used for HTML sanitation boto3 = "1.34.76" # AWS diff --git a/settings/settings.py b/settings/settings.py index 3819e8f13..bd163237b 100644 --- a/settings/settings.py +++ b/settings/settings.py @@ -64,6 +64,7 @@ "drf_yasg", "tz_detect", "webpack_loader", + "core", # "django_minify_html", ] From ec02ccd0e09cb030cba2669dca6f79181e0c7250 Mon Sep 17 00:00:00 2001 From: Trey <73353716+TreyWW@users.noreply.github.com> Date: Thu, 19 Dec 2024 22:35:29 +0000 Subject: [PATCH 02/21] Initial Commit Signed-off-by: Trey <73353716+TreyWW@users.noreply.github.com> --- .gitignore | 2 +- backend/admin.py | 80 +- backend/api/urls.py | 10 + backend/apps.py | 1 - backend/{core => boto3}/__init__.py | 0 .../api => boto3/async_tasks}/__init__.py | 0 .../asyn_tasks => boto3/async_tasks}/tasks.py | 0 backend/{core/service => }/boto3/handler.py | 0 .../api/base => boto3/scheduler}/__init__.py | 0 .../boto3/scheduler/create_schedule.py | 4 +- .../boto3/scheduler/delete_schedule.py | 8 +- .../{core/service => }/boto3/scheduler/get.py | 4 +- .../service => }/boto3/scheduler/pause.py | 6 +- .../boto3/scheduler/update_schedule.py | 6 +- backend/clients/api/delete.py | 4 +- backend/clients/api/fetch.py | 6 +- backend/clients/models.py | 4 +- backend/clients/views/create.py | 4 +- backend/clients/views/dashboard.py | 2 +- backend/clients/views/detail.py | 6 +- backend/context_processors.py | 9 +- backend/core/api/base/breadcrumbs.py | 19 - backend/core/api/base/modal.py | 189 --- backend/core/api/base/notifications.py | 41 - backend/core/api/base/urls.py | 29 - backend/core/api/emails/fetch.py | 43 - backend/core/api/emails/send.py | 374 ----- backend/core/api/emails/status.py | 100 -- backend/core/api/emails/urls.py | 23 - backend/core/api/healthcheck/healthcheck.py | 17 - backend/core/api/healthcheck/urls.py | 18 - .../core/api/landing_page/email_waitlist.py | 49 - backend/core/api/landing_page/urls.py | 8 - backend/core/api/maintenance/now.py | 30 - backend/core/api/maintenance/urls.py | 9 - backend/core/api/public/__init__.py | 1 - backend/core/api/public/authentication.py | 66 - backend/core/api/public/decorators.py | 47 - .../api/public/endpoints/Invoices/create.py | 126 -- .../api/public/endpoints/Invoices/delete.py | 29 - .../public/endpoints/Invoices/download_pdf.py | 76 - .../api/public/endpoints/Invoices/edit.py | 132 -- .../core/api/public/endpoints/Invoices/get.py | 60 - .../api/public/endpoints/Invoices/list.py | 86 -- .../api/public/endpoints/Invoices/urls.py | 36 - .../api/public/endpoints/clients/create.py | 61 - .../api/public/endpoints/clients/delete.py | 68 - .../core/api/public/endpoints/clients/list.py | 52 - .../core/api/public/endpoints/clients/urls.py | 16 - .../api/public/endpoints/system_health.py | 63 - .../api/public/endpoints/webhooks/urls.py | 12 - .../webhooks/webhook_task_queue_handler.py | 46 - backend/core/api/public/helpers/deprecate.py | 46 - backend/core/api/public/helpers/response.py | 19 - backend/core/api/public/middleware.py | 0 backend/core/api/public/models.py | 70 - backend/core/api/public/permissions.py | 66 - .../core/api/public/serializers/clients.py | 9 - .../core/api/public/serializers/invoices.py | 31 - backend/core/api/public/swagger_ui.py | 42 - backend/core/api/public/types.py | 12 - backend/core/api/public/urls.py | 18 - backend/core/api/quotas/fetch.py | 30 - backend/core/api/quotas/requests.py | 136 -- backend/core/api/quotas/urls.py | 16 - backend/core/api/settings/api_keys.py | 69 - backend/core/api/settings/change_name.py | 40 - backend/core/api/settings/defaults.py | 94 -- backend/core/api/settings/email_templates.py | 32 - backend/core/api/settings/preferences.py | 48 - backend/core/api/settings/profile_picture.py | 29 - backend/core/api/settings/urls.py | 29 - backend/core/api/teams/create.py | 36 - backend/core/api/teams/create_user.py | 37 - backend/core/api/teams/edit_permissions.py | 31 - backend/core/api/teams/invites.py | 216 --- backend/core/api/teams/kick.py | 33 - backend/core/api/teams/leave.py | 32 - backend/core/api/teams/switch_team.py | 53 - backend/core/api/teams/urls.py | 58 - backend/core/api/urls.py | 19 - backend/core/data/default_feature_flags.py | 26 - backend/core/data/default_quota_limits.py | 186 --- backend/core/management/commands/auto.py | 12 - .../management/commands/contributors.json | 266 ---- .../core/management/commands/contributors.py | 295 ---- .../core/management/commands/feature_flags.py | 55 - .../commands/generate_aws_scheduler_apikey.py | 32 - backend/core/management/commands/lint.py | 32 - .../management/commands/navbar_refresh.py | 12 - backend/core/management/commands/test_urls.py | 9 - .../core/management/commands/test_views.py | 33 - .../scheduled_tasks/update_all_schedules.py | 23 - backend/core/models.py | 723 ---------- backend/core/service/__init__.py | 1 - backend/core/service/api_keys/delete.py | 18 - backend/core/service/api_keys/generate.py | 103 -- backend/core/service/api_keys/get.py | 10 - backend/core/service/base/breadcrumbs.py | 85 -- backend/core/service/clients/__init__.py | 0 backend/core/service/defaults/__init__.py | 0 backend/core/service/file_storage/__init__.py | 0 backend/core/service/invoices/__init__.py | 0 .../core/service/invoices/common/__init__.py | 0 .../invoices/common/create/__init__.py | 0 .../common/create/services/__init__.py | 0 .../invoices/common/emails/__init__.py | 0 .../service/invoices/recurring/__init__.py | 0 .../invoices/recurring/create/__init__.py | 0 .../invoices/recurring/generation/__init__.py | 0 .../invoices/recurring/schedules/__init__.py | 0 .../invoices/recurring/validate/__init__.py | 0 .../invoices/recurring/webhooks/__init__.py | 0 .../core/service/invoices/single/__init__.py | 0 .../invoices/single/create/__init__.py | 0 backend/core/service/maintenance/__init__.py | 0 .../service/maintenance/expire/__init__.py | 0 .../core/service/maintenance/expire/run.py | 38 - backend/core/service/permissions/__init__.py | 0 backend/core/service/permissions/scopes.py | 34 - backend/core/service/reports/__init__.py | 0 backend/core/service/settings/__init__.py | 0 backend/core/service/settings/update.py | 36 - backend/core/service/settings/view.py | 54 - backend/core/service/teams/__init__.py | 0 backend/core/service/teams/create_user.py | 65 - backend/core/service/teams/fetch.py | 8 - backend/core/service/teams/permissions.py | 47 - backend/core/service/webhooks/__init__.py | 0 backend/core/service/webhooks/auth.py | 0 backend/core/service/webhooks/get_url.py | 7 - backend/core/signals/__init__.py | 4 - backend/core/signals/migrations.py | 71 - backend/core/signals/signals.py | 98 -- backend/core/types/__init__.py | 0 backend/core/types/emails.py | 45 - backend/core/types/htmx.py | 24 - backend/core/types/requests.py | 19 - backend/core/utils/__init__.py | 0 backend/core/utils/calendar.py | 24 - backend/core/utils/dataclasses.py | 134 -- backend/core/utils/feature_flags.py | 29 - backend/core/utils/http_utils.py | 16 - backend/core/utils/quota_limit_ops.py | 42 - backend/core/utils/service_retry.py | 19 - backend/core/views/__init__.py | 0 backend/core/views/auth/__init__.py | 0 backend/core/views/auth/create_account.py | 97 -- backend/core/views/auth/helpers.py | 3 - backend/core/views/auth/login.py | 275 ---- backend/core/views/auth/passwords/__init__.py | 0 backend/core/views/auth/passwords/generate.py | 105 -- backend/core/views/auth/passwords/set.py | 45 - backend/core/views/auth/passwords/view.py | 21 - backend/core/views/auth/urls.py | 75 - backend/core/views/auth/verify.py | 92 -- backend/core/views/emails/__init__.py | 0 backend/core/views/emails/dashboard.py | 13 - backend/core/views/emails/urls.py | 15 - backend/core/views/other/__init__.py | 2 - backend/core/views/other/errors.py | 63 - backend/core/views/other/index.py | 16 - backend/core/views/quotas/__init__.py | 0 backend/core/views/quotas/view.py | 28 - backend/core/views/settings/__init__.py | 0 backend/core/views/settings/teams.py | 81 -- backend/core/views/settings/urls.py | 15 - backend/core/views/settings/view.py | 84 -- backend/core/views/teams/__init__.py | 0 backend/core/views/teams/urls.py | 13 - backend/core/webhooks/__init__.py | 0 backend/core/webhooks/invoices/__init__.py | 0 backend/{core/api/emails => data}/__init__.py | 0 .../data/default_email_templates.py | 0 backend/decorators.py | 66 +- .../invoices/create/services/add_service.py | 4 +- .../api/invoices/create/set_destination.py | 4 +- backend/finance/api/invoices/delete.py | 4 +- backend/finance/api/invoices/edit.py | 2 +- backend/finance/api/invoices/fetch.py | 4 +- backend/finance/api/invoices/manage.py | 2 +- .../finance/api/invoices/recurring/delete.py | 6 +- .../finance/api/invoices/recurring/edit.py | 6 +- .../finance/api/invoices/recurring/fetch.py | 4 +- .../recurring/generate_next_invoice_now.py | 6 +- .../finance/api/invoices/recurring/poll.py | 8 +- .../api/invoices/recurring/update_status.py | 14 +- .../finance/api/invoices/reminders/fetch.py | 2 +- backend/finance/api/products/create.py | 2 +- backend/finance/api/products/fetch.py | 2 +- backend/finance/api/receipts/delete.py | 2 +- backend/finance/api/receipts/fetch.py | 2 +- backend/finance/api/receipts/new.py | 6 +- backend/finance/api/reports/fetch.py | 2 +- backend/finance/api/reports/generate.py | 4 +- backend/finance/models.py | 2 +- .../service}/__init__.py | 0 .../service/clients}/__init__.py | 0 .../service/clients/create.py | 4 +- .../service/clients/delete.py | 4 +- .../{core => finance}/service/clients/get.py | 2 +- .../service/clients/validate.py | 0 .../service/defaults}/__init__.py | 0 .../{core => finance}/service/defaults/get.py | 0 .../service/defaults/update.py | 4 +- .../service/invoices}/__init__.py | 0 .../service/invoices/common}/__init__.py | 0 .../invoices/common/create}/__init__.py | 0 .../service/invoices/common/create/create.py | 8 +- .../invoices/common/create/get_page.py | 8 +- .../common/create/services}/__init__.py | 0 .../invoices/common/create/services/add.py | 6 +- .../invoices/common/emails}/__init__.py | 0 .../invoices/common/emails/on_create.py | 10 +- .../service/invoices/common/fetch.py | 0 .../service/invoices/handler.py | 0 .../service/invoices/recurring}/__init__.py | 0 .../invoices/recurring/create}/__init__.py | 0 .../invoices/recurring/create/get_page.py | 4 +- .../service/invoices/recurring/create/save.py | 12 +- .../recurring/generation}/__init__.py | 0 .../recurring/generation/next_invoice.py | 6 +- .../service/invoices/recurring/get.py | 4 +- .../invoices/recurring/schedules}/__init__.py | 0 .../recurring/schedules/date_handlers.py | 2 +- .../invoices/recurring/validate}/__init__.py | 0 .../recurring/validate/frequencies.py | 2 +- .../invoices/recurring/webhooks}/__init__.py | 0 .../recurring/webhooks/webhook_apikey_auth.py | 6 +- .../service/invoices/single}/__init__.py | 0 .../invoices/single/create}/__init__.py | 0 .../service/invoices/single/create/create.py | 13 +- .../invoices/single/create/get_page.py | 4 +- .../service/invoices/single/create_pdf.py | 0 .../service/invoices/single/create_url.py | 4 +- .../service/invoices/single/get_invoice.py | 2 +- .../service/reports}/__init__.py | 0 .../service/reports/generate.py | 2 +- .../{core => finance}/service/reports/get.py | 2 +- backend/finance/signals/schedules.py | 4 +- backend/finance/views/invoices/handler.py | 2 +- .../views/invoices/recurring/create.py | 14 +- .../views/invoices/recurring/dashboard.py | 2 +- .../finance/views/invoices/recurring/edit.py | 2 +- .../views/invoices/recurring/overview.py | 2 +- .../finance/views/invoices/single/create.py | 6 +- .../views/invoices/single/dashboard.py | 2 +- backend/finance/views/invoices/single/edit.py | 4 +- .../views/invoices/single/manage_access.py | 6 +- backend/finance/views/invoices/single/view.py | 2 +- backend/finance/views/receipts/dashboard.py | 2 +- backend/finance/views/reports/dashboard.py | 2 +- backend/finance/views/reports/view.py | 4 +- backend/middleware.py | 4 +- backend/migrations/0001_initial.py | 2 +- ...alter_verificationcodes_expiry_and_more.py | 4 +- .../0023_apikey_invoiceonetimeschedule.py | 2 +- ...ereminder_boto_schedule_status_and_more.py | 8 +- ...lter_defaultvalues_default_invoice_logo.py | 2 +- backend/migrations/0049_filestoragefile.py | 4 +- ...ing_invoices_invoice_cancelled_and_more.py | 12 +- backend/models.py | 26 +- backend/storage/api/delete.py | 2 +- backend/storage/api/fetch.py | 4 +- backend/storage/file_storage.py | 2 +- backend/storage/models.py | 31 + .../service}/__init__.py | 0 .../service}/create.py | 2 +- .../file_storage => storage/service}/utils.py | 0 backend/storage/views/dashboard.py | 6 +- backend/storage/views/upload.py | 6 +- backend/templatetags/cal_filters.py | 16 - backend/templatetags/dictfilters.py | 13 - backend/templatetags/feature_enabled.py | 44 - backend/templatetags/listfilters.py | 53 - backend/templatetags/strfilters.py | 59 - backend/templatetags/utils.py | 42 - backend/urls.py | 49 +- .../service/base => webhooks}/__init__.py | 0 .../boto3 => webhooks/invoices}/__init__.py | 0 .../webhooks/invoices/invoice_status.py | 6 +- .../{core => }/webhooks/invoices/recurring.py | 6 +- backend/{core => }/webhooks/urls.py | 2 +- billing/__init__.py | 1 - billing/admin.py | 3 - billing/apps.py | 11 - billing/billing_settings.py | 43 - billing/data/__init__.py | 0 billing/data/default_usage_plans.py | 135 -- billing/decorators.py | 32 - billing/management/__init__.py | 0 billing/management/commands/__init__.py | 0 billing/management/commands/stripe.py | 43 - billing/middleware.py | 50 - billing/migrations/0001_initial.py | 89 -- .../0002_subscriptionplan_stripe_price_id.py | 18 - ...ookevent_usersubscription_uuid_and_more.py | 69 - billing/migrations/0004_auto_20240830_1655.py | 22 - billing/migrations/0005_auto_20240830_1655.py | 19 - billing/migrations/0006_billingusage.py | 51 - billing/migrations/__init__.py | 0 billing/models.py | 117 -- billing/service/__init__.py | 0 billing/service/checkout_completed.py | 48 - billing/service/entitlements.py | 57 - billing/service/get_user.py | 8 - billing/service/plan_change.py | 31 - billing/service/price.py | 8 - billing/service/stripe_customer.py | 32 - billing/service/subscription_ended.py | 39 - billing/service/subscription_handler.py | 86 -- billing/service/test.py | 38 - billing/signals/__init__.py | 1 - billing/signals/migrations.py | 69 - billing/signals/quotas.py | 15 - billing/signals/stripe/__init__.py | 1 - billing/signals/stripe/webhook_handler.py | 19 - billing/signals/usage.py | 59 - .../billing/dashboard/all_subscriptions.html | 71 - .../dashboard/choose_plan_section.html | 199 --- .../pages/billing/dashboard/dashboard.html | 15 - .../billing/dashboard/growth_usages.html | 137 -- .../billing/dashboard/starter_usages.html | 73 - billing/urls.py | 21 - billing/views.py | 3 - billing/views/__init__.py | 0 billing/views/change_plan.py | 111 -- billing/views/dashboard.py | 50 - billing/views/return_urls/failed.py | 9 - billing/views/return_urls/success.py | 7 - billing/views/stripe_misc.py | 30 - billing/views/stripe_webhooks.py | 33 - frontend/templates/pages/dashboard.html | 2 +- package-lock.json | 1235 +++++++++-------- package.json | 12 +- poetry.lock | 530 +++---- pyproject.toml | 2 +- .../boto3/scheduler => settings}/__init__.py | 0 settings/helpers.py | 2 +- settings/local_settings.py | 5 +- settings/settings.py | 8 +- tests/api/test_account_settings.py | 2 +- tests/handler.py | 2 +- tests/views/test_other.py | 26 - 344 files changed, 1277 insertions(+), 10457 deletions(-) create mode 100644 backend/api/urls.py rename backend/{core => boto3}/__init__.py (100%) rename backend/{core/api => boto3/async_tasks}/__init__.py (100%) rename backend/{core/service/asyn_tasks => boto3/async_tasks}/tasks.py (100%) rename backend/{core/service => }/boto3/handler.py (100%) rename backend/{core/api/base => boto3/scheduler}/__init__.py (100%) rename backend/{core/service => }/boto3/scheduler/create_schedule.py (95%) rename backend/{core/service => }/boto3/scheduler/delete_schedule.py (83%) rename backend/{core/service => }/boto3/scheduler/get.py (89%) rename backend/{core/service => }/boto3/scheduler/pause.py (87%) rename backend/{core/service => }/boto3/scheduler/update_schedule.py (95%) delete mode 100644 backend/core/api/base/breadcrumbs.py delete mode 100644 backend/core/api/base/modal.py delete mode 100644 backend/core/api/base/notifications.py delete mode 100644 backend/core/api/base/urls.py delete mode 100644 backend/core/api/emails/fetch.py delete mode 100644 backend/core/api/emails/send.py delete mode 100644 backend/core/api/emails/status.py delete mode 100644 backend/core/api/emails/urls.py delete mode 100644 backend/core/api/healthcheck/healthcheck.py delete mode 100644 backend/core/api/healthcheck/urls.py delete mode 100644 backend/core/api/landing_page/email_waitlist.py delete mode 100644 backend/core/api/landing_page/urls.py delete mode 100644 backend/core/api/maintenance/now.py delete mode 100644 backend/core/api/maintenance/urls.py delete mode 100644 backend/core/api/public/__init__.py delete mode 100644 backend/core/api/public/authentication.py delete mode 100644 backend/core/api/public/decorators.py delete mode 100644 backend/core/api/public/endpoints/Invoices/create.py delete mode 100644 backend/core/api/public/endpoints/Invoices/delete.py delete mode 100644 backend/core/api/public/endpoints/Invoices/download_pdf.py delete mode 100644 backend/core/api/public/endpoints/Invoices/edit.py delete mode 100644 backend/core/api/public/endpoints/Invoices/get.py delete mode 100644 backend/core/api/public/endpoints/Invoices/list.py delete mode 100644 backend/core/api/public/endpoints/Invoices/urls.py delete mode 100644 backend/core/api/public/endpoints/clients/create.py delete mode 100644 backend/core/api/public/endpoints/clients/delete.py delete mode 100644 backend/core/api/public/endpoints/clients/list.py delete mode 100644 backend/core/api/public/endpoints/clients/urls.py delete mode 100644 backend/core/api/public/endpoints/system_health.py delete mode 100644 backend/core/api/public/endpoints/webhooks/urls.py delete mode 100644 backend/core/api/public/endpoints/webhooks/webhook_task_queue_handler.py delete mode 100644 backend/core/api/public/helpers/deprecate.py delete mode 100644 backend/core/api/public/helpers/response.py delete mode 100644 backend/core/api/public/middleware.py delete mode 100644 backend/core/api/public/models.py delete mode 100644 backend/core/api/public/permissions.py delete mode 100644 backend/core/api/public/serializers/clients.py delete mode 100644 backend/core/api/public/serializers/invoices.py delete mode 100644 backend/core/api/public/swagger_ui.py delete mode 100644 backend/core/api/public/types.py delete mode 100644 backend/core/api/public/urls.py delete mode 100644 backend/core/api/quotas/fetch.py delete mode 100644 backend/core/api/quotas/requests.py delete mode 100644 backend/core/api/quotas/urls.py delete mode 100644 backend/core/api/settings/api_keys.py delete mode 100644 backend/core/api/settings/change_name.py delete mode 100644 backend/core/api/settings/defaults.py delete mode 100644 backend/core/api/settings/email_templates.py delete mode 100644 backend/core/api/settings/preferences.py delete mode 100644 backend/core/api/settings/profile_picture.py delete mode 100644 backend/core/api/settings/urls.py delete mode 100644 backend/core/api/teams/create.py delete mode 100644 backend/core/api/teams/create_user.py delete mode 100644 backend/core/api/teams/edit_permissions.py delete mode 100644 backend/core/api/teams/invites.py delete mode 100644 backend/core/api/teams/kick.py delete mode 100644 backend/core/api/teams/leave.py delete mode 100644 backend/core/api/teams/switch_team.py delete mode 100644 backend/core/api/teams/urls.py delete mode 100644 backend/core/api/urls.py delete mode 100644 backend/core/data/default_feature_flags.py delete mode 100644 backend/core/data/default_quota_limits.py delete mode 100644 backend/core/management/commands/auto.py delete mode 100644 backend/core/management/commands/contributors.json delete mode 100644 backend/core/management/commands/contributors.py delete mode 100644 backend/core/management/commands/feature_flags.py delete mode 100644 backend/core/management/commands/generate_aws_scheduler_apikey.py delete mode 100644 backend/core/management/commands/lint.py delete mode 100644 backend/core/management/commands/navbar_refresh.py delete mode 100644 backend/core/management/commands/test_urls.py delete mode 100644 backend/core/management/commands/test_views.py delete mode 100644 backend/core/management/scheduled_tasks/update_all_schedules.py delete mode 100644 backend/core/models.py delete mode 100644 backend/core/service/__init__.py delete mode 100644 backend/core/service/api_keys/delete.py delete mode 100644 backend/core/service/api_keys/generate.py delete mode 100644 backend/core/service/api_keys/get.py delete mode 100644 backend/core/service/base/breadcrumbs.py delete mode 100644 backend/core/service/clients/__init__.py delete mode 100644 backend/core/service/defaults/__init__.py delete mode 100644 backend/core/service/file_storage/__init__.py delete mode 100644 backend/core/service/invoices/__init__.py delete mode 100644 backend/core/service/invoices/common/__init__.py delete mode 100644 backend/core/service/invoices/common/create/__init__.py delete mode 100644 backend/core/service/invoices/common/create/services/__init__.py delete mode 100644 backend/core/service/invoices/common/emails/__init__.py delete mode 100644 backend/core/service/invoices/recurring/__init__.py delete mode 100644 backend/core/service/invoices/recurring/create/__init__.py delete mode 100644 backend/core/service/invoices/recurring/generation/__init__.py delete mode 100644 backend/core/service/invoices/recurring/schedules/__init__.py delete mode 100644 backend/core/service/invoices/recurring/validate/__init__.py delete mode 100644 backend/core/service/invoices/recurring/webhooks/__init__.py delete mode 100644 backend/core/service/invoices/single/__init__.py delete mode 100644 backend/core/service/invoices/single/create/__init__.py delete mode 100644 backend/core/service/maintenance/__init__.py delete mode 100644 backend/core/service/maintenance/expire/__init__.py delete mode 100644 backend/core/service/maintenance/expire/run.py delete mode 100644 backend/core/service/permissions/__init__.py delete mode 100644 backend/core/service/permissions/scopes.py delete mode 100644 backend/core/service/reports/__init__.py delete mode 100644 backend/core/service/settings/__init__.py delete mode 100644 backend/core/service/settings/update.py delete mode 100644 backend/core/service/settings/view.py delete mode 100644 backend/core/service/teams/__init__.py delete mode 100644 backend/core/service/teams/create_user.py delete mode 100644 backend/core/service/teams/fetch.py delete mode 100644 backend/core/service/teams/permissions.py delete mode 100644 backend/core/service/webhooks/__init__.py delete mode 100644 backend/core/service/webhooks/auth.py delete mode 100644 backend/core/service/webhooks/get_url.py delete mode 100644 backend/core/signals/__init__.py delete mode 100644 backend/core/signals/migrations.py delete mode 100644 backend/core/signals/signals.py delete mode 100644 backend/core/types/__init__.py delete mode 100644 backend/core/types/emails.py delete mode 100644 backend/core/types/htmx.py delete mode 100644 backend/core/types/requests.py delete mode 100644 backend/core/utils/__init__.py delete mode 100644 backend/core/utils/calendar.py delete mode 100644 backend/core/utils/dataclasses.py delete mode 100644 backend/core/utils/feature_flags.py delete mode 100644 backend/core/utils/http_utils.py delete mode 100644 backend/core/utils/quota_limit_ops.py delete mode 100644 backend/core/utils/service_retry.py delete mode 100644 backend/core/views/__init__.py delete mode 100644 backend/core/views/auth/__init__.py delete mode 100644 backend/core/views/auth/create_account.py delete mode 100644 backend/core/views/auth/helpers.py delete mode 100644 backend/core/views/auth/login.py delete mode 100644 backend/core/views/auth/passwords/__init__.py delete mode 100644 backend/core/views/auth/passwords/generate.py delete mode 100644 backend/core/views/auth/passwords/set.py delete mode 100644 backend/core/views/auth/passwords/view.py delete mode 100644 backend/core/views/auth/urls.py delete mode 100644 backend/core/views/auth/verify.py delete mode 100644 backend/core/views/emails/__init__.py delete mode 100644 backend/core/views/emails/dashboard.py delete mode 100644 backend/core/views/emails/urls.py delete mode 100644 backend/core/views/other/__init__.py delete mode 100644 backend/core/views/other/errors.py delete mode 100644 backend/core/views/other/index.py delete mode 100644 backend/core/views/quotas/__init__.py delete mode 100644 backend/core/views/quotas/view.py delete mode 100644 backend/core/views/settings/__init__.py delete mode 100644 backend/core/views/settings/teams.py delete mode 100644 backend/core/views/settings/urls.py delete mode 100644 backend/core/views/settings/view.py delete mode 100644 backend/core/views/teams/__init__.py delete mode 100644 backend/core/views/teams/urls.py delete mode 100644 backend/core/webhooks/__init__.py delete mode 100644 backend/core/webhooks/invoices/__init__.py rename backend/{core/api/emails => data}/__init__.py (100%) rename backend/{core => }/data/default_email_templates.py (100%) rename backend/{core/api/healthcheck => finance/service}/__init__.py (100%) rename backend/{core/api/landing_page => finance/service/clients}/__init__.py (100%) rename backend/{core => finance}/service/clients/create.py (90%) rename backend/{core => finance}/service/clients/delete.py (88%) rename backend/{core => finance}/service/clients/get.py (91%) rename backend/{core => finance}/service/clients/validate.py (100%) rename backend/{core/api/maintenance => finance/service/defaults}/__init__.py (100%) rename backend/{core => finance}/service/defaults/get.py (100%) rename backend/{core => finance}/service/defaults/update.py (97%) rename backend/{core/api/public/endpoints/Invoices => finance/service/invoices}/__init__.py (100%) rename backend/{core/api/public/endpoints => finance/service/invoices/common}/__init__.py (100%) rename backend/{core/api/public/endpoints/clients => finance/service/invoices/common/create}/__init__.py (100%) rename backend/{core => finance}/service/invoices/common/create/create.py (92%) rename backend/{core => finance}/service/invoices/common/create/get_page.py (92%) rename backend/{core/api/public/endpoints/webhooks => finance/service/invoices/common/create/services}/__init__.py (100%) rename backend/{core => finance}/service/invoices/common/create/services/add.py (94%) rename backend/{core/api/public/helpers => finance/service/invoices/common/emails}/__init__.py (100%) rename backend/{core => finance}/service/invoices/common/emails/on_create.py (87%) rename backend/{core => finance}/service/invoices/common/fetch.py (100%) rename backend/{core => finance}/service/invoices/handler.py (100%) rename backend/{core/api/public/serializers => finance/service/invoices/recurring}/__init__.py (100%) rename backend/{core/api/quotas => finance/service/invoices/recurring/create}/__init__.py (100%) rename backend/{core => finance}/service/invoices/recurring/create/get_page.py (87%) rename backend/{core => finance}/service/invoices/recurring/create/save.py (85%) rename backend/{core/api/settings => finance/service/invoices/recurring/generation}/__init__.py (100%) rename backend/{core => finance}/service/invoices/recurring/generation/next_invoice.py (96%) rename backend/{core => finance}/service/invoices/recurring/get.py (88%) rename backend/{core/api/teams => finance/service/invoices/recurring/schedules}/__init__.py (100%) rename backend/{core => finance}/service/invoices/recurring/schedules/date_handlers.py (98%) rename backend/{core/data => finance/service/invoices/recurring/validate}/__init__.py (100%) rename backend/{core => finance}/service/invoices/recurring/validate/frequencies.py (97%) rename backend/{core/management => finance/service/invoices/recurring/webhooks}/__init__.py (100%) rename backend/{core => finance}/service/invoices/recurring/webhooks/webhook_apikey_auth.py (86%) rename backend/{core/management/commands => finance/service/invoices/single}/__init__.py (100%) rename backend/{core/management/scheduled_tasks => finance/service/invoices/single/create}/__init__.py (100%) rename backend/{core => finance}/service/invoices/single/create/create.py (89%) rename backend/{core => finance}/service/invoices/single/create/get_page.py (81%) rename backend/{core => finance}/service/invoices/single/create_pdf.py (100%) rename backend/{core => finance}/service/invoices/single/create_url.py (80%) rename backend/{core => finance}/service/invoices/single/get_invoice.py (91%) rename backend/{core/service/api_keys => finance/service/reports}/__init__.py (100%) rename backend/{core => finance}/service/reports/generate.py (96%) rename backend/{core => finance}/service/reports/get.py (87%) create mode 100644 backend/storage/models.py rename backend/{core/service/asyn_tasks => storage/service}/__init__.py (100%) rename backend/{core/service/file_storage => storage/service}/create.py (95%) rename backend/{core/service/file_storage => storage/service}/utils.py (100%) delete mode 100644 backend/templatetags/cal_filters.py delete mode 100644 backend/templatetags/dictfilters.py delete mode 100644 backend/templatetags/feature_enabled.py delete mode 100644 backend/templatetags/listfilters.py delete mode 100644 backend/templatetags/strfilters.py delete mode 100644 backend/templatetags/utils.py rename backend/{core/service/base => webhooks}/__init__.py (100%) rename backend/{core/service/boto3 => webhooks/invoices}/__init__.py (100%) rename backend/{core => }/webhooks/invoices/invoice_status.py (87%) rename backend/{core => }/webhooks/invoices/recurring.py (88%) rename backend/{core => }/webhooks/urls.py (67%) delete mode 100644 billing/__init__.py delete mode 100644 billing/admin.py delete mode 100644 billing/apps.py delete mode 100644 billing/billing_settings.py delete mode 100644 billing/data/__init__.py delete mode 100644 billing/data/default_usage_plans.py delete mode 100644 billing/decorators.py delete mode 100644 billing/management/__init__.py delete mode 100644 billing/management/commands/__init__.py delete mode 100644 billing/management/commands/stripe.py delete mode 100644 billing/middleware.py delete mode 100644 billing/migrations/0001_initial.py delete mode 100644 billing/migrations/0002_subscriptionplan_stripe_price_id.py delete mode 100644 billing/migrations/0003_stripewebhookevent_usersubscription_uuid_and_more.py delete mode 100644 billing/migrations/0004_auto_20240830_1655.py delete mode 100644 billing/migrations/0005_auto_20240830_1655.py delete mode 100644 billing/migrations/0006_billingusage.py delete mode 100644 billing/migrations/__init__.py delete mode 100644 billing/models.py delete mode 100644 billing/service/__init__.py delete mode 100644 billing/service/checkout_completed.py delete mode 100644 billing/service/entitlements.py delete mode 100644 billing/service/get_user.py delete mode 100644 billing/service/plan_change.py delete mode 100644 billing/service/price.py delete mode 100644 billing/service/stripe_customer.py delete mode 100644 billing/service/subscription_ended.py delete mode 100644 billing/service/subscription_handler.py delete mode 100644 billing/service/test.py delete mode 100644 billing/signals/__init__.py delete mode 100644 billing/signals/migrations.py delete mode 100644 billing/signals/quotas.py delete mode 100644 billing/signals/stripe/__init__.py delete mode 100644 billing/signals/stripe/webhook_handler.py delete mode 100644 billing/signals/usage.py delete mode 100644 billing/templates/pages/billing/dashboard/all_subscriptions.html delete mode 100644 billing/templates/pages/billing/dashboard/choose_plan_section.html delete mode 100644 billing/templates/pages/billing/dashboard/dashboard.html delete mode 100644 billing/templates/pages/billing/dashboard/growth_usages.html delete mode 100644 billing/templates/pages/billing/dashboard/starter_usages.html delete mode 100644 billing/urls.py delete mode 100644 billing/views.py delete mode 100644 billing/views/__init__.py delete mode 100644 billing/views/change_plan.py delete mode 100644 billing/views/dashboard.py delete mode 100644 billing/views/return_urls/failed.py delete mode 100644 billing/views/return_urls/success.py delete mode 100644 billing/views/stripe_misc.py delete mode 100644 billing/views/stripe_webhooks.py rename {backend/core/service/boto3/scheduler => settings}/__init__.py (100%) delete mode 100644 tests/views/test_other.py diff --git a/.gitignore b/.gitignore index f3647efb0..3c089d5c2 100644 --- a/.gitignore +++ b/.gitignore @@ -166,4 +166,4 @@ Pulumi.*.yaml.bak # Closed Source features -./billing +../core/billing diff --git a/backend/admin.py b/backend/admin.py index f60925484..9b348839c 100644 --- a/backend/admin.py +++ b/backend/admin.py @@ -1,29 +1,5 @@ from django.contrib import admin -from django.contrib.auth.admin import UserAdmin - -from backend.core.api.public import APIAuthToken -from backend.core.models import ( - PasswordSecret, - AuditLog, - LoginLog, - Error, - TracebackError, - UserSettings, - Notification, - Organization, - TeamInvitation, - TeamMemberPermission, - User, - FeatureFlags, - VerificationCodes, - QuotaLimit, - QuotaOverrides, - QuotaUsage, - QuotaIncreaseRequest, - EmailSendStatus, - FileStorageFile, - MultiFileUpload, -) + from backend.finance.models import ( Invoice, InvoiceURL, @@ -37,45 +13,23 @@ from backend.clients.models import Client, DefaultValues -from settings.settings import BILLING_ENABLED - # from django.contrib.auth.models imp/ort User # admin.register(Invoice) admin.site.register( [ - UserSettings, Client, Invoice, InvoiceURL, InvoiceItem, - PasswordSecret, - AuditLog, - LoginLog, - Error, - TracebackError, - Notification, - Organization, - TeamInvitation, - TeamMemberPermission, InvoiceProduct, - FeatureFlags, - VerificationCodes, Receipt, ReceiptDownloadToken, InvoiceReminder, - APIAuthToken, InvoiceRecurringProfile, - FileStorageFile, - MultiFileUpload, DefaultValues, ] ) -if BILLING_ENABLED: - from billing.models import PlanFeature, PlanFeatureGroup, SubscriptionPlan, UserSubscription - - admin.site.register([PlanFeature, PlanFeatureGroup, SubscriptionPlan, UserSubscription]) - class QuotaLimitAdmin(admin.ModelAdmin): readonly_fields = ["name", "slug"] @@ -96,38 +50,14 @@ def get_queryset(self, request): return super().get_queryset(request).select_related("quota_limit", "user") -class EmailSendStatusAdmin(admin.ModelAdmin): - readonly_fields = ["aws_message_id"] - - class InvoiceURLAdmin(admin.ModelAdmin): readonly_fields = ["expires"] -admin.site.register(QuotaLimit, QuotaLimitAdmin) -admin.site.register(QuotaUsage, QuotaUsageAdmin) -admin.site.register(QuotaOverrides, QuotaOverridesAdmin) -admin.site.register(QuotaIncreaseRequest, QuotaIncreaseRequestAdmin) -admin.site.register(EmailSendStatus, EmailSendStatusAdmin) - -# admin.site.unregister(User) -fields = list(UserAdmin.fieldsets) # type: ignore[arg-type] -fields[0] = ( - None, - { - "fields": ( - "username", - "password", - "logged_in_as_team", - "awaiting_email_verification", - "stripe_customer_id", - "entitlements", - "require_change_password", - ) - }, -) -UserAdmin.fieldsets = tuple(fields) -admin.site.register(User, UserAdmin) +# admin.site.register(QuotaLimit, QuotaLimitAdmin) +# admin.site.register(QuotaUsage, QuotaUsageAdmin) +# admin.site.register(QuotaOverrides, QuotaOverridesAdmin) +# admin.site.register(QuotaIncreaseRequest, QuotaIncreaseRequestAdmin) admin.site.site_header = "MyFinances Admin" admin.site.index_title = "MyFinances" diff --git a/backend/api/urls.py b/backend/api/urls.py new file mode 100644 index 000000000..d6bd0a347 --- /dev/null +++ b/backend/api/urls.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from django.urls import include +from django.urls import path + +urlpatterns = [ + path("finance/", include("backend.finance.api.urls")), +] + +app_name = "api" diff --git a/backend/apps.py b/backend/apps.py index 7af63d87f..3ba516cc5 100644 --- a/backend/apps.py +++ b/backend/apps.py @@ -6,7 +6,6 @@ class BackendConfig(AppConfig): def ready(self): from .finance import signals - from .core import signals # from .clients import signals # from .storage import signals diff --git a/backend/core/__init__.py b/backend/boto3/__init__.py similarity index 100% rename from backend/core/__init__.py rename to backend/boto3/__init__.py diff --git a/backend/core/api/__init__.py b/backend/boto3/async_tasks/__init__.py similarity index 100% rename from backend/core/api/__init__.py rename to backend/boto3/async_tasks/__init__.py diff --git a/backend/core/service/asyn_tasks/tasks.py b/backend/boto3/async_tasks/tasks.py similarity index 100% rename from backend/core/service/asyn_tasks/tasks.py rename to backend/boto3/async_tasks/tasks.py diff --git a/backend/core/service/boto3/handler.py b/backend/boto3/handler.py similarity index 100% rename from backend/core/service/boto3/handler.py rename to backend/boto3/handler.py diff --git a/backend/core/api/base/__init__.py b/backend/boto3/scheduler/__init__.py similarity index 100% rename from backend/core/api/base/__init__.py rename to backend/boto3/scheduler/__init__.py diff --git a/backend/core/service/boto3/scheduler/create_schedule.py b/backend/boto3/scheduler/create_schedule.py similarity index 95% rename from backend/core/service/boto3/scheduler/create_schedule.py rename to backend/boto3/scheduler/create_schedule.py index 2ce7aa153..33ff32503 100644 --- a/backend/core/service/boto3/scheduler/create_schedule.py +++ b/backend/boto3/scheduler/create_schedule.py @@ -6,8 +6,8 @@ from django.urls import reverse from backend.finance.models import InvoiceRecurringProfile -from backend.core.service.boto3.handler import BOTO3_HANDLER -from backend.core.service.invoices.recurring.schedules.date_handlers import get_schedule_cron, CronServiceResponse +from backend.boto3.handler import BOTO3_HANDLER +from backend.finance.service.invoices.recurring.schedules.date_handlers import get_schedule_cron, CronServiceResponse from settings.helpers import get_var logger = logging.getLogger(__name__) diff --git a/backend/core/service/boto3/scheduler/delete_schedule.py b/backend/boto3/scheduler/delete_schedule.py similarity index 83% rename from backend/core/service/boto3/scheduler/delete_schedule.py rename to backend/boto3/scheduler/delete_schedule.py index 5e232f6ab..3bc640a71 100644 --- a/backend/core/service/boto3/scheduler/delete_schedule.py +++ b/backend/boto3/scheduler/delete_schedule.py @@ -2,16 +2,12 @@ import json import logging from typing import Type -from uuid import uuid4 from django.apps import apps from django.core.exceptions import ObjectDoesNotExist -from django.urls import reverse -from backend.finance.models import InvoiceRecurringProfile, BotoSchedule, InvoiceReminder -from backend.core.service.boto3.handler import BOTO3_HANDLER -from backend.core.service.invoices.recurring.schedules.date_handlers import get_schedule_cron, CronServiceResponse -from settings.helpers import get_var +from backend.finance.models import BotoSchedule +from backend.boto3.handler import BOTO3_HANDLER logger = logging.getLogger(__name__) diff --git a/backend/core/service/boto3/scheduler/get.py b/backend/boto3/scheduler/get.py similarity index 89% rename from backend/core/service/boto3/scheduler/get.py rename to backend/boto3/scheduler/get.py index 446d4b5e7..b7961b650 100644 --- a/backend/core/service/boto3/scheduler/get.py +++ b/backend/boto3/scheduler/get.py @@ -2,8 +2,8 @@ from mypy_boto3_scheduler.type_defs import GetScheduleOutputTypeDef -from backend.core.service.boto3.handler import BOTO3_HANDLER -from backend.core.utils.dataclasses import BaseServiceResponse +from backend.boto3.handler import BOTO3_HANDLER +from core.utils.dataclasses import BaseServiceResponse logger = logging.getLogger(__name__) diff --git a/backend/core/service/boto3/scheduler/pause.py b/backend/boto3/scheduler/pause.py similarity index 87% rename from backend/core/service/boto3/scheduler/pause.py rename to backend/boto3/scheduler/pause.py index b60c2fb57..b90e40865 100644 --- a/backend/core/service/boto3/scheduler/pause.py +++ b/backend/boto3/scheduler/pause.py @@ -2,9 +2,9 @@ from mypy_boto3_scheduler.type_defs import UpdateScheduleOutputTypeDef -from backend.core.service.boto3.handler import BOTO3_HANDLER -from backend.core.service.boto3.scheduler.get import get_boto_schedule -from backend.core.utils.dataclasses import BaseServiceResponse +from backend.boto3.handler import BOTO3_HANDLER +from backend.boto3.scheduler.get import get_boto_schedule +from core.utils.dataclasses import BaseServiceResponse logger = logging.getLogger(__name__) diff --git a/backend/core/service/boto3/scheduler/update_schedule.py b/backend/boto3/scheduler/update_schedule.py similarity index 95% rename from backend/core/service/boto3/scheduler/update_schedule.py rename to backend/boto3/scheduler/update_schedule.py index fe0a8e8bb..c87acbc89 100644 --- a/backend/core/service/boto3/scheduler/update_schedule.py +++ b/backend/boto3/scheduler/update_schedule.py @@ -3,9 +3,9 @@ from uuid import UUID from backend.finance.models import InvoiceRecurringProfile -from backend.core.service.boto3.handler import BOTO3_HANDLER -from backend.core.service.boto3.scheduler.create_schedule import create_boto_schedule -from backend.core.service.boto3.scheduler.get import get_boto_schedule +from backend.boto3.handler import BOTO3_HANDLER +from backend.boto3.scheduler.create_schedule import create_boto_schedule +from backend.boto3.scheduler.get import get_boto_schedule from backend.core.service.invoices.recurring.schedules.date_handlers import get_schedule_cron, CronServiceResponse logger = logging.getLogger(__name__) diff --git a/backend/clients/api/delete.py b/backend/clients/api/delete.py index 2dc5ee734..a0416f220 100644 --- a/backend/clients/api/delete.py +++ b/backend/clients/api/delete.py @@ -3,8 +3,8 @@ from django.views.decorators.http import require_http_methods from backend.decorators import web_require_scopes -from backend.core.service.clients.delete import delete_client, DeleteClientServiceResponse -from backend.core.types.requests import WebRequest +from backend.finance.service.clients.delete import delete_client, DeleteClientServiceResponse +from core.types.requests import WebRequest @require_http_methods(["DELETE"]) diff --git a/backend/clients/api/fetch.py b/backend/clients/api/fetch.py index abf4a7435..bbbac23ed 100644 --- a/backend/clients/api/fetch.py +++ b/backend/clients/api/fetch.py @@ -3,9 +3,9 @@ from backend.decorators import web_require_scopes from backend.clients.models import Client -from backend.core.service.clients.get import fetch_clients, FetchClientServiceResponse -from backend.core.types.htmx import HtmxHttpRequest -from backend.core.types.requests import WebRequest +from backend.finance.service.clients.get import fetch_clients, FetchClientServiceResponse +from core.types.htmx import HtmxHttpRequest +from core.types.requests import WebRequest @require_http_methods(["GET"]) diff --git a/backend/clients/models.py b/backend/clients/models.py index fad6da28f..19e5dabbd 100644 --- a/backend/clients/models.py +++ b/backend/clients/models.py @@ -2,12 +2,12 @@ from datetime import date, timedelta from django.db import models -from backend.core.data.default_email_templates import ( +from backend.data.default_email_templates import ( recurring_invoices_invoice_created_default_email_template, recurring_invoices_invoice_overdue_default_email_template, recurring_invoices_invoice_cancelled_default_email_template, ) -from backend.core.models import OwnerBase, User, UserSettings, _private_storage +from backend.models import OwnerBase, User, UserSettings, _private_storage class Client(OwnerBase): diff --git a/backend/clients/views/create.py b/backend/clients/views/create.py index 97674d4a8..b9caf81e9 100644 --- a/backend/clients/views/create.py +++ b/backend/clients/views/create.py @@ -2,8 +2,8 @@ from django.shortcuts import render, redirect from backend.decorators import web_require_scopes -from backend.core.service.clients.create import create_client, CreateClientServiceResponse -from backend.core.types.requests import WebRequest +from backend.finance.service.clients.create import create_client, CreateClientServiceResponse +from core.types.requests import WebRequest @web_require_scopes("clients:write", False, False, "clients:dashboard") diff --git a/backend/clients/views/dashboard.py b/backend/clients/views/dashboard.py index 362dde0ab..eddaaa869 100644 --- a/backend/clients/views/dashboard.py +++ b/backend/clients/views/dashboard.py @@ -1,7 +1,7 @@ from django.shortcuts import render from backend.decorators import web_require_scopes -from backend.core.types.htmx import HtmxHttpRequest +from core.types.htmx import HtmxHttpRequest @web_require_scopes("clients:read", False, False, "dashboard") diff --git a/backend/clients/views/detail.py b/backend/clients/views/detail.py index e120442d9..70e62fafd 100644 --- a/backend/clients/views/detail.py +++ b/backend/clients/views/detail.py @@ -5,9 +5,9 @@ from django.views.decorators.http import require_http_methods from backend.decorators import web_require_scopes -from backend.core.service.clients.delete import delete_client, DeleteClientServiceResponse -from backend.core.service.clients.validate import validate_client -from backend.core.types.requests import WebRequest +from backend.finance.service.clients.delete import delete_client, DeleteClientServiceResponse +from backend.finance.service.clients.validate import validate_client +from core.types.requests import WebRequest from backend.clients.models import Client diff --git a/backend/context_processors.py b/backend/context_processors.py index ddf02044f..5ba9e8f12 100644 --- a/backend/context_processors.py +++ b/backend/context_processors.py @@ -1,16 +1,15 @@ from typing import List, Optional, Dict, Any +from core.service.base.breadcrumbs import get_breadcrumbs from django.http import HttpRequest from django.urls import reverse import calendar -from backend.core.service.base.breadcrumbs import get_breadcrumbs - -from settings.helpers import get_var +from settings.helpers import get_var, BASE_DIR from backend import __version__ -from settings.settings import BASE_DIR, DEBUG +from settings.settings import DEBUG ## Context processors need to be put in SETTINGS TEMPLATES to be recognized @@ -64,7 +63,7 @@ def get_git_revision(base_path): data["day_names_monday_first"] = [day for day in calendar.day_name] if hasattr(request, "htmx") and request.htmx.boosted: - data["base"] = "core/htmx.html" + data["base"] = "core/base/htmx.html" return data diff --git a/backend/core/api/base/breadcrumbs.py b/backend/core/api/base/breadcrumbs.py deleted file mode 100644 index 7dfbdbfee..000000000 --- a/backend/core/api/base/breadcrumbs.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.shortcuts import render - -from backend.core.types.requests import WebRequest -from backend.core.service.base.breadcrumbs import get_breadcrumbs - - -def update_breadcrumbs_endpoint(request: WebRequest): - url = request.GET.get("url") - - breadcrumb_dict: dict = get_breadcrumbs(url=url) - return render( - request, - "base/breadcrumbs.html", - { - "breadcrumb": breadcrumb_dict.get("breadcrumb"), - "swapping": True, - # "swap": True - }, - ) diff --git a/backend/core/api/base/modal.py b/backend/core/api/base/modal.py deleted file mode 100644 index 0a6225ad9..000000000 --- a/backend/core/api/base/modal.py +++ /dev/null @@ -1,189 +0,0 @@ -from __future__ import annotations - -from django.contrib import messages -from django.http import HttpResponseBadRequest -from django.shortcuts import render - -from backend.core.api.public import APIAuthToken -from backend.core.api.public.permissions import SCOPE_DESCRIPTIONS - -from backend.clients.models import Client -from backend.finance.models import InvoiceURL, Invoice, Receipt -from backend.models import QuotaLimit, Organization, UserSettings -from backend.core.types.requests import WebRequest -from backend.core.utils.feature_flags import get_feature_status -from backend.core.service.defaults.get import get_account_defaults - - -def open_modal(request: WebRequest, modal_name, context_type=None, context_value=None): - try: - context = {} - template_name = f"modals/{modal_name}.html" - if context_type and context_value: - if context_type == "profile_picture": - try: - context["users_profile_picture"] = request.user.user_profile.profile_picture_url - except UserSettings.DoesNotExist: - pass - elif context_type == "accept_invite_with_code": - context["code"] = context_value - elif context_type == "leave_team": - if request.user.teams_joined.filter(id=context_value).exists(): - context["team"] = Organization.objects.filter(id=context_value).first() - elif context_type == "edit_receipt": - try: - receipt = Receipt.objects.get(pk=context_value) - except Receipt.DoesNotExist: - return render(request, template_name, context) - receipt_date = receipt.date.strftime("%Y-%m-%d") if receipt.date else "" - context = { - "modal_id": f"modal_{receipt.id}_receipts_upload", - "receipt_id": context_value, - "receipt_name": receipt.name, - "receipt_date": receipt_date, - "merchant_store_name": receipt.merchant_store, - "purchase_category": receipt.purchase_category, - "total_price": receipt.total_price, - "has_receipt_image": True if receipt.image else False, - "edit_flag": True, - } - elif context_type == "upload_receipt": - context["modal_id"] = f"modal_receipts_upload" - elif context_type == "edit_invoice_to": - invoice = context_value - try: - invoice = Invoice.filter_by_owner(request.actor).get(id=invoice) - except Invoice.DoesNotExist: - return render(request, template_name, context) - - if invoice.client_to: - context["to_name"] = invoice.client_to.name - context["to_company"] = invoice.client_to.company - context["to_email"] = invoice.client_to.email - context["to_address"] = invoice.client_to.address - context["existing_client_id"] = ( - invoice.client_to.id - ) # context["to_city"] = invoice.client_to.city # context["to_county"] = invoice.client_to.county # context["to_country"] = invoice.client_to.country - else: - context["to_name"] = invoice.client_name - context["to_company"] = invoice.client_company - context["to_email"] = invoice.client_email - context["is_representative"] = invoice.client_is_representative - context["to_address"] = ( - invoice.client_address - ) # context["to_city"] = invoice.client_city # context["to_county"] = invoice.client_county # context["to_country"] = invoice.client_country - elif context_type == "edit_invoice_from": - invoice = context_value - try: - invoice = Invoice.filter_by_owner(request.actor).get(id=invoice) - except Invoice.DoesNotExist: - return render(request, template_name, context) - - context["from_name"] = invoice.self_name - context["from_company"] = invoice.self_company - context["from_address"] = invoice.self_address - context["from_city"] = invoice.self_city - context["from_county"] = invoice.self_county - context["from_country"] = invoice.self_country - elif context_type == "create_invoice_from": - defaults = get_account_defaults(request.actor) - - context["from_name"] = getattr(defaults, f"invoice_from_name") - context["from_company"] = getattr(defaults, f"invoice_from_company") - context["from_address"] = getattr(defaults, f"invoice_from_address") - context["from_city"] = getattr(defaults, f"invoice_from_city") - context["from_county"] = getattr(defaults, f"invoice_from_county") - context["from_country"] = getattr(defaults, f"invoice_from_country") - elif context_type == "invoice": - try: - invoice = Invoice.objects.get(id=context_value) - if invoice.has_access(request.user): - context["invoice"] = invoice - except Invoice.DoesNotExist: - ... - elif context_type == "quota": - try: - quota = QuotaLimit.objects.prefetch_related("quota_overrides").get(slug=context_value) - context["quota"] = quota - context["current_limit"] = quota.get_quota_limit(user=request.user, quota_limit=quota) - usage = quota.strict_get_quotas(user=request.user, quota_limit=quota) - context["quota_usage"] = usage.count() if usage != "Not Available" else "Not available" - print(context["quota_usage"]) - except QuotaLimit.DoesNotExist: - ... - elif context_type == "invoice_reminder": - try: - invoice = ( - Invoice.objects.only("id", "client_email", "client_to__email").select_related("client_to").get(id=context_value) - ) - except Invoice.DoesNotExist: - return render(request, template_name, context) - - if invoice.has_access(request.user): - context["invoice"] = invoice - else: - messages.error(request, "You don't have access to this invoice") - return render(request, "base/toasts.html") - - # above_quota_usage = False # quota_usage_check_under(request, "invoices-schedules", api=True, htmx=True) - - # if not isinstance(above_quota_usage, bool): # context["above_quota_usage"] = True - - else: - context[context_type] = context_value - - if modal_name == "send_single_email" or modal_name == "send_bulk_email": - if not get_feature_status("areUserEmailsAllowed"): - messages.error(request, "Emails are disabled") - return render(request, "base/toast.html") - context["content_min_length"] = 64 - quota = QuotaLimit.objects.prefetch_related("quota_overrides").get(slug="emails-email_character_count") - context["content_max_length"] = quota.get_quota_limit(user=request.user, quota_limit=quota) - context["email_list"] = Client.filter_by_owner(owner=request.actor).filter(email__isnull=False).values_list("email", flat=True) - - if context_type == "invoice_code_send": - invoice_url: InvoiceURL | None = InvoiceURL.objects.filter(uuid=context_value).prefetch_related("invoice").first() - - if not invoice_url or not invoice_url.invoice.has_access(request.user): - messages.error(request, "You don't have access to this invoice") - return render(request, "base/toast.html", {"autohide": False}) - - context["invoice"] = invoice_url.invoice - context["selected_clients"] = [ - invoice_url.invoice.client_to.email if invoice_url.invoice.client_to else invoice_url.invoice.client_email - for value in [ - invoice_url.invoice.client_to.email if invoice_url.invoice.client_to else invoice_url.invoice.client_email - ] - if value is not None - ] - - context["email_list"] = list(context["email_list"]) + context["selected_clients"] - - elif modal_name == "invoices_to_destination": - if existing_client := request.GET.get("client"): - context["existing_client_id"] = existing_client - elif modal_name in ["generate_api_key", "edit_team_member_permissions", "team_create_user"]: - # example - # "clients": { - # "description": "Access customer details", - # "options": ["read", "write"] - # }, - context["permissions"] = [ - {"name": group, "description": perms["description"], "options": perms["options"]} - for group, perms in SCOPE_DESCRIPTIONS.items() - ] - context["APIAuthToken_types"] = APIAuthToken.AdministratorServiceTypes - - if modal_name == "edit_team_member_permissions": - team = request.user.logged_in_as_team - if team: - for_user = team.members.filter(id=context_value).first() - for_user_perms = team.permissions.filter(user=for_user).first() - if for_user: - context["editing_user"] = for_user - context["user_current_scopes"] = for_user_perms.scopes if for_user_perms else [] - - return render(request, template_name, context) - except ValueError as e: - print(f"Something went wrong with loading modal {modal_name}. Error: {e}") - return HttpResponseBadRequest("Something went wrong") diff --git a/backend/core/api/base/notifications.py b/backend/core/api/base/notifications.py deleted file mode 100644 index 9880b1210..000000000 --- a/backend/core/api/base/notifications.py +++ /dev/null @@ -1,41 +0,0 @@ -from django.contrib import messages -from django.http import HttpResponse -from django.shortcuts import render - -from backend.models import Notification -from backend.core.types.htmx import HtmxHttpRequest - - -def get_notification_html(request: HtmxHttpRequest): - user_notifications = Notification.objects.filter(user=request.user).order_by("-date") - count = user_notifications.count() - - if count > 5: - user_notifications = user_notifications[:5] - - return render( - request, - "base/topbar/_notification_dropdown_items.html", - {"notifications": user_notifications, "notif_count": count}, - ) - - -def get_notification_count_html(request: HtmxHttpRequest): - user_notifications = Notification.objects.filter(user=request.user).count() - return HttpResponse(f"{user_notifications}") - - -def delete_notification(request: HtmxHttpRequest, id: int): - notif = Notification.objects.filter(id=id, user=request.user).first() - - if notif is None or notif.user != request.user: - if request.htmx: - messages.error(request, "Notification not found") - return render(request, "base/toasts.html") - return HttpResponse(status=404, content="Notification not found") - - notif.delete() - - response = HttpResponse(status=200) - response["HX-Trigger"] = "refresh_notification_count" - return response diff --git a/backend/core/api/base/urls.py b/backend/core/api/base/urls.py deleted file mode 100644 index 539b0c64d..000000000 --- a/backend/core/api/base/urls.py +++ /dev/null @@ -1,29 +0,0 @@ -from django.urls import path -from . import modal, notifications, breadcrumbs - -urlpatterns = [ - path( - "modals//retrieve", - modal.open_modal, - name="modal retrieve", - ), - path( - "modals//retrieve//", - modal.open_modal, - name="modal retrieve with context", - ), - path( - "notifications/get", - notifications.get_notification_html, - name="notifications get", - ), - path("notifications/get_count", notifications.get_notification_count_html, name="notifications get count"), - path( - "notifications/delete/", - notifications.delete_notification, - name="notifications delete", - ), - path("breadcrumbs/refetch/", breadcrumbs.update_breadcrumbs_endpoint, name="breadcrumbs refetch"), -] - -app_name = "base" diff --git a/backend/core/api/emails/fetch.py b/backend/core/api/emails/fetch.py deleted file mode 100644 index 87c062c72..000000000 --- a/backend/core/api/emails/fetch.py +++ /dev/null @@ -1,43 +0,0 @@ -from django.core.paginator import Paginator, Page -from django.db.models import Q, QuerySet -from django.http import HttpResponse -from django.shortcuts import render, redirect -from django_ratelimit.core import is_ratelimited - -from backend.decorators import web_require_scopes -from backend.models import EmailSendStatus -from backend.core.types.htmx import HtmxHttpRequest - - -@web_require_scopes("emails:read", True, True) -def fetch_all_emails(request: HtmxHttpRequest): - if is_ratelimited(request, group="fetch_all_emails", key="user", rate="2/4s", increment=True) or is_ratelimited( - request, - group="fetch_all_emails", - key="user", - rate="5/10s", - increment=True or is_ratelimited(request, group="fetch_all_emails", key="user", rate="20/2m", increment=True), - ): - return HttpResponse(status=429) - context = {} - if not request.htmx: - return redirect("quotas") - - search_text = request.GET.get("search") - page_num = request.GET.get("page") - - if request.user.logged_in_as_team: - results: QuerySet[EmailSendStatus] = EmailSendStatus.objects.filter(organization=request.user.logged_in_as_team) - else: - results = EmailSendStatus.objects.filter(user=request.user) - - if search_text: - results = results.filter(Q(recipient__icontains=search_text)) - - result: Page | QuerySet = results.order_by("-id") - - paginator = Paginator(result, 8) - result = paginator.get_page(page_num) - - context.update({"emails": result}) - return render(request, "pages/emails/_fetch_body.html", context) diff --git a/backend/core/api/emails/send.py b/backend/core/api/emails/send.py deleted file mode 100644 index 317a59ad0..000000000 --- a/backend/core/api/emails/send.py +++ /dev/null @@ -1,374 +0,0 @@ -from __future__ import annotations - -import re -from dataclasses import dataclass - -from collections.abc import Iterator -from string import Template - -from django.contrib import messages -from django.core.exceptions import ValidationError -from django.core.validators import validate_email -from django.db.models import QuerySet -from django.http import HttpResponse -from django.shortcuts import render -from django.views.decorators.http import require_POST -from mypy_boto3_sesv2.type_defs import BulkEmailEntryResultTypeDef - -from backend.core.data.default_email_templates import email_footer -from backend.decorators import feature_flag_check, web_require_scopes -from backend.decorators import htmx_only -from backend.models import Client -from backend.models import EmailSendStatus -from backend.models import QuotaLimit -from backend.models import QuotaUsage -from backend.core.types.emails import ( - BulkEmailEmailItem, -) -from backend.core.types.requests import WebRequest - -from settings.helpers import send_email, send_templated_bulk_email, get_var -from backend.core.types.htmx import HtmxHttpRequest - - -@dataclass -class Ok: ... - - -@dataclass -class Invalid: - message: str - - -@require_POST -@htmx_only("emails:dashboard") -@feature_flag_check("areUserEmailsAllowed", status=True, api=True, htmx=True) -@web_require_scopes("emails:send", False, False, "emails:dashboard") -def send_single_email_view(request: WebRequest) -> HttpResponse: - # check_usage = False # quota_usage_check_under(request, "emails-single-count", api=True, htmx=True) - # if not isinstance(check_usage, bool): - # return check_usage - - return _send_single_email_view(request) - - -@require_POST -@htmx_only("emails:dashboard") -@feature_flag_check("areUserEmailsAllowed", status=True, api=True, htmx=True) -@web_require_scopes("emails:send", False, False, "emails:dashboard") -def send_bulk_email_view(request: WebRequest) -> HttpResponse: - # email_count = len(request.POST.getlist("emails")) - 1 - - # check_usage = quota_usage_check_under(request, "emails-single-count", add=email_count, api=True, htmx=True) - # if not isinstance(check_usage, bool): - # return check_usage - return _send_bulk_email_view(request) - - -def _send_bulk_email_view(request: WebRequest) -> HttpResponse: - emails: list[str] = request.POST.getlist("emails") - subject: str = request.POST.get("subject", "") - message: str = request.POST.get("content", "") - cc_yourself = True if request.POST.get("cc_yourself") else False - bcc_yourself = True if request.POST.get("bcc_yourself") else False - - if request.user.logged_in_as_team: - clients = Client.objects.filter(organization=request.user.logged_in_as_team, email__in=emails) - else: - clients = Client.objects.filter(user=request.user, email__in=emails) - - validated_bulk = validate_bulk_inputs(request=request, emails=emails, clients=clients, message=message, subject=subject) - - if validated_bulk: - messages.error(request, validated_bulk) - return render(request, "base/toast.html") - - message += email_footer() - message_single_line_html = message.replace("\r\n", "
").replace("\n", "
") - - email_list: list[BulkEmailEmailItem] = [] - - for email in emails: - client = clients.filter(email=email).first() - - email_data = { - "users_name": client.name.split()[0] if client else "User", - "first_name": client.name.split()[0] if client else "User", - "company_name": request.actor.name, - } # todo: add all variables from https://docs.myfinances.cloud/user-guide/emails/templates/ - - email_list.append( - BulkEmailEmailItem( - destination=email, - cc=[request.user.email] if cc_yourself else [], - bcc=[request.user.email] if bcc_yourself else [], - template_data={ - "users_name": client.name.split()[0] if client else "User", - "content_text": Template(message).substitute(email_data), - "content_html": Template(message_single_line_html).substitute(email_data), - }, - ) - ) - - if get_var("DEBUG", "").lower() == "true": - print( - { - "email_list": email_list, - "template_name": "user_send_client_email", - "default_template_data": { - "sender_name": request.user.first_name or request.user.email, - "sender_id": request.user.id, - "subject": subject, - }, - } - ) - messages.success(request, f"Successfully emailed {len(email_list)} people.") - return render(request, "base/toast.html") - - EMAIL_SENT = send_templated_bulk_email( - email_list=email_list, - template_name="user_send_client_email", - default_template_data={ - "sender_name": request.user.first_name or request.user.email, - "sender_id": request.user.id, - "subject": subject, - }, - ) - - if EMAIL_SENT.failed: - messages.error(request, EMAIL_SENT.error) - return render(request, "base/toast.html") - - # todo - fix - - EMAIL_RESPONSES: Iterator[tuple[BulkEmailEmailItem, BulkEmailEntryResultTypeDef]] = zip( - email_list, EMAIL_SENT.response.get("BulkEmailEntryResults") # type: ignore[arg-type] - ) - - if request.user.logged_in_as_team: - SEND_STATUS_OBJECTS: list[EmailSendStatus] = EmailSendStatus.objects.bulk_create( - [ - EmailSendStatus( - organization=request.user.logged_in_as_team, - sent_by=request.user, - recipient=response[0].destination, - aws_message_id=response[1].get("MessageId"), - status="pending", - ) - for response in EMAIL_RESPONSES - ] - ) - else: - SEND_STATUS_OBJECTS = EmailSendStatus.objects.bulk_create( - [ - EmailSendStatus( - user=request.user, - sent_by=request.user, - recipient=response[0].destination, - aws_message_id=response[1].get("MessageId"), - status="pending", - ) - for response in EMAIL_RESPONSES - ] - ) - - messages.success(request, f"Successfully emailed {len(email_list)} people.") - - try: - quota_limits = QuotaLimit.objects.filter(slug__in=["emails-single-count", "emails-bulk-count"]) - - QuotaUsage.objects.bulk_create( - [ - QuotaUsage(user=request.user, quota_limit=quota_limits.get(slug="emails-single-count"), extra_data=status.id) - for status in SEND_STATUS_OBJECTS - ] - + [QuotaUsage(user=request.user, quota_limit=quota_limits.get(slug="emails-bulk-count"))] - ) - except QuotaLimit.DoesNotExist: - ... - - return render(request, "base/toast.html") - - -def _send_single_email_view(request: WebRequest) -> HttpResponse: - email: str = str(request.POST.get("email", "")).strip() - subject: str = request.POST.get("subject", "") - message: str = request.POST.get("content", "") - - if request.user.logged_in_as_team: - client = Client.objects.filter(organization=request.user.logged_in_as_team, email=email).first() - else: - client = Client.objects.filter(user=request.user, email=email).first() - - validated_single = validate_single_inputs(request=request, email=email, client=client, message=message, subject=subject) - - if validated_single: - messages.error(request, validated_single) - return render(request, "base/toast.html") - - message += email_footer() - message_single_line_html = message.replace("\r\n", "
").replace("\n", "
") - - email_data = {"company_name": request.actor.name} - - EMAIL_SENT = send_email( - destination=email, - subject=subject, - content={ - "template_name": "user_send_client_email", - "template_data": { - "subject": subject, - "sender_name": request.user.first_name or request.user.email, - "sender_id": request.user.id, - "content_text": Template(message).substitute(email_data), - "content_html": Template(message_single_line_html).substitute(email_data), - }, - }, - ) - - aws_message_id = None - if EMAIL_SENT.response is not None: - aws_message_id = EMAIL_SENT.response.get("MessageId") - - status_object = EmailSendStatus(sent_by=request.user, recipient=email, aws_message_id=aws_message_id) - - if EMAIL_SENT.success: - messages.success(request, f"Successfully emailed {email}.") - status_object.status = "pending" - else: - status_object.status = "failed_to_send" - messages.error(request, f"Failed to send the email. Error: {EMAIL_SENT.error}") - - if request.user.logged_in_as_team: - status_object.organization = request.user.logged_in_as_team - else: - status_object.user = request.user - - status_object.save() - - QuotaUsage.create_str(request.user, "emails-single-count", status_object.id) - - return render(request, "base/toast.html") - - -def validate_bulk_inputs(*, request, emails, clients, message, subject) -> str | None: - def run_validations(): - yield validate_bulk_quotas(request=request, emails=emails) - yield validate_email_list(emails=emails) - # yield validate_client_list(clients=clients, emails=emails) - yield validate_email_content(message=message, request=request) - yield validate_email_subject(subject=subject) - - for validation in run_validations(): - if validation: - return validation - - return None - - -def validate_single_inputs(*, request, email, client, message, subject) -> str | None: - def run_validations(): - yield validate_client_email(email=email, client=client) - yield validate_client(client=client) - yield validate_email_content(message=message, request=request) - yield validate_email_subject(subject=subject) - - for validation in run_validations(): - if validation: - return validation - - return None - - -def validate_bulk_quotas(*, request: HtmxHttpRequest, emails: list) -> str | None: - email_count = len(emails) - - slugs = ["emails-bulk-count", "emails-bulk-max_sends"] - quota_limits: QuerySet[QuotaLimit] = QuotaLimit.objects.prefetch_related("quota_overrides", "quota_usage").filter(slug__in=slugs) - - # quota_limits.get(). - - above_bulk_sends_limit: bool = quota_limits.get(slug="emails-bulk-count").strict_goes_above_limit(request.user) - if above_bulk_sends_limit: - return "You have exceeded the quota limit for bulk email sends per month" - - max_email_count = quota_limits.get(slug="emails-bulk-max_sends").get_quota_limit(user=request.user) - - if email_count > max_email_count: - return "You have exceeded the quota limit for the number of emails allowed per bulk send" - else: - return None - - -def validate_client_email(email, client) -> str | None: - if not email: - return "No email provided" - - try: - validate_email(email) - except ValidationError: - return "Invalid email" - - if client.email != email: - return "Something went wrong when checking the email of the client" - - return None - - -def validate_client(client: Client) -> str | None: - if not client: - return "Could not find client object" - - # if not client.email_verified: - # return "The clients email has not yet been verified" - return None - - -def validate_email_list(emails: list[str]) -> str | None: - if not emails: - return "There was no emails provided" - - for email in emails: - try: - validate_email(email) - except ValidationError: - return f"The email {email} is invalid." - return None - - -def validate_client_list(clients: QuerySet[Client], emails: list[str]) -> str | None: - for email in emails: - if not clients.filter(email=email).exists(): - return f"Could not find client object for {email}" - return None - - -def validate_email_subject(subject: str) -> str | None: - min_count = 8 - max_count = 64 - - if len(subject) < min_count: - return "The minimum character count is 16 for a subject" - - if len(subject) > max_count: - return "The maximum character count is 64 characters for a subject" - - alpha_count = len(re.findall("[a-zA-Z ]", subject)) - non_alpha_count = len(subject) - alpha_count - - if non_alpha_count > 0 and alpha_count / non_alpha_count < 10: - return "The subject should have at least 10 letters per 'symbol'" - - return None - - -def validate_email_content(message: str, request: HtmxHttpRequest) -> str | None: - min_count = 64 - max_count = QuotaLimit.objects.get(slug="emails-email_character_count").get_quota_limit(user=request.user) - - if len(message) < min_count: - return "The minimum character count is 64 for an email" - - if len(message) > max_count: - return "The maximum character count is 1000 characters for an email" - return None diff --git a/backend/core/api/emails/status.py b/backend/core/api/emails/status.py deleted file mode 100644 index 8b6dd95e3..000000000 --- a/backend/core/api/emails/status.py +++ /dev/null @@ -1,100 +0,0 @@ -from logging import exception -from typing import TypedDict - -from django.contrib import messages -from django.http import HttpResponse -from django.shortcuts import render -from django.views.decorators.http import require_POST -from django_ratelimit.core import is_ratelimited -from mypy_boto3_sesv2.type_defs import GetMessageInsightsResponseTypeDef, InsightsEventTypeDef - -from backend.decorators import htmx_only, feature_flag_check, web_require_scopes -from backend.models import EmailSendStatus -from backend.core.types.htmx import HtmxHttpRequest -from settings.helpers import EMAIL_CLIENT - - -@require_POST -@htmx_only("emails:dashboard") -@feature_flag_check("areUserEmailsAllowed", status=True, api=True, htmx=True) -@web_require_scopes("emails:read", True, True) -def get_status_view(request: HtmxHttpRequest, status_id: str) -> HttpResponse: - try: - if request.user.logged_in_as_team: - EMAIL_STATUS = EmailSendStatus.objects.get(organization=request.user.logged_in_as_team, id=status_id) - else: - EMAIL_STATUS = EmailSendStatus.objects.get(user=request.user, id=status_id) - except EmailSendStatus.DoesNotExist: - messages.error(request, "Status not found") - return render(request, "base/toast.html") - - message_insight = get_message_insights(message_id=EMAIL_STATUS.aws_message_id) # type: ignore[arg-type] - - if isinstance(message_insight, str): - messages.error(request, message_insight) - return render(request, "base/toast.html", {"autohide": False}) - - important_info = get_important_info_from_response(message_insight) - - EMAIL_STATUS.status = important_info["status"] - EMAIL_STATUS.updated_status_at = important_info["most_recent_event"]["Timestamp"] - EMAIL_STATUS.save() - - messages.success(request, f"Status updated to {important_info['status']}") - return render(request, "base/toast.html", {"autohide": False}) - - -@require_POST -@htmx_only("emails:dashboard") -@feature_flag_check("areUserEmailsAllowed", status=True, api=True, htmx=True) -def refresh_all_statuses_view(request: HtmxHttpRequest) -> HttpResponse: - if is_ratelimited(request, group="email-refresh_all_statuses", key="user", rate="5/10m", increment=True) or is_ratelimited( - request, group="email-refresh_all_statuses", key="user", rate="1/m", increment=True - ): - messages.error(request, "Woah, slow down! Refreshing the statuses takes a while, give us a break!") - return render(request, "base/toast.html") - if request.user.logged_in_as_team: - ALL_STATUSES = EmailSendStatus.objects.filter(organization=request.user.logged_in_as_team) - else: - ALL_STATUSES = EmailSendStatus.objects.filter(user=request.user) - - for status in ALL_STATUSES: - response = get_message_insights(message_id=status.aws_message_id) # type: ignore[arg-type] - - if isinstance(response, str): - messages.error(request, response) - continue - - important_info = get_important_info_from_response(response) - - status.status = important_info["status"] - status.updated_status_at = important_info["most_recent_event"]["Timestamp"] - - ALL_STATUSES.bulk_update(ALL_STATUSES, fields=["status", "updated_status_at", "updated_at"]) - - messages.success(request, "All statuses have been refreshed") - http_response = HttpResponse(status=200) - http_response["HX-Refresh"] = "true" - return http_response - - -class ImportantInfo(TypedDict): - most_recent_event: InsightsEventTypeDef - status: str - - -def get_important_info_from_response(response: GetMessageInsightsResponseTypeDef) -> ImportantInfo: - return {"most_recent_event": (most_recent_event := response["Insights"][0]["Events"][0]), "status": most_recent_event["Type"].lower()} - - -def get_message_insights(message_id: str) -> GetMessageInsightsResponseTypeDef | str: - try: - response = EMAIL_CLIENT.get_message_insights(MessageId=message_id) - return response - except EMAIL_CLIENT.exceptions.NotFoundException: - return "A message was not found with this ID. Maybe wait for it to process" - except EMAIL_CLIENT.exceptions.BadRequestException: - return "Something went wrong when trying to fetch the email with this ID" - except Exception as err: - exception(err) - return "Something went wrong when trying to fetch the email with this ID" diff --git a/backend/core/api/emails/urls.py b/backend/core/api/emails/urls.py deleted file mode 100644 index 0f5217e70..000000000 --- a/backend/core/api/emails/urls.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -from django.urls import path - -from . import send, fetch, status - -urlpatterns = [ - path( - "send/single/", - send.send_single_email_view, - name="send single", - ), - path( - "send/bulk/", - send.send_bulk_email_view, - name="send bulk", - ), - path("fetch/", fetch.fetch_all_emails, name="fetch"), - path("get_status//", status.get_status_view, name="get_status"), - path("refresh_statuses/", status.refresh_all_statuses_view, name="refresh statuses"), -] - -app_name = "emails" diff --git a/backend/core/api/healthcheck/healthcheck.py b/backend/core/api/healthcheck/healthcheck.py deleted file mode 100644 index 4c1862d30..000000000 --- a/backend/core/api/healthcheck/healthcheck.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.db import connection, OperationalError -from django.http import HttpRequest, HttpResponse -from login_required import login_not_required - - -@login_not_required -def ping(request: HttpRequest) -> HttpResponse: - return HttpResponse("pong") - - -@login_not_required -def healthcheck(request: HttpRequest) -> HttpResponse: - try: - connection.ensure_connection() - return HttpResponse(status=200, content="All operations are up and running!") - except OperationalError: - return HttpResponse(status=503, content="Service Unavailable") diff --git a/backend/core/api/healthcheck/urls.py b/backend/core/api/healthcheck/urls.py deleted file mode 100644 index 7d271225f..000000000 --- a/backend/core/api/healthcheck/urls.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.urls import path - -from . import healthcheck - -urlpatterns = [ - path( - "ping/", - healthcheck.ping, - name="ping", - ), - path( - "healthcheck/", - healthcheck.healthcheck, - name="healthcheck", - ), -] - -app_name = "healthcheck" diff --git a/backend/core/api/landing_page/email_waitlist.py b/backend/core/api/landing_page/email_waitlist.py deleted file mode 100644 index 25fedcd4b..000000000 --- a/backend/core/api/landing_page/email_waitlist.py +++ /dev/null @@ -1,49 +0,0 @@ -from textwrap import dedent - -from login_required import login_not_required - -from backend.core.service import BOTO3_HANDLER -from backend.core.types.requests import WebRequest - -from django.http import HttpResponse - -from settings.helpers import send_email - - -@login_not_required -def join_waitlist_endpoint(request: WebRequest): - email_address = request.POST.get("email", "") - name = request.POST.get("name", "") - - if not email_address: - return HttpResponse(status=400) - - if not BOTO3_HANDLER.initiated: - return HttpResponse(status=500) - - BOTO3_HANDLER.dynamodb_client.put_item(TableName="myfinances-emails", Item={"email": {"S": email_address}, "name": {"S": name}}) - - content = """ -
- Successfully registered! Expect some discounts and updates as we progress in our journey :) -
- """ - - send_email( - destination=email_address, - subject="Welcome aboard", - content=dedent( - f""" - Thank you for joining our waitlist! - - We're excited to have you on board and will be in touch with more updates as we progress in our journey. - - Stay tuned for discounts, updates and personal direct emails from our founder! - - Best regards, - The MyFinances Team - """ - ).strip(), - ) - - return HttpResponse(status=200, content=dedent(content).strip()) diff --git a/backend/core/api/landing_page/urls.py b/backend/core/api/landing_page/urls.py deleted file mode 100644 index 309fdf768..000000000 --- a/backend/core/api/landing_page/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.urls import path -from . import email_waitlist - -urlpatterns = [ - path("join_waitlist/", email_waitlist.join_waitlist_endpoint, name="join_waitlist"), -] - -app_name = "landing_page" diff --git a/backend/core/api/maintenance/now.py b/backend/core/api/maintenance/now.py deleted file mode 100644 index 11777c2d0..000000000 --- a/backend/core/api/maintenance/now.py +++ /dev/null @@ -1,30 +0,0 @@ -from django.http import JsonResponse -from django.views.decorators.csrf import csrf_exempt -from django.views.decorators.http import require_POST -from login_required import login_not_required - -from backend.core.service.invoices.recurring.webhooks.webhook_apikey_auth import authenticate_api_key - -from backend.core.service.maintenance.expire.run import expire_and_cleanup_objects - -import logging - -from backend.core.types.requests import WebRequest - -logger = logging.getLogger(__name__) - - -@require_POST -@csrf_exempt -@login_not_required -def handle_maintenance_now_endpoint(request: WebRequest): - logger.info("Received routine cleanup handler. Now authenticating...") - api_auth_response = authenticate_api_key(request) - - if api_auth_response.failed: - logger.info(f"Maintenance auth failed: {api_auth_response.error}") - return JsonResponse({"message": api_auth_response.error, "success": False}, status=api_auth_response.status_code or 400) - - cleanup_str = expire_and_cleanup_objects() - logger.info(cleanup_str) - return JsonResponse({"message": cleanup_str, "success": True}, status=200) diff --git a/backend/core/api/maintenance/urls.py b/backend/core/api/maintenance/urls.py deleted file mode 100644 index c1b4d2e98..000000000 --- a/backend/core/api/maintenance/urls.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.urls import path - -from . import now - -urlpatterns = [ - path("cleanup/", now.handle_maintenance_now_endpoint, name="cleanup"), -] - -app_name = "maintenance" diff --git a/backend/core/api/public/__init__.py b/backend/core/api/public/__init__.py deleted file mode 100644 index 496d78000..000000000 --- a/backend/core/api/public/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .models import APIAuthToken diff --git a/backend/core/api/public/authentication.py b/backend/core/api/public/authentication.py deleted file mode 100644 index 77b52d59d..000000000 --- a/backend/core/api/public/authentication.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import Type - -from rest_framework.authentication import TokenAuthentication, get_authorization_header -from rest_framework.exceptions import AuthenticationFailed -from django.utils.translation import gettext_lazy as _ -from backend.core.api.public.models import APIAuthToken -from backend.models import User, Organization - -from rest_framework import exceptions - - -class CustomBearerAuthentication(TokenAuthentication): - keyword = "Bearer" - - def get_model(self) -> Type[APIAuthToken]: - return APIAuthToken - - def authenticate(self, request): - auth = get_authorization_header(request).split() - - if not auth or auth[0].lower() != self.keyword.lower().encode(): - return None - - if len(auth) == 1: - msg = _("Invalid token header. No credentials provided.") - raise exceptions.AuthenticationFailed(msg) - elif len(auth) > 2: - msg = _("Invalid token header. Token string should not contain spaces.") - raise exceptions.AuthenticationFailed(msg) - - try: - token = auth[1].decode() - except UnicodeError: - msg = _("Invalid token header. Token string should not contain invalid characters.") - raise exceptions.AuthenticationFailed(msg) - - user_or_org, token = self.authenticate_credentials(token) - - request.actor = user_or_org - - if isinstance(user_or_org, Organization): - request.team = user_or_org - request.team_id = user_or_org.id - else: - request.team = None - request.team_id = None - - return (user_or_org, token) - - def authenticate_credentials(self, raw_key) -> tuple[User | Organization | None, APIAuthToken]: - model = self.get_model() - - try: - token = model.objects.get(hashed_key=model.hash_raw_key(raw_key), active=True) - except model.DoesNotExist: - raise AuthenticationFailed(_("Invalid token.")) - - if token.has_expired: - raise AuthenticationFailed(_("Token has expired.")) - - user_or_org = token.user or token.organization - - if user_or_org is None: - raise AuthenticationFailed(_("Associated user or organization not found.")) - - return user_or_org, token diff --git a/backend/core/api/public/decorators.py b/backend/core/api/public/decorators.py deleted file mode 100644 index 71360540c..000000000 --- a/backend/core/api/public/decorators.py +++ /dev/null @@ -1,47 +0,0 @@ -from functools import wraps - -from rest_framework.exceptions import PermissionDenied -from rest_framework.generics import get_object_or_404 -from rest_framework import status - -from backend.models import TeamMemberPermission, Organization, Client -from backend.core.api.public.helpers.response import APIResponse - -import logging - -logger = logging.getLogger(__name__) - - -def require_scopes(scopes): - def decorator(view_func): - @wraps(view_func) - def _wrapped_view(request, *args, **kwargs): - token = request.auth - if not token: - logger.info( - f"Authentication credentials were not provided in api request |" f" {request.META.get('REMOTE_ADDR', 'Unknown IP')}" - ) - return APIResponse(False, {"detail": "Authentication credentials were not provided."}, status=status.HTTP_401_UNAUTHORIZED) - - if request.team_id and not request.team: - return APIResponse(False, {"detail": "Team not found."}, status=status.HTTP_404_NOT_FOUND) - - if request.team: - # Check for team permissions based on team_id and scopes - if not request.team.is_owner(token.user) and not request.team.is_logged_in_as_team(request): - team_permissions = TeamMemberPermission.objects.filter(team=request.team, user=token.user).first() - if not team_permissions or not all(scope in team_permissions.scopes for scope in scopes): - return APIResponse(False, {"detail": "Permission denied."}, status=status.HTTP_403_FORBIDDEN) - - # Check for global API Key permissions based on token scopes - if not all(scope in token.scopes for scope in scopes): - return APIResponse(False, {"detail": "Permission denied."}, status=status.HTTP_403_FORBIDDEN) - - token.update_last_used() - - return view_func(request, *args, **kwargs) - - _wrapped_view.required_scopes = scopes - return _wrapped_view - - return decorator diff --git a/backend/core/api/public/endpoints/Invoices/create.py b/backend/core/api/public/endpoints/Invoices/create.py deleted file mode 100644 index 3382c93c4..000000000 --- a/backend/core/api/public/endpoints/Invoices/create.py +++ /dev/null @@ -1,126 +0,0 @@ -from drf_yasg import openapi -from drf_yasg.utils import swagger_auto_schema -from rest_framework import status -from rest_framework.decorators import api_view -from rest_framework.response import Response - -from backend.clients.models import Client -from backend.core.api.public.decorators import require_scopes -from backend.core.api.public.helpers.response import APIResponse -from backend.core.api.public.serializers.invoices import InvoiceSerializer -from backend.core.api.public.swagger_ui import TEAM_PARAMETER -from backend.core.api.public.types import APIRequest -from backend.finance.models import InvoiceProduct - - -def get_client(request: APIRequest) -> Client | None: - if request.team: - client = Client.objects.get(organization=request.team, id=request.data.get("client_id")) # type: ignore[misc] - return client - elif request.user: - client = Client.objects.get(user=request.user, id=request.data.get("client_id")) # type: ignore[misc] - return client - return None - - -def get_products(request: APIRequest) -> list[dict] | None: - product_id_list = request.query_params.get("product_id", "").split(",") - product_ids = [int(id.strip()) for id in product_id_list if id.strip().isdigit()] - - items_data = [] - for product_id in product_ids: - if request.team: - product = InvoiceProduct.objects.get(organization=request.team, id=product_id) - else: - product = InvoiceProduct.objects.get(user=request.user, id=product_id) - - product_data = { - "name": product.name, - "description": product.description, - "hours": product.quantity, - "price_per_hour": product.rate, - "price": (product.rate * product.quantity) if product.rate else product.quantity, - } - items_data.append(product_data) - - return items_data - - -@swagger_auto_schema( - method="post", - operation_description="Create invoice", - operation_id="create_invoice", - manual_parameters=[ - TEAM_PARAMETER, - openapi.Parameter( - "product_id", - openapi.IN_QUERY, - description="Id of a product", - type=openapi.TYPE_ARRAY, - items=openapi.Items(type=openapi.TYPE_INTEGER), - ), - openapi.Parameter( - "client_id", - openapi.IN_QUERY, - description="Id of a client", - type=openapi.TYPE_INTEGER, - ), - ], - request_body=InvoiceSerializer, - responses={ - 201: openapi.Response( - description="Invoice created successfully", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN, description="Indicates if the operation was successful"), - "invoice_id": openapi.Schema(type=openapi.TYPE_STRING, description="The ID of the created invoice"), - }, - ), - ), - 400: openapi.Response( - description="Bad request", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN, description="Indicates if the operation was successful"), - "message": openapi.Schema( - type=openapi.TYPE_STRING, description="Your request is missing fields or fields are incorrect" - ), - }, - ), - ), - }, - partial=True, -) -@api_view(["POST"]) -@require_scopes(["invoices:write"]) -def create_invoice_endpoint(request: APIRequest) -> Response: - for key, value in request.query_params.items(): - request.data[key] = value - - serializer = InvoiceSerializer(data=request.data) - - if not serializer.is_valid(): - return APIResponse(False, serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - if "client_id" in request.data and request.data["client_id"]: - try: - client = get_client(request) - serializer.validated_data["client_to"] = client - except Client.DoesNotExist: - return APIResponse(False, "Client not found", status=status.HTTP_400_BAD_REQUEST) - - if "product_id" in request.data and request.data["product_id"]: - try: - items_data = get_products(request) - serializer.validated_data["items"] = items_data - except InvoiceProduct.DoesNotExist: - return APIResponse(False, "InvoiceProduct not found", status=status.HTTP_400_BAD_REQUEST) - - if request.team: - invoice = serializer.save(organization=request.team) - else: - invoice = serializer.save(user=request.user) - - return APIResponse(True, {"invoice_id": invoice.id}, status=status.HTTP_201_CREATED) diff --git a/backend/core/api/public/endpoints/Invoices/delete.py b/backend/core/api/public/endpoints/Invoices/delete.py deleted file mode 100644 index 6f7ed1770..000000000 --- a/backend/core/api/public/endpoints/Invoices/delete.py +++ /dev/null @@ -1,29 +0,0 @@ -from django.http import QueryDict -from rest_framework import status -from rest_framework.decorators import api_view - -from backend.core.api.public.decorators import require_scopes -from backend.core.api.public.types import APIRequest -from backend.core.api.public.helpers.response import APIResponse - -from backend.models import Invoice, QuotaLimit - - -@api_view(["DELETE"]) -@require_scopes(["invoices:write"]) -def delete_invoice_endpoint(request: APIRequest): - delete_items = QueryDict(request.body) - - try: - invoice = Invoice.objects.get(id=delete_items.get("invoice", "")) - except Invoice.DoesNotExist: - return APIResponse(False, {"error": "Invoice Not Found"}, status=status.HTTP_404_NOT_FOUND) - - if not invoice.has_access(request.user): - return APIResponse(False, {"error": "You do not have permission to delete this invoice"}, status=status.HTTP_403_FORBIDDEN) - - QuotaLimit.delete_quota_usage("invoices-count", request.user, invoice.id, invoice.date_created) - - invoice.delete() - - return APIResponse(True, {"message": "Invoice successfully deleted"}, status=status.HTTP_200_OK) diff --git a/backend/core/api/public/endpoints/Invoices/download_pdf.py b/backend/core/api/public/endpoints/Invoices/download_pdf.py deleted file mode 100644 index d49c18224..000000000 --- a/backend/core/api/public/endpoints/Invoices/download_pdf.py +++ /dev/null @@ -1,76 +0,0 @@ -from datetime import datetime - -from django.http import HttpResponse -from drf_yasg import openapi -from drf_yasg.utils import swagger_auto_schema -from rest_framework import status -from rest_framework.decorators import api_view -from rest_framework.response import Response - -from backend.core.api.public.decorators import require_scopes -from backend.core.api.public.helpers.deprecate import deprecated -from backend.core.api.public.swagger_ui import TEAM_PARAMETER -from backend.core.api.public.types import APIRequest -from backend.finance.models import Invoice -from backend.core.service.invoices.single.create_pdf import generate_pdf -from backend.core.api.public.helpers.response import APIResponse - - -@swagger_auto_schema( - method="get", - operation_description="Download invoice", - operation_id="download_invoice", - manual_parameters=[ - TEAM_PARAMETER, - ], - responses={ - 200: openapi.Response( - description="Download invoice by id.", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN), - "invoice": openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_OBJECT)), - }, - ), - ), - 400: openapi.Response( - description="Bad request", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN, description="Indicates if the operation was successful"), - "message": openapi.Schema( - type=openapi.TYPE_STRING, description="Your request is missing fields or fields are incorrect" - ), - }, - ), - ), - 500: openapi.Response( - description="Internal error", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN, description="Indicates if the operation was successful"), - "message": openapi.Schema(type=openapi.TYPE_STRING, description="Internal error occurred while generating PDF"), - }, - ), - ), - }, - deprecated=True, -) -@api_view(["GET"]) -@deprecated(datetime(2024, 7, 16), datetime(2024, 7, 16)) -@require_scopes(["invoices:read"]) -def download(request: APIRequest, id: str) -> HttpResponse | Response: - try: - if request.team: - invoice = Invoice.objects.get(organization=request.team, id=id) - else: - invoice = Invoice.objects.get(user=request.user, id=id) - except Invoice.DoesNotExist: - return APIResponse(False, {"message": "Invoice not found"}, status=status.HTTP_400_BAD_REQUEST) - - if response := generate_pdf(invoice, "attachment"): - return response - return APIResponse(False, {"message": "Error generating PDF"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/backend/core/api/public/endpoints/Invoices/edit.py b/backend/core/api/public/endpoints/Invoices/edit.py deleted file mode 100644 index 12232404e..000000000 --- a/backend/core/api/public/endpoints/Invoices/edit.py +++ /dev/null @@ -1,132 +0,0 @@ -from datetime import datetime -from typing import Literal - -from rest_framework import status -from rest_framework.decorators import api_view - -from backend.core.api.public.decorators import require_scopes -from backend.core.api.public.types import APIRequest -from backend.core.api.public.helpers.response import APIResponse -from backend.finance.models import Invoice - - -@api_view(["POST"]) -@require_scopes(["invoices:write"]) -def edit_invoice_endpoint(request: APIRequest): - invoice_id = request.data.get("invoice_id", "") - if not invoice_id: - return APIResponse(False, {"error": "Invoice ID is required"}, status=status.HTTP_400_BAD_REQUEST) - - try: - invoice = Invoice.objects.get(id=invoice_id) - except Invoice.DoesNotExist: - return APIResponse(False, {"error": "Invoice Not Found"}, status=status.HTTP_404_NOT_FOUND) - - if request.user.logged_in_as_team and request.user.logged_in_as_team != invoice.organization: - return APIResponse( - False, - {"error": "You do not have permission to edit this invoice"}, - status=status.HTTP_403_FORBIDDEN, - ) - elif request.user != invoice.user: - return APIResponse( - False, - {"error": "You do not have permission to edit this invoice"}, - status=status.HTTP_403_FORBIDDEN, - ) - - attributes_to_updates = { - "date_due": request.POST.get("date_due"), - "date_issued": request.POST.get("date_issued"), - "client_name": request.POST.get("to_name"), - "client_company": request.POST.get("to_company"), - "client_email": request.POST.get("to_email"), - "client_address": request.POST.get("to_address"), - "client_city": request.POST.get("to_city"), - "client_county": request.POST.get("to_county"), - "client_country": request.POST.get("to_country"), - "self_name": request.POST.get("from_name"), - "self_company": request.POST.get("from_company"), - "self_address": request.POST.get("from_address"), - "self_city": request.POST.get("from_city"), - "self_county": request.POST.get("from_county"), - "self_country": request.POST.get("from_country"), - "notes": request.POST.get("notes"), - "vat_number": request.POST.get("vat_number"), - "reference": request.POST.get("reference"), - "sort_code": request.POST.get("sort_code"), - "account_number": request.POST.get("account_number"), - "account_holder_name": request.POST.get("account_holder_name"), - } - - for column_name, new_value in attributes_to_updates.items(): - if new_value is not None: - if column_name == "date_due": - try: - new_value = datetime.strptime(new_value, "%Y-%m-%d").date() # type: ignore[assignment] - except ValueError: - return APIResponse(False, {"error": "Invalid date format for date_due"}, status=status.HTTP_400_BAD_REQUEST) - setattr(invoice, column_name, new_value) - - invoice.save() - - return APIResponse(True, {"message": "Invoice successfully edited"}, status=status.HTTP_200_OK) - - -@api_view(["POST"]) -def change_status_endpoint(request, invoice_id: int, invoice_status: str): - new_status = invoice_status.lower() if invoice_status else "" - - try: - invoice = Invoice.objects.get(id=invoice_id) - except Invoice.DoesNotExist: - return APIResponse(False, {"error": "Invoice Not Found"}, status=status.HTTP_404_NOT_FOUND) - - if request.user.logged_in_as_team and request.user.logged_in_as_team != invoice.organization or request.user != invoice.user: - return APIResponse(False, {"error": "You do not have permission to edit this invoice"}, status=status.HTTP_403_FORBIDDEN) - - if invoice.status == new_status: - return APIResponse(False, {"error": f"Invoice status is already {new_status}"}, status=status.HTTP_400_BAD_REQUEST) - - if not invoice.set_status(new_status, save=True): - return APIResponse(False, {"error": "Invalid status. Please choose from: pending, paid, draft"}, status=status.HTTP_400_BAD_REQUEST) - - return APIResponse(True, {"message": f"Invoice status been changed to {new_status}"}, status=status.HTTP_200_OK) - - -@api_view(["POST"]) -def edit_discount_endpoint(request, invoice_id: str): - discount_type = "percentage" if request.data.get("discount_type") == "on" else "amount" - discount_amount_str: str = request.data.get("discount_amount", "") - percentage_amount_str: str = request.data.get("percentage_amount", "") - - try: - invoice: Invoice = Invoice.objects.get(id=invoice_id) - except Invoice.DoesNotExist: - return APIResponse(False, {"error": "Invoice not found"}, status=status.HTTP_404_NOT_FOUND) - - if not invoice.has_access(request.user): - return APIResponse(False, {"error": "You don't have permission to make changes to this invoice."}, status=status.HTTP_403_FORBIDDEN) - - if discount_type == "percentage": - try: - percentage_amount = int(percentage_amount_str) - if percentage_amount < 0 or percentage_amount > 100: - raise ValueError - except ValueError: - return APIResponse( - False, {"error": "Please enter a valid percentage amount (between 0 and 100)"}, status=status.HTTP_400_BAD_REQUEST - ) - invoice.discount_percentage = percentage_amount - else: - try: - discount_amount = int(discount_amount_str) - if discount_amount < 0: - raise ValueError - except ValueError: - return APIResponse(False, {"error": "Please enter a valid discount amount"}, status=status.HTTP_400_BAD_REQUEST) - invoice.discount_amount = discount_amount - - invoice.save() - - return APIResponse(True, {"message": "Discount was applied successfully"}, status=status.HTTP_200_OK) diff --git a/backend/core/api/public/endpoints/Invoices/get.py b/backend/core/api/public/endpoints/Invoices/get.py deleted file mode 100644 index 044be292e..000000000 --- a/backend/core/api/public/endpoints/Invoices/get.py +++ /dev/null @@ -1,60 +0,0 @@ -from drf_yasg import openapi -from drf_yasg.utils import swagger_auto_schema -from rest_framework import status -from rest_framework.decorators import api_view -from rest_framework.response import Response - -from backend.core.api.public.decorators import require_scopes -from backend.core.api.public.serializers.invoices import InvoiceSerializer -from backend.core.api.public.swagger_ui import TEAM_PARAMETER -from backend.core.api.public.types import APIRequest -from backend.core.api.public.helpers.response import APIResponse -from backend.finance.models import Invoice - - -@swagger_auto_schema( - method="get", - operation_description="Get invoice", - operation_id="get_invoice", - manual_parameters=[ - TEAM_PARAMETER, - ], - responses={ - 200: openapi.Response( - description="Get invoice by id.", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN), - "invoice": openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_OBJECT)), - }, - ), - ), - 400: openapi.Response( - description="Bad request", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN, description="Indicates if the operation was successful"), - "message": openapi.Schema( - type=openapi.TYPE_STRING, description="Your request is missing fields or fields are incorrect" - ), - }, - ), - ), - }, -) -@api_view(["GET"]) -@require_scopes(["invoices:read"]) -def get_invoices_endpoint(request: APIRequest, id: str) -> Response: - try: - if request.team: - invoices = Invoice.objects.filter(organization=request.team, id=id) - else: - invoices = Invoice.objects.filter(user=request.user, id=id) - except Invoice.DoesNotExist: - return APIResponse(False, {"message": "Invoice not found"}, status=status.HTTP_400_BAD_REQUEST) - - serializer = InvoiceSerializer(invoices, many=True) - - return APIResponse(True, {"invoice": serializer.data}, status=status.HTTP_200_OK) diff --git a/backend/core/api/public/endpoints/Invoices/list.py b/backend/core/api/public/endpoints/Invoices/list.py deleted file mode 100644 index adb81033b..000000000 --- a/backend/core/api/public/endpoints/Invoices/list.py +++ /dev/null @@ -1,86 +0,0 @@ -from django.db.models import Case, When, CharField -from django.db.models.expressions import Value, F -from django.utils import timezone -from drf_yasg import openapi -from drf_yasg.utils import swagger_auto_schema -from rest_framework import status -from rest_framework.decorators import api_view -from rest_framework.response import Response - -from backend.core.api.public.decorators import require_scopes -from backend.core.api.public.helpers.response import APIResponse -from backend.core.api.public.serializers.invoices import InvoiceSerializer -from backend.core.api.public.swagger_ui import TEAM_PARAMETER -from backend.core.api.public.types import APIRequest - -from backend.finance.models import Invoice -from backend.core.service.invoices.common.fetch import get_context - - -@swagger_auto_schema( - method="get", - operation_description="List all invoices", - operation_id="list_invoices", - manual_parameters=[ - TEAM_PARAMETER, - # openapi.Parameter( - # "sort", - # openapi.IN_QUERY, - # description="Field you want to order by to. Sort options: 'date_due', 'id', 'status'. Default by 'id'.", - # type=openapi.TYPE_STRING, - # ), - # openapi.Parameter( - # "sort_direction", - # openapi.IN_QUERY, - # description="Order by descending or ascending. 'False' for descending and 'True' for ascending. Default is ascending.", - # type=openapi.TYPE_STRING, - # ), - # openapi.Parameter( - # "filter_type", - # openapi.IN_QUERY, - # description="Select filter type by which results will be filtered. Filter types are 'status' and " - # "'amount'. By default there is no filter types applied.", - # type=openapi.TYPE_STRING, - # ), - # openapi.Parameter( - # "filter", - # openapi.IN_QUERY, - # description="Select filter by which results will be filtered. Filters for 'status' are 'paid', " - # "'pending', 'overdue', 'draft' and for 'amount' are '20+', '50+', '100+'. By default there is no " - # "filter applied.", - # type=openapi.TYPE_STRING, - # ), - ], - responses={ - 200: openapi.Response( - description="List of invoices", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN), - "invoices": openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_OBJECT)), - }, - ), - ) - }, -) -@api_view(["GET"]) -@require_scopes(["invoices:read"]) -def list_invoices_endpoint(request: APIRequest) -> Response: - if request.team: - invoices = Invoice.objects.filter(organization=request.team) - else: - invoices = Invoice.objects.filter(user=request.user) - - # sort_by = request.query_params.get("sort") - # sort_direction = request.query_params.get("sort_direction", "") - # action_filter_type = request.query_params.get("filter_type") - # action_filter_by = request.query_params.get("filter") - - # todo: add back sort + filters on backend for API - - _, invoices = get_context(invoices) # type: ignore[assignment] - - serializer = InvoiceSerializer(invoices, many=True) - - return APIResponse(True, {"invoices": serializer.data}, status=status.HTTP_200_OK) diff --git a/backend/core/api/public/endpoints/Invoices/urls.py b/backend/core/api/public/endpoints/Invoices/urls.py deleted file mode 100644 index 6d0d55d6e..000000000 --- a/backend/core/api/public/endpoints/Invoices/urls.py +++ /dev/null @@ -1,36 +0,0 @@ -from django.urls import path - -from . import create, list, get, download_pdf - -urlpatterns = [ - path( - "create", - create.create_invoice_endpoint, - name="create", - ), - # path( - # "delete/", - # delete.delete_invoice_endpoint, - # name="delete", - # ), - # path( - # "edit/", - # edit.edit_invoice_endpoint, - # name="edit", - # ), - # path( - # "edit//set_status//", - # edit.change_status_endpoint, - # name="edit status" - # ), - # path( - # "edit//discount/", - # edit.edit_discount_endpoint, - # name="edit discount" - # ), - path("", list.list_invoices_endpoint, name="list"), - path("/", get.get_invoices_endpoint, name="get"), - path("/download/", download_pdf.download, name="download"), -] - -app_name = "invoices" diff --git a/backend/core/api/public/endpoints/clients/create.py b/backend/core/api/public/endpoints/clients/create.py deleted file mode 100644 index 7899eb3db..000000000 --- a/backend/core/api/public/endpoints/clients/create.py +++ /dev/null @@ -1,61 +0,0 @@ -from drf_yasg import openapi -from drf_yasg.utils import swagger_auto_schema -from rest_framework import status -from rest_framework.decorators import api_view -from rest_framework.response import Response - -from backend.core.api.public.decorators import require_scopes -from backend.core.api.public.helpers.response import APIResponse -from backend.core.api.public.serializers.clients import ClientSerializer -from backend.core.api.public.swagger_ui import TEAM_PARAMETER -from backend.core.api.public.types import APIRequest - - -@swagger_auto_schema( - method="post", - operation_description="Create a client", - operation_id="clients_create", - manual_parameters=[ - TEAM_PARAMETER, - ], - query_serializer=ClientSerializer, - responses={ - 201: openapi.Response( - description="Client created successfully", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN, description="Indicates if the operation was successful"), - "client_id": openapi.Schema(type=openapi.TYPE_STRING, description="The ID of the created client"), - }, - ), - ), - 403: openapi.Response( - description="Forbidden", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN, description="Indicates if the operation was successful"), - "message": openapi.Schema( - type=openapi.TYPE_STRING, description="You do not have permission to create client under " "this account" - ), - }, - ), - ), - }, -) -@api_view(["POST"]) -@require_scopes(["clients:write"]) -def client_create_endpoint(request: APIRequest): - - serializer = ClientSerializer(data=request.data) - - if not serializer.is_valid(): - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - if request.team: - client = serializer.save(organization=request.team) - else: - client = serializer.save(user=request.user) - - return APIResponse(True, {"client_id": client.id}, status=status.HTTP_201_CREATED) diff --git a/backend/core/api/public/endpoints/clients/delete.py b/backend/core/api/public/endpoints/clients/delete.py deleted file mode 100644 index 398f77f1a..000000000 --- a/backend/core/api/public/endpoints/clients/delete.py +++ /dev/null @@ -1,68 +0,0 @@ -from typing import Literal - -from drf_yasg import openapi -from drf_yasg.utils import swagger_auto_schema -from rest_framework.decorators import api_view - -from backend.core.api.public.decorators import require_scopes -from backend.core.api.public.swagger_ui import TEAM_PARAMETER -from backend.core.api.public.types import APIRequest - -from backend.core.service.clients.delete import delete_client, DeleteClientServiceResponse -from backend.core.api.public.helpers.response import APIResponse - - -@swagger_auto_schema( - method="delete", - operation_description="Delete a client", - operation_id="clients_delete", - manual_parameters=[ - TEAM_PARAMETER, - ], - responses={ - 200: openapi.Response( - description="Client deleted successfully", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN, description="Indicates if the operation was successful"), - "client_id": openapi.Schema(type=openapi.TYPE_STRING, description="The ID of the deleted client"), - }, - ), - ), - 403: openapi.Response( - description="Forbidden", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN, description="Indicates if the operation was successful"), - "message": openapi.Schema(type=openapi.TYPE_STRING, description="You do not have permission to delete this client"), - }, - ), - ), - 404: openapi.Response( - description="Not Found", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN, description="Indicates if the operation was successful"), - "message": openapi.Schema(type=openapi.TYPE_STRING, description="This client does not exist"), - }, - ), - examples={ - "application/json": { - "success": False, - "message": "This client does not exist", - } - }, - ), - }, -) -@api_view(["DELETE"]) -@require_scopes(["clients:write"]) -def client_delete_endpoint(request: APIRequest, id: str): - response: DeleteClientServiceResponse = delete_client(request, id) - - if response.failed: - return APIResponse(False, response.error, status=403 if "do not have permission" in response.error else 404) - return APIResponse(True, {"client_id": id}, status=200) diff --git a/backend/core/api/public/endpoints/clients/list.py b/backend/core/api/public/endpoints/clients/list.py deleted file mode 100644 index 29c59f969..000000000 --- a/backend/core/api/public/endpoints/clients/list.py +++ /dev/null @@ -1,52 +0,0 @@ -from drf_yasg import openapi -from drf_yasg.utils import swagger_auto_schema -from django.db.models import QuerySet -from rest_framework.decorators import api_view -from rest_framework.response import Response - -from backend.core.api.public.decorators import require_scopes -from backend.core.api.public.helpers.response import APIResponse -from backend.core.api.public.serializers.clients import ClientSerializer -from backend.core.api.public.swagger_ui import TEAM_PARAMETER -from backend.core.api.public.types import APIRequest -from backend.core.service.clients.get import fetch_clients, FetchClientServiceResponse - -from backend.models import Organization - - -@swagger_auto_schema( - method="get", - operation_description="List all clients", - operation_id="clients_list", - manual_parameters=[ - TEAM_PARAMETER, - openapi.Parameter("order_by", openapi.IN_QUERY, description="field you want to order by to", type=openapi.TYPE_STRING), - openapi.Parameter("search", openapi.IN_QUERY, description="field you want to search by", type=openapi.TYPE_STRING), - ], - responses={ - 200: openapi.Response( - description="List of clients", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "success": openapi.Schema(type=openapi.TYPE_BOOLEAN), - "clients": openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Items(type=openapi.TYPE_OBJECT)), - }, - ), - ) - }, -) -@api_view(["GET"]) -@require_scopes(["clients:read"]) -def list_clients_endpoint(request: APIRequest): - # paginator = PageNumberPagination() - # paginator.page_size = 5 - - search_text = request.data.get("search") - - clients: FetchClientServiceResponse = fetch_clients(request, search_text=search_text, team=request.team) - - # queryset = paginator.paginate_queryset(clients, request) - - serializer = ClientSerializer(clients.response, many=True) - return APIResponse(True, {"clients": serializer.data}) diff --git a/backend/core/api/public/endpoints/clients/urls.py b/backend/core/api/public/endpoints/clients/urls.py deleted file mode 100644 index f0c5dbf14..000000000 --- a/backend/core/api/public/endpoints/clients/urls.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.urls import path - -from . import list, delete -from .create import client_create_endpoint - -urlpatterns = [ - path( - "", - list.list_clients_endpoint, - name="list", - ), - path("/", delete.client_delete_endpoint, name="delete"), - path("create/", client_create_endpoint, name="create"), -] - -app_name = "clients" diff --git a/backend/core/api/public/endpoints/system_health.py b/backend/core/api/public/endpoints/system_health.py deleted file mode 100644 index 391ae4fcc..000000000 --- a/backend/core/api/public/endpoints/system_health.py +++ /dev/null @@ -1,63 +0,0 @@ -from drf_yasg.utils import swagger_auto_schema -from drf_yasg import openapi -from django.db import connection, OperationalError -from django.core.cache import cache - -from rest_framework.decorators import api_view, permission_classes - -from backend.core.api.public.permissions import IsSuperuser -from backend.core.api.public.helpers.response import APIResponse - - -@swagger_auto_schema( - method="get", - operation_description="Check the system's health by verifying database and external API connections.", - responses={ - 200: openapi.Response( - description="System health check result", - schema=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "problems": openapi.Schema( - type=openapi.TYPE_ARRAY, - items=openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - "id": openapi.Schema(type=openapi.TYPE_STRING, description="Problem ID"), - "message": openapi.Schema(type=openapi.TYPE_STRING, description="Problem message"), - }, - ), - ), - "healthy": openapi.Schema(type=openapi.TYPE_BOOLEAN, description="Indicates overall system health"), - }, - ), - examples={ - "application/json": { - "problems": [ - {"id": "database", "message": "database failed to connect"}, - ], - "healthy": False, - } - }, - ) - }, -) -@api_view(["GET"]) -@permission_classes([IsSuperuser]) -def system_health_endpoint(request): - if not request.user or not request.user.is_superuser: - return APIResponse(False, "User is not permitted to view internal information", status=403) - - problems = [] - - try: - connection.ensure_connection() - except OperationalError: - problems.append({"id": "database", "message": "database failed to connect"}) - - try: - cache._cache.get_client().ping() - except ConnectionError: - problems.append({"id": "redis", "message": "redis failed to connect"}) - - return APIResponse({"problems": problems, "healthy": not bool(problems)}) diff --git a/backend/core/api/public/endpoints/webhooks/urls.py b/backend/core/api/public/endpoints/webhooks/urls.py deleted file mode 100644 index f4209f0a0..000000000 --- a/backend/core/api/public/endpoints/webhooks/urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.urls import path -from .webhook_task_queue_handler import webhook_task_queue_handler_view_endpoint - -urlpatterns = [ - path( - "receive/global/", - webhook_task_queue_handler_view_endpoint, - name="receive_global", - ) -] - -app_name = "webhooks" diff --git a/backend/core/api/public/endpoints/webhooks/webhook_task_queue_handler.py b/backend/core/api/public/endpoints/webhooks/webhook_task_queue_handler.py deleted file mode 100644 index 256f302ce..000000000 --- a/backend/core/api/public/endpoints/webhooks/webhook_task_queue_handler.py +++ /dev/null @@ -1,46 +0,0 @@ -import logging -from backend.core.api.public import APIAuthToken -from rest_framework.decorators import api_view - -from backend.core.service.asyn_tasks.tasks import Task -from backend.core.api.public.helpers.response import APIResponse - - -@api_view(["POST"]) -def webhook_task_queue_handler_view_endpoint(request): - token: APIAuthToken | None = request.auth - - if not token: - return APIResponse(False, {"status": "error", "message": "No token found"}, status=500) - - if not token.administrator_service_type == token.AdministratorServiceTypes.AWS_WEBHOOK_CALLBACK: - return APIResponse(False, {"status": "error", "message": "Invalid API key for this service"}, status=500) - - try: - data: dict = request.data - func_name: str = data.get("func_name") - args: list = data.get("args", []) - kwargs: dict = data.get("kwargs", {}) - - print(f"Function Name: {func_name}") - print(f"Arguments: {args}") - print(f"Keyword Arguments: {kwargs}") - - # Validate function name - if not func_name: - raise ValueError("Function name is required.") - - # Create an instance of Task - task_helper = Task() - - # Attempt to execute the function - result = task_helper.execute_now(func_name, *args, **kwargs) - - # Handle the result (e.g., store it or log it) - print(f"Webhook executed: {func_name} with result: {result}") - - return APIResponse(True, {"status": "success", "result": result}) - - except Exception as e: - logging.error(f"Error executing webhook task: {str(e)}") - return APIResponse(False, {"status": "error", "message": "An internal error has occurred."}, status=500) diff --git a/backend/core/api/public/helpers/deprecate.py b/backend/core/api/public/helpers/deprecate.py deleted file mode 100644 index 54c4ee325..000000000 --- a/backend/core/api/public/helpers/deprecate.py +++ /dev/null @@ -1,46 +0,0 @@ -import datetime -import functools -import logging - -from rest_framework.response import Response - -logger = logging.getLogger(__name__) - - -# add type hints for these deprecation dates - - -def deprecated(deprecation_date: datetime.datetime | None = None, end_of_life_date: datetime.datetime | None = None): - """ - Returns a decorator which informs requester that the decorated endpoint has been deprecated. - """ - - def decorator_deprecated(func): - """Amend the request with information that the endpoint has been deprecated and when it will be removed""" - - @functools.wraps(func) - def wrapper_deprecated(*args, **kwargs): - # do something before handling the request, could e.g. issue a django signal - logger.warning("Deprecated endpoint %s called", func.__name__) - - if end_of_life_date and datetime.datetime.now() > end_of_life_date: - return Response( - {"success": False, "message": "This endpoint is no longer available"}, - status=410, - headers={"X-Deprecated": "", "X-Deprecation-Date": deprecation_date, "X-End-Of-Life-Date": end_of_life_date}, - ) - - response: Response = func(*args, **kwargs) - - # amend the response with deprecation information - if isinstance(response, Response): - response.headers["X-Deprecated"] = "" - if deprecation_date: - response.headers["X-Deprecation-Date"] = deprecation_date - if end_of_life_date: - response.headers["X-End-Of-Life-Date"] = deprecation_date - return response - - return wrapper_deprecated - - return decorator_deprecated diff --git a/backend/core/api/public/helpers/response.py b/backend/core/api/public/helpers/response.py deleted file mode 100644 index f0ae92470..000000000 --- a/backend/core/api/public/helpers/response.py +++ /dev/null @@ -1,19 +0,0 @@ -from rest_framework.response import Response - - -def APIResponse(success: bool = True, data: str | dict | None = None, meta=None, status: int = 0, **kwargs) -> Response: - """ - - Returns a rest_framework Response object, but prefills meta (success etc) aswell as the data with KWARGS. - - """ - meta = meta or {} - if not status and success: - status = 201 - elif not status: - status = 400 - - if success: - return Response({"meta": {"success": True, **meta}, "data": {**kwargs} | data if isinstance(data, dict) else {}}, status=status) - else: - return Response({"meta": {"success": False}, "error": data}, status=status) diff --git a/backend/core/api/public/middleware.py b/backend/core/api/public/middleware.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/api/public/models.py b/backend/core/api/public/models.py deleted file mode 100644 index 96476bac2..000000000 --- a/backend/core/api/public/models.py +++ /dev/null @@ -1,70 +0,0 @@ -from django.contrib.contenttypes.fields import GenericForeignKey -from django.contrib.contenttypes.models import ContentType -from django.db import models -from django.contrib.auth.hashers import check_password, make_password -import binascii -import os -from django.utils import timezone - -from backend.core.models import OwnerBase, ExpiresBase - - -class APIAuthToken(OwnerBase, ExpiresBase): - id = models.AutoField(primary_key=True) - - hashed_key = models.CharField("Key", max_length=128, unique=True) - - name = models.CharField("Key Name", max_length=64) - description = models.TextField("Description", blank=True, null=True) - created = models.DateTimeField("Created", auto_now_add=True) - last_used = models.DateTimeField("Last Used", null=True, blank=True) - # expires = models.DateTimeField("Expires", null=True, blank=True, help_text="Leave blank for no expiry") - # expired = models.BooleanField("Expired", default=False, help_text="If the key has expired") - # active = models.BooleanField("Active", default=True, help_text="If the key is active") - scopes = models.JSONField("Scopes", default=list, help_text="List of permitted scopes") - - class AdministratorServiceTypes(models.TextChoices): - AWS_WEBHOOK_CALLBACK = "aws_webhook_callback", "AWS Webhook Callback" - AWS_API_DESTINATION = "aws_api_destination", "AWS API Destination" - - administrator_service_type = models.CharField("Administrator Service Type", max_length=64, blank=True, null=True) - - class Meta: - verbose_name = "API Key" - verbose_name_plural = "API Keys" - - def __str__(self): - return self.name - - def update_last_used(self): - self.last_used = timezone.now() - self.save() - return True - - # def save(self, *args, **kwargs): - # return super().save(*args, **kwargs) - - def generate_key(self) -> str: - """ - :returns: raw_key - """ - - raw = binascii.hexlify(os.urandom(20)).decode() - self.hashed_key = self.hash_raw_key(raw) - - return raw - - @classmethod - def hash_raw_key(cls, raw_key: str): - return make_password(raw_key, salt="api_tokens", hasher="default") - - def verify(self, key) -> bool: - return check_password(key, self.hashed_key) - - def deactivate(self): - self.active = False - self.save() - return self - - def has_scope(self, scope): - return scope in self.scopes diff --git a/backend/core/api/public/permissions.py b/backend/core/api/public/permissions.py deleted file mode 100644 index 9893a41cc..000000000 --- a/backend/core/api/public/permissions.py +++ /dev/null @@ -1,66 +0,0 @@ -from rest_framework.permissions import BasePermission -from rest_framework.request import Request -from django.conf import settings - -SCOPES = { - "clients:read", - "clients:write", - "invoices:read", - "invoices:write", - "receipts:read", - "receipts:write", - "clients:read", - "clients:write", - "emails:read", - "emails:send", - "profile:read", - "profile:write", - "api_keys:read", - "api_keys:write", - "team_permissions:read", - "team_permissions:write", - "team:invite", - "team:kick", - "account_defaults:write", - "email_templates:read", - "email_templates:write", -} - -SCOPES_TREE = { - "clients:read": {"clients:read"}, - "clients:write": {"clients:read", "clients:write"}, - "invoices:read": {"invoices:read"}, - "invoices:write": {"invoices:read", "invoices:write"}, - "profile:read": {"profile:read"}, - "profile:write": {"profile:read", "profile:write"}, - "api_keys:read": {"api_keys:read"}, - "api_keys:write": {"api_keys:read", "api_keys:write"}, - "team_permissions:read": {"team_permissions:read"}, - "team_permissions:write": {"team_permissions:read", "team_permissions:write"}, - "team:invite": {"team:invite"}, - "team:kick": {"team:kick", "team:invite"}, - "email_templates:read": {"email_templates:read"}, - "email_templates:write": {"email_templates:read", "email_templates:write"}, - "account_defaults:write": {"account_defaults:write"}, -} - -SCOPE_DESCRIPTIONS = { - "clients": {"description": "Access customer details", "options": {"read": "Read only", "write": "Read and write"}}, - "invoices": {"description": "Access invoices", "options": {"read": "Read only", "write": "Read and write"}}, - "profile": {"description": "Access profile details", "options": {"read": "Read only", "write": "Read and write"}}, - "api_keys": {"description": "Access API keys", "options": {"read": "Read only", "write": "Read and write"}}, - "team_permissions": {"description": "Access team permissions", "options": {"read": "Read only", "write": "Read and write"}}, - "team": {"description": "Invite team members", "options": {"invite": "Invite members"}}, - "email_templates": {"description": "Access email templates", "options": {"read": "Read only", "write": "Read and write"}}, - "account_defaults": {"description": "Modify account defaults", "options": {"write": "Read and write"}}, -} - -if settings.BILLING_ENABLED: - SCOPES.add("billing:manage") - SCOPES_TREE["billing:manage"] = {"billing:manage"} - SCOPE_DESCRIPTIONS["billing"] = {"description": "Access billing details + stripe", "options": {"manage": "Manage billing"}} - - -class IsSuperuser(BasePermission): - def has_permission(self, request: Request, view: object) -> bool: - return bool(request.user and request.user.is_superuser) diff --git a/backend/core/api/public/serializers/clients.py b/backend/core/api/public/serializers/clients.py deleted file mode 100644 index fd721a322..000000000 --- a/backend/core/api/public/serializers/clients.py +++ /dev/null @@ -1,9 +0,0 @@ -from rest_framework import serializers - -from backend.finance.models import Client - - -class ClientSerializer(serializers.ModelSerializer): - class Meta: - model = Client - exclude = ("organization", "user", "email_verified") diff --git a/backend/core/api/public/serializers/invoices.py b/backend/core/api/public/serializers/invoices.py deleted file mode 100644 index b019ea706..000000000 --- a/backend/core/api/public/serializers/invoices.py +++ /dev/null @@ -1,31 +0,0 @@ -from rest_framework import serializers - -from backend.finance.models import InvoiceItem, Invoice - - -class InvoiceItemSerializer(serializers.ModelSerializer): - name = serializers.CharField(required=False) - description = serializers.CharField(required=False) - - class Meta: - model = InvoiceItem - fields = "__all__" - - -class InvoiceSerializer(serializers.ModelSerializer): - items = InvoiceItemSerializer(many=True, required=False) - - class Meta: - model = Invoice - exclude = ("user", "organization", "client_to") - # fields = "__all__" - - def create(self, validated_data): - items_data = validated_data.pop("items", []) - invoice = Invoice.objects.create(**validated_data) - - for item_data in items_data: - item = InvoiceItem.objects.create(invoice=invoice, **item_data) - invoice.items.add(item) - - return invoice diff --git a/backend/core/api/public/swagger_ui.py b/backend/core/api/public/swagger_ui.py deleted file mode 100644 index e1b2f331a..000000000 --- a/backend/core/api/public/swagger_ui.py +++ /dev/null @@ -1,42 +0,0 @@ -from django.urls import path -from drf_yasg import openapi -from drf_yasg.views import get_schema_view -from rest_framework import permissions - -INFO = openapi.Info( - title="MyFinances Public API", - default_version="v0.0.1", - description="", - terms_of_service="", - contact=openapi.Contact(email="support@strelix.org"), - license=openapi.License(name="AGPL v3"), -) - -schema_view = get_schema_view( - INFO, - public=True, - permission_classes=[permissions.AllowAny], -) - - -def get_swagger_ui(): - return schema_view - - -def get_swagger_endpoints(debug): - return ( - [ - path("redoc/", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"), - ] - + [ - path("swagger/", schema_view.without_ui(cache_timeout=0), name="schema-json"), - path("swagger/", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"), - ] - if debug - else [] - ) - - -TEAM_PARAMETER = openapi.Parameter( - "team_id", openapi.IN_QUERY, description="id of the team you want to do this action under", type=openapi.TYPE_STRING, required=False -) diff --git a/backend/core/api/public/types.py b/backend/core/api/public/types.py deleted file mode 100644 index 05d937c70..000000000 --- a/backend/core/api/public/types.py +++ /dev/null @@ -1,12 +0,0 @@ -from rest_framework.request import Request - -from backend.core.api.public import APIAuthToken -from backend.models import User, Organization - - -class APIRequest(Request): - user: User - auth: APIAuthToken - api_token: APIAuthToken - team: Organization | None - team_id: int | None diff --git a/backend/core/api/public/urls.py b/backend/core/api/public/urls.py deleted file mode 100644 index 2ab400e50..000000000 --- a/backend/core/api/public/urls.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import annotations - -from django.conf.urls import include -from django.urls import path, re_path -from rest_framework.authentication import TokenAuthentication - -from .endpoints.system_health import system_health_endpoint - -INTERNAL_URLS = [path("health/", system_health_endpoint, name="public-system-health")] - -urlpatterns = [ - path("internal/", include(INTERNAL_URLS)), - path("clients/", include("backend.core.api.public.endpoints.clients.urls")), - path("invoices/", include("backend.core.api.public.endpoints.Invoices.urls")), - path("webhooks/", include("backend.core.api.public.endpoints.webhooks.urls")), -] - -app_name = "public" diff --git a/backend/core/api/quotas/fetch.py b/backend/core/api/quotas/fetch.py deleted file mode 100644 index f2520a030..000000000 --- a/backend/core/api/quotas/fetch.py +++ /dev/null @@ -1,30 +0,0 @@ -from django.db.models import Q -from django.shortcuts import render, redirect - -from backend.models import QuotaLimit -from backend.core.types.htmx import HtmxHttpRequest - - -def fetch_all_quotas(request: HtmxHttpRequest, group: str): - context = {} - if not request.htmx: - return redirect("quotas") - - search_text = request.GET.get("search") - - results = QuotaLimit.objects.filter(slug__startswith=group).prefetch_related("quota_overrides", "quota_usage").order_by("-slug") - - if search_text: - results = results.filter(Q(name__icontains=search_text)) - - quotas = [ - { - "quota_limit": ql.get_quota_limit(request.user), - "period_usage": ql.get_period_usage(request.user), - "quota_object": ql, - } - for ql in results - ] - - context.update({"quotas": quotas}) - return render(request, "pages/quotas/_fetch_body.html", context) diff --git a/backend/core/api/quotas/requests.py b/backend/core/api/quotas/requests.py deleted file mode 100644 index a2c239895..000000000 --- a/backend/core/api/quotas/requests.py +++ /dev/null @@ -1,136 +0,0 @@ -from dataclasses import dataclass -from typing import Union - -from django.contrib import messages -from django.http import HttpResponse -from django.shortcuts import redirect, render -from django.views.decorators.http import require_http_methods - -from backend.decorators import superuser_only -from backend.models import QuotaIncreaseRequest, QuotaLimit, QuotaUsage, QuotaOverrides -from backend.core.types.htmx import HtmxHttpRequest - - -# from backend.utils.quota_limit_ops import quota_usage_check_under - - -def submit_request(request: HtmxHttpRequest, slug) -> HttpResponse: - if not request.htmx: - return redirect("quotas") - - new_value = request.POST.get("new_value", "") - reason = request.POST.get("reason", "") - - try: - quota_limit = QuotaLimit.objects.get(slug=slug) - except QuotaLimit.DoesNotExist: - return error(request, "Failed to get the quota limit type") - - # usage_per_item = quota_usage_check_under(request, "quota_increase-request", extra_data=quota_limit.id, api=True, htmx=True) - # usage_per_month = quota_usage_check_under( - # request, "quota_increase-requests_per_month_per_quota", extra_data=quota_limit.id, api=True, htmx=True - # ) - - # if not isinstance(usage_per_item, bool): - # return usage_per_item - - # if not isinstance(usage_per_month, bool): - # return usage_per_month - - current = quota_limit.get_quota_limit(request.user) - - validate = validate_request(new_value, reason, current) - - if isinstance(validate, Error): - return error(request, validate.message) - - quota_increase_request = QuotaIncreaseRequest.objects.create( - user=request.user, quota_limit=quota_limit, new_value=new_value, current_value=current, reason=reason - ) - - QuotaUsage.create_str(request.user, "quota_increase-request", quota_increase_request.id) - QuotaUsage.create_str(request.user, "quota_increase-requests_per_month_per_quota", quota_limit.id) - - messages.success(request, "Successfully submitted a quota increase request") - return render(request, "base/toast.html") - - -@dataclass -class Error: - message: str - - -def error(request: HtmxHttpRequest, message: str) -> HttpResponse: - messages.error(request, message) - return render(request, "partials/messages_list.html") - - -def validate_request(new_value, reason, current) -> Union[bool, Error]: - if not new_value: - return Error("Please enter a valid increase value") - - try: - new_value = int(new_value) - if new_value <= current: - raise ValueError - except ValueError: - return Error("Please enter a valid increase value that is above your current limit.") - - if len(reason) < 25: - return Error("Please enter a valid reason for the increase.") - - return True - - -@superuser_only -@require_http_methods(["DELETE", "POST"]) -def approve_request(request: HtmxHttpRequest, request_id) -> HttpResponse: - if not request.htmx: - return redirect("quotas") - try: - quota_request = QuotaIncreaseRequest.objects.get(id=request_id) - except QuotaIncreaseRequest.DoesNotExist: - return error(request, "Failed to get the quota increase request") - - try: - quota_override_existing = QuotaOverrides.objects.get(user=quota_request.user, quota_limit=quota_request.quota_limit) - quota_override_existing.value = quota_request.new_value - quota_override_existing.save() - except QuotaOverrides.DoesNotExist: - QuotaOverrides.objects.create( - user=quota_request.user, - value=quota_request.new_value, - quota_limit=quota_request.quota_limit, - ) - - quota_limit_for_increase = QuotaLimit.objects.get(slug="quota_increase-request") - QuotaUsage.objects.filter(user=quota_request.user, quota_limit=quota_limit_for_increase, extra_data=quota_request.id).delete() - quota_request.status = "approved" - quota_request.save() - - try: - QuotaUsage.objects.get( - quota_limit=QuotaLimit.objects.get(slug="quota_increase-requests_per_month_per_quota"), extra_data=quota_request.quota_limit_id - ).delete() - except QuotaUsage.DoesNotExist: - ... - - return HttpResponse(status=200) - - -@superuser_only -@require_http_methods(["DELETE", "POST"]) -def decline_request(request: HtmxHttpRequest, request_id) -> HttpResponse: - if not request.htmx: - return redirect("quotas") - try: - quota_request = QuotaIncreaseRequest.objects.get(id=request_id) - except QuotaIncreaseRequest.DoesNotExist: - return error(request, "Failed to get the quota increase request") - - quota_limit_for_increase = QuotaLimit.objects.get(slug="quota_increase-request") - QuotaUsage.objects.filter(user=quota_request.user, quota_limit=quota_limit_for_increase, extra_data=quota_request.id).delete() - quota_request.status = "decline" - quota_request.save() - - return HttpResponse(status=200) diff --git a/backend/core/api/quotas/urls.py b/backend/core/api/quotas/urls.py deleted file mode 100644 index 373da4f99..000000000 --- a/backend/core/api/quotas/urls.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.urls import path - -from . import fetch, requests - -urlpatterns = [ - path( - "fetch//", - fetch.fetch_all_quotas, - name="fetch", - ), - path("submit_request//", requests.submit_request, name="submit_request"), - path("request//approve/", requests.approve_request, name="approve request"), - path("request//decline/", requests.decline_request, name="decline request"), -] - -app_name = "quotas" diff --git a/backend/core/api/settings/api_keys.py b/backend/core/api/settings/api_keys.py deleted file mode 100644 index ca2891a2d..000000000 --- a/backend/core/api/settings/api_keys.py +++ /dev/null @@ -1,69 +0,0 @@ -from django.contrib import messages -from django.http import HttpResponse -from django.shortcuts import render -from django.views.decorators.http import require_http_methods - -from backend.core.api.public import APIAuthToken -from backend.core.service.api_keys.delete import delete_api_key -from backend.core.service.api_keys.generate import generate_public_api_key -from backend.core.service.api_keys.get import get_api_key_by_id -from backend.core.service.permissions.scopes import get_permissions_from_request - -from backend.core.types.requests import WebRequest -from backend.decorators import web_require_scopes - - -@require_http_methods(["POST"]) -@web_require_scopes("api_keys:write") -def generate_api_key_endpoint(request: WebRequest) -> HttpResponse: - name = request.POST.get("name") - expires = request.POST.get("expires") - description = request.POST.get("description") - administrator_toggle = True if request.POST.get("administrator") == "on" else False - administrator_type = request.POST.get("administrator_type") - - permissions: list = get_permissions_from_request(request) - - key_obj, key_response = generate_public_api_key( - request, - request.user.logged_in_as_team or request.user, - name, - permissions, - expires=expires, - description=description, - administrator_toggle=administrator_toggle, - administrator_type=administrator_type, - ) - - if not key_obj: - messages.error(request, key_response) - return render(request, "base/toast.html") - - messages.success(request, "API key generated successfully") - - http_response = render( - request, - "pages/settings/settings/api_key_generated_response.html", - { - "raw_key": key_response, - "name": name, - }, - ) - - http_response.headers["HX-Reswap"] = "beforebegin" - http_response.headers["HX-Retarget"] = 'div[data-hx-container="api_keys"]' - - return http_response - - -@require_http_methods(["DELETE"]) -def revoke_api_key_endpoint(request: WebRequest, key_id: str) -> HttpResponse: - key: APIAuthToken | None = get_api_key_by_id(request.user.logged_in_as_team or request.user, key_id) - - delete_key_response = delete_api_key(request, request.user.logged_in_as_team or request.user, key=key) - - if isinstance(delete_key_response, str): - messages.error(request, "This key does not exist") - else: - messages.success(request, "Successfully revoked the API Key") - return render(request, "base/toast.html") diff --git a/backend/core/api/settings/change_name.py b/backend/core/api/settings/change_name.py deleted file mode 100644 index 03afc3d88..000000000 --- a/backend/core/api/settings/change_name.py +++ /dev/null @@ -1,40 +0,0 @@ -from django.contrib import messages -from django.http import HttpResponse -from django.shortcuts import render -from django.views.decorators.http import require_http_methods - -from backend.core.types.htmx import HtmxHttpRequest - - -@require_http_methods(["POST"]) -def change_account_name(request: HtmxHttpRequest): - if not request.htmx: - return HttpResponse("Invalid Request", status=405) - - htmx_return = "base/toasts.html" - - first_name = request.POST.get("first_name") - last_name = request.POST.get("last_name") - - if not first_name and not last_name: - messages.error(request, "Please enter a valid firstname or lastname.") - return render(request, htmx_return) - - if request.user.first_name == first_name and request.user.last_name == last_name: - messages.warning(request, "You already have this name.") - return render(request, htmx_return) - - if first_name: - request.user.first_name = first_name - - if last_name: - request.user.last_name = last_name - - request.user.save() - - messages.success( - request, - f"Successfully changed your name to {request.user.get_full_name()}", - ) - - return render(request, htmx_return) diff --git a/backend/core/api/settings/defaults.py b/backend/core/api/settings/defaults.py deleted file mode 100644 index 7d8859256..000000000 --- a/backend/core/api/settings/defaults.py +++ /dev/null @@ -1,94 +0,0 @@ -from django.contrib import messages -from django.core.exceptions import ValidationError -from django.http import HttpResponse -from django.shortcuts import render -from django.views.decorators.http import require_http_methods - -from backend.clients.models import Client -from backend.core.service.clients.validate import validate_client -from backend.core.service.defaults.get import get_account_defaults -from backend.core.service.defaults.update import change_client_defaults -from backend.core.types.requests import WebRequest - - -# @require_http_methods(["GET", "PUT"]) -@require_http_methods(["GET", "POST"]) -def handle_client_defaults_endpoints(request: WebRequest, client_id: int | None = None): - if request.method == "GET": - return get_defaults_endpoint(request, client_id) - # elif request.method == "PUT": - elif request.method == "POST": - return change_client_defaults_endpoint(request, client_id) - else: - return HttpResponse("Something went wrong") - - -@require_http_methods(["GET"]) -def get_defaults_endpoint(request: WebRequest, client_id: int | None = None): - context: dict = {} - - if client_id: - try: - client = validate_client(request, client_id, get_defaults=True) - except (ValidationError, Client.DoesNotExist): - return HttpResponse("Something went wrong") - - defaults = get_account_defaults(request.actor, client) - context |= {"client": client} - else: - defaults = get_account_defaults(request.actor) - - return render(request, "pages/clients/detail/client_defaults.html", context | {"defaults": defaults}) - - -# @require_http_methods(["PUT"]) -@require_http_methods(["POST"]) -def change_client_defaults_endpoint(request: WebRequest, client_id: int | None = None): - context: dict = {} - - if client_id: - try: - client = validate_client(request, client_id, get_defaults=True) - except (ValidationError, Client.DoesNotExist): - return HttpResponse("Something went wrong") - - defaults = get_account_defaults(request.actor, client) - context |= {"client": client} - else: - defaults = get_account_defaults(request.actor) - - response = change_client_defaults(request, defaults) - - if response.failed: - messages.error(request, response.error) - return render(request, "base/toast.html") - - messages.success(request, "Successfully updated client defaults") - return render(request, "base/toast.html") - - -@require_http_methods(["DELETE"]) -def remove_client_default_logo_endpoint(request: WebRequest, client_id: int | None = None): - context: dict = {} - - if client_id: - try: - client = validate_client(request, client_id, get_defaults=True) - except (ValidationError, Client.DoesNotExist): - return HttpResponse("Something went wrong") - - defaults = get_account_defaults(request.actor, client) - context |= {"client": client} - else: - defaults = get_account_defaults(request.actor) - - if not defaults.default_invoice_logo: - messages.error(request, "No default logo to remove") - return render(request, "base/toast.html") - - defaults.default_invoice_logo.delete() - - messages.success(request, "Successfully updated client defaults") - resp = render(request, "base/toast.html") - resp["HX-Refresh"] = "true" - return resp diff --git a/backend/core/api/settings/email_templates.py b/backend/core/api/settings/email_templates.py deleted file mode 100644 index 97458738d..000000000 --- a/backend/core/api/settings/email_templates.py +++ /dev/null @@ -1,32 +0,0 @@ -from django.contrib import messages -from django.shortcuts import render -from django.views.decorators.http import require_POST - -from backend.core.service.defaults.get import get_account_defaults -from backend.decorators import web_require_scopes -from backend.core.types.requests import WebRequest - - -@require_POST -@web_require_scopes(["email_templates:write", "account_defaults:write"], True, True) -def save_email_template(request: WebRequest, template: str): - template = template.lower().strip() - content = request.POST.get("content") - - if template not in ["invoice_created", "invoice_overdue", "invoice_cancelled"]: - messages.error(request, f"Invalid template: {template}") - return render(request, "base/toast.html") - - if content is None: - messages.error(request, f"Missing content for template: {template}") - return render(request, "base/toast.html") - - acc_defaults = get_account_defaults(request.actor) - - setattr(acc_defaults, f"email_template_recurring_invoices_{template}", content) - - acc_defaults.save() - - messages.success(request, f"Email template '{template}' saved successfully") - - return render(request, "base/toast.html") diff --git a/backend/core/api/settings/preferences.py b/backend/core/api/settings/preferences.py deleted file mode 100644 index a67e28b93..000000000 --- a/backend/core/api/settings/preferences.py +++ /dev/null @@ -1,48 +0,0 @@ -from django.contrib import messages -from django.http import HttpResponse -from django.shortcuts import render -from django.views.decorators.http import require_http_methods - -from backend.models import UserSettings - - -@require_http_methods(["POST"]) -def update_account_preferences(request): - currency = request.POST.get("currency", None) - try: - usersettings = request.user.user_profile - except UserSettings.DoesNotExist: - usersettings = UserSettings.objects.create(user=request.user) - - htmx_return = "base/toasts.html" - - if not request.htmx and not currency: - return HttpResponse("Invalid Request", status=400) - elif not currency or currency not in usersettings.CURRENCIES: - messages.error(request, "Invalid Currency") - return render(request, htmx_return) - - usersettings.currency = currency - - updated_features: bool = False - - for choice, _ in usersettings.CoreFeatures.choices: - selected: str | None = request.POST.get(f"selected_{choice}", None) - - if choice in usersettings.disabled_features: # currently disabled - if selected: # enabled - updated_features = True - usersettings.disabled_features.remove(choice) - else: - if not selected: # disabled - updated_features = True - usersettings.disabled_features.append(choice) - - usersettings.save(update_fields=["disabled_features", "currency"]) - messages.success(request, "Successfully updated preferences") - - if updated_features: - response = HttpResponse("Success") - response["HX-Refresh"] = "true" - return response - return render(request, htmx_return) diff --git a/backend/core/api/settings/profile_picture.py b/backend/core/api/settings/profile_picture.py deleted file mode 100644 index 27dd71121..000000000 --- a/backend/core/api/settings/profile_picture.py +++ /dev/null @@ -1,29 +0,0 @@ -from django.contrib import messages -from django.shortcuts import redirect, render -from django.views.decorators.http import require_http_methods - -from backend.core.service.settings.update import update_profile_picture, UpdateProfilePictureServiceResponse -from backend.core.service.settings.view import get_user_profile -from backend.core.types.requests import WebRequest - - -@require_http_methods(["POST"]) -def change_profile_picture_endpoint(request: WebRequest): - if not request.htmx: - messages.error(request, "Invalid request") - return redirect("settings:dashboard with page", page="profile") - - user_profile = get_user_profile(request) - - update_response: UpdateProfilePictureServiceResponse = update_profile_picture(request.FILES.get("profile_picture_image"), user_profile) - - if update_response.success: - messages.success(request, update_response.response) - else: - messages.error(request, update_response.error) - - return render( - request, - "pages/settings/settings/_post_profile_pic.html", - {"users_profile_picture": user_profile.profile_picture_url}, - ) diff --git a/backend/core/api/settings/urls.py b/backend/core/api/settings/urls.py deleted file mode 100644 index 56e54a7aa..000000000 --- a/backend/core/api/settings/urls.py +++ /dev/null @@ -1,29 +0,0 @@ -from django.urls import path - -from . import change_name, profile_picture, preferences -from .api_keys import generate_api_key_endpoint, revoke_api_key_endpoint -from .defaults import handle_client_defaults_endpoints, remove_client_default_logo_endpoint -from .email_templates import save_email_template - -urlpatterns = [ - path( - "account_preferences/", - preferences.update_account_preferences, - name="account_preferences", - ), - path( - "change_name/", - change_name.change_account_name, - name="change_name", - ), - path("profile_picture/", profile_picture.change_profile_picture_endpoint, name="update profile picture"), - path("api_keys/generate/", generate_api_key_endpoint, name="api_keys generate"), - path("api_keys/revoke//", revoke_api_key_endpoint, name="api_keys revoke"), - path("client_defaults//", handle_client_defaults_endpoints, name="client_defaults"), - path("client_defaults/", handle_client_defaults_endpoints, name="client_defaults without client"), - path("client_defaults/remove_default_logo/", remove_client_default_logo_endpoint, name="client_defaults remove logo without client"), - path("client_defaults/remove_default_logo/", remove_client_default_logo_endpoint, name="client_defaults remove logo"), - path("email_templates//save/", save_email_template, name="email_template save"), -] - -app_name = "settings" diff --git a/backend/core/api/teams/create.py b/backend/core/api/teams/create.py deleted file mode 100644 index f01172589..000000000 --- a/backend/core/api/teams/create.py +++ /dev/null @@ -1,36 +0,0 @@ -from django.contrib import messages -from django.shortcuts import render -from django.views.decorators.http import require_POST - -from backend.decorators import has_entitlements -from backend.models import Organization, QuotaUsage -from backend.core.types.htmx import HtmxHttpRequest - - -@require_POST -@has_entitlements("organizations") -# @quota_usage_check("teams-count", api=True, htmx=True) -def create_team(request: HtmxHttpRequest): - name = request.POST.get("name") - - if not name: - messages.error(request, "A team name field must be filled.") - return render(request, "partials/messages_list.html") - - if Organization.objects.filter(name=name).exists(): - messages.error(request, "A team with this name already exists.") - return render(request, "partials/messages_list.html") - - team = Organization.objects.create(name=name, leader=request.user) - - QuotaUsage.create_str(request.user, "teams-count", team.id) - QuotaUsage.create_str(request.user, "teams-joined", team.id) - - if not request.user.logged_in_as_team: - request.user.logged_in_as_team = team - request.user.save() - - messages.success(request, f"Successfully created team {name} with the ID of #{team.id}") - response = render(request, "partials/messages_list.html") - response["HX-Refresh"] = "true" - return response diff --git a/backend/core/api/teams/create_user.py b/backend/core/api/teams/create_user.py deleted file mode 100644 index 23c311b93..000000000 --- a/backend/core/api/teams/create_user.py +++ /dev/null @@ -1,37 +0,0 @@ -from django.contrib import messages -from django.shortcuts import render - -from backend.decorators import web_require_scopes -from backend.models import Organization -from backend.core.service.permissions.scopes import get_permissions_from_request -from backend.core.service.teams.create_user import create_user_service -from backend.core.types.requests import WebRequest - - -@web_require_scopes("team:invite", True, True) -def create_user_endpoint(request: WebRequest): - team_id = request.POST.get("team_id", "") - - team: Organization | None = Organization.objects.filter(id=team_id).first() - - if not team: - messages.error(request, "This team does not exist") - return render(request, "base/toast.html") - - if not team.is_owner(request.user): - messages.error(request, "Only the team owner can create users") - return render(request, "base/toast.html") - - first_name = request.POST.get("first_name", "") - last_name = request.POST.get("last_name", "") - email = request.POST.get("email", "") - permissions: list = get_permissions_from_request(request) - - created_user = create_user_service(request, email, team, first_name, last_name, permissions) - - if created_user.failed: - messages.error(request, created_user.error) - return render(request, "base/toast.html") - else: - messages.success(request, f"The account for {first_name} was created successfully. They have been emailed instructions.") - return render(request, "base/toast.html") diff --git a/backend/core/api/teams/edit_permissions.py b/backend/core/api/teams/edit_permissions.py deleted file mode 100644 index 532a57a06..000000000 --- a/backend/core/api/teams/edit_permissions.py +++ /dev/null @@ -1,31 +0,0 @@ -from django.contrib import messages -from django.http import HttpResponse -from django.shortcuts import render -from django.views.decorators.http import require_http_methods - -from backend.decorators import web_require_scopes -from backend.models import User -from backend.core.service.permissions.scopes import get_permissions_from_request -from backend.core.service.teams.permissions import edit_member_permissions -from backend.core.types.requests import WebRequest - - -@require_http_methods(["POST"]) -@web_require_scopes("team_permissions:write") -def edit_user_permissions_endpoint(request: WebRequest) -> HttpResponse: - permissions: list = get_permissions_from_request(request) - user_id = request.POST.get("user_id") - - receiver: User | None = User.objects.filter(id=user_id).first() - - if not receiver: - messages.error(request, "Invalid user") - return render(request, "base/toast.html") - - edit_response = edit_member_permissions(receiver, request.user.logged_in_as_team, permissions) - - if edit_response.success: - messages.success(request, "User permissions saved successfully") - else: - messages.error(request, edit_response.error) - return render(request, "base/toast.html") diff --git a/backend/core/api/teams/invites.py b/backend/core/api/teams/invites.py deleted file mode 100644 index 7555fa4f0..000000000 --- a/backend/core/api/teams/invites.py +++ /dev/null @@ -1,216 +0,0 @@ -from textwrap import dedent - -from django.contrib import messages -from django.http import HttpResponse -from django.shortcuts import render -from django.urls import reverse - -from backend.core.models import QuotaLimit -from backend.decorators import web_require_scopes -from backend.models import Notification, Organization, TeamInvitation, User -from backend.core.types.htmx import HtmxHttpRequest -from settings.helpers import send_email - - -def delete_notification(user: User, code: TeamInvitation): - notification = Notification.objects.filter( - user=user, - message="New Organization Invite", - action="modal", - action_value="accept_invite", - extra_type="accept_invite_with_code", - extra_value=code, - ).first() - - if notification: - notification.delete() - - -def check_team_invitation_is_valid(request, invitation: TeamInvitation, code=None): - valid: bool = True - - if not invitation.is_active(): - valid = False - messages.error(request, "Invitation has expired") - - try: - quota_limit = QuotaLimit.objects.get(slug="teams-user_count") - if invitation.team.members.count() >= quota_limit.get_quota_limit(invitation.team.leader): - valid = False - messages.error(request, "Unfortunately this team is currently full") - except QuotaLimit.DoesNotExist: - valid = False - messages.error(request, "Something went wrong with fetching the quota limit") - - if not valid: - delete_notification(request.user, code) - return False - - return True - - -@web_require_scopes("team:invite", True, True) -def send_user_team_invite(request: HtmxHttpRequest): - user_email = request.POST.get("email") - team_id = request.POST.get("team_id", "") - team: Organization | None = Organization.objects.filter(leader=request.user, id=team_id).first() - - def return_error_notif(request: HtmxHttpRequest, message: str, autohide=None): - messages.error(request, message) - context = {"autohide": False} if autohide is False else {} - resp = render(request, "partials/messages_list.html", context=context, status=200) - resp["HX-Trigger-After-Swap"] = "invite_user_error" - return resp - - if not user_email: - return return_error_notif(request, "Please enter a valid user email") - - if not team: - return return_error_notif(request, "You are not the leader of this team") - - user: User | None = User.objects.filter(email=user_email).first() - - if not user: - return return_error_notif(request, 'User not found. Either ask them to create an account or press "Create User"') - - if user.teams_joined.filter(pk=team_id).exists(): - return return_error_notif(request, "User already is in this team") - - try: - quota_limit = QuotaLimit.objects.get(slug="teams-user_count") - if team.members.count() >= quota_limit.get_quota_limit(team.leader): - return return_error_notif( - request, - "Unfortunately your team has reached the maximum members limit. Go to the service quotas " - "page to request a higher number or kick some users to make space.", - autohide=False, - ) - except QuotaLimit.DoesNotExist: - return return_error_notif(request, "Something went wrong with fetching the quota limit") - - invitation = TeamInvitation.objects.create(team=team, user=user, invited_by=request.user) - - Notification.objects.create( - user=user, - message=f"New Organization Invite", - action="modal", - action_value="accept_invite", - extra_type="accept_invite_with_code", - extra_value=invitation.code, - ) - - send_email( - destination=user.email, - subject="New Organization Invite", - content=dedent( - f""" - Hi {user.first_name or "User"}, - - {request.user.first_name or f"User {request.user.email}"} has invited you to join the organization \"{team.name}\" (#{team.id}) - - - Click the url below to accept the invite! - {request.build_absolute_uri(reverse("api:teams:join accept", kwargs={"code": invitation.code}))} - - Didn't give permission to be added to this organization? You can safely ignore the email, no actions can be done on - behalf of you without your action. - """ - ), - ) - - messages.success(request, "Invitation successfully sent") - response = HttpResponse(status=200) - response["HX-Refresh"] = "true" - return response - - -def accept_team_invite(request: HtmxHttpRequest, code): - invitation: TeamInvitation | None = TeamInvitation.objects.filter(code=code).prefetch_related("team").first() - - if not invitation: - messages.error(request, "Invalid Invite Code") - # Force break early to avoid "no invitation" on invitation.code - delete_notification(request.user, code) - return render(request, "partials/messages_list.html") - - if not check_team_invitation_is_valid(request, invitation, code): - messages.error(request, "Invalid invite - Maybe it has expired?") - return render(request, "partials/messages_list.html") - - if request.user.teams_joined.filter(pk=invitation.team_id).exists(): - messages.error(request, "You are already in this team") - response = render(request, "partials/messages_list.html", status=200) - response["HX-Trigger-After-Swap"] = "accept_invite_error" - return response - - invitation.team.members.add(request.user) - - notification = Notification.objects.filter( - user=request.user, - action="modal", - action_value="accept_invite", - extra_type="accept_invite_with_code", - extra_value=code, - ).first() - - if notification: - notification.delete() - - Notification.objects.create( - user=request.user, - message=f"You have now joined the team {invitation.team.name}", - action="normal", - ) - - Notification.objects.create( - user=invitation.invited_by, - message=f"{request.user.username} has joined your team", - action="normal", - ) - - invitation.delete() - - messages.success(request, f"You have successfully joined the team {invitation.team.name}") - response = HttpResponse(status=200) - response["HX-Refresh"] = "true" - return response - # return render(request, "partials/messages_list.html") - - -def decline_team_invite(request: HtmxHttpRequest, code): - invitation: TeamInvitation | None = TeamInvitation.objects.filter(code=code).first() - confirmation_text = request.POST.get("confirmation_text") - - if not invitation: - messages.error(request, "Invalid Invite Code") - # Force break early to avoid "no invitation" on invitation.code - delete_notification(request.user, code) - return render(request, "partials/messages_list.html") - - if not check_team_invitation_is_valid(request, invitation, code): - return render(request, "partials/messages_list.html") - - # if confirmation_text != "i confirm i want to decline " + invitation.team.name: - # messages.error(request, "Invalid confirmation text") - # return redirect("teams:dashboard join", code=code) # kwargs={"code": code}) - - invitation.team.members.remove(request.user) - - Notification.objects.create( - user=request.user, - message=f"You have declined the team invitation", - action="normal", - ) - - Notification.objects.create( - user=invitation.invited_by, - message=f"{request.user.username} has declined the team invitation", - action="normal", - ) - - delete_notification(request.user, code) - - invitation.delete() - messages.success(request, "You have successfully declined the team invitation") - - return render(request, "partials/messages_list.html") diff --git a/backend/core/api/teams/kick.py b/backend/core/api/teams/kick.py deleted file mode 100644 index e15d26ae8..000000000 --- a/backend/core/api/teams/kick.py +++ /dev/null @@ -1,33 +0,0 @@ -from django.contrib import messages -from django.http import HttpRequest -from django.shortcuts import redirect - -from backend.decorators import web_require_scopes -from backend.models import User, Organization - - -@web_require_scopes("team:kick", True, True) -def kick_user(request: HttpRequest, user_id): - user: User | None = User.objects.filter(id=user_id).first() - confirmation_text = request.POST.get("confirmation_text") - if not user: - messages.error(request, "User not found") - return redirect("teams:dashboard") - - if confirmation_text != f"i confirm i want to kick {user.username}": - messages.error(request, "Invalid confirmation") - return redirect("teams:dashboard") - - team: Organization | None = user.teams_joined.first() - if not team: - messages.error(request, "User is not apart of your team") - return redirect("teams:dashboard") - - if team.leader != request.user: - messages.error(request, "You don't have the required permissions to kick this user") - return redirect("teams:dashboard") - - team.members.remove(user) - messages.success(request, f"Successfully kicked {user.username}") - - return redirect("teams:dashboard") diff --git a/backend/core/api/teams/leave.py b/backend/core/api/teams/leave.py deleted file mode 100644 index f5a717d97..000000000 --- a/backend/core/api/teams/leave.py +++ /dev/null @@ -1,32 +0,0 @@ -from django.contrib import messages -from django.http import HttpResponse -from django.shortcuts import render - -from backend.models import Organization -from backend.core.types.htmx import HtmxHttpRequest - - -def return_error_notif(request: HtmxHttpRequest, message: str): - messages.error(request, message) - resp = render(request, "partials/messages_list.html", status=200) - resp["HX-Trigger-After-Swap"] = "leave_team_error" - return resp - - -def leave_team_confirmed(request: HtmxHttpRequest, team_id): - team: Organization | None = Organization.objects.filter(id=team_id).first() - - if not team: - return return_error_notif(request, "Team not found") - - if team.leader == request.user: # may be changed in the future. If no members allow delete - return return_error_notif(request, "You cannot leave your own team") - - if request.user.teams_joined.filter(id=team_id).exists(): - team.members.remove(request.user) - messages.success(request, f"You have successfully left the team {team.name}") - response = HttpResponse(status=200) - response["HX-Refresh"] = "true" - return response - else: - return return_error_notif(request, "You are not a member of this team") diff --git a/backend/core/api/teams/switch_team.py b/backend/core/api/teams/switch_team.py deleted file mode 100644 index f1a12eda3..000000000 --- a/backend/core/api/teams/switch_team.py +++ /dev/null @@ -1,53 +0,0 @@ -from django.contrib import messages -from django.http import HttpResponse -from django.shortcuts import render - -from backend.models import Organization -from backend.core.types.htmx import HtmxHttpRequest - - -def switch_team(request: HtmxHttpRequest, team_id: str | int | None = None): - if not team_id: - team_id = request.POST.get("join_team", None) - - if not team_id: - if not request.user.logged_in_as_team: - messages.warning(request, "You are not logged into an organization") - else: - messages.success(request, "You are now logged into your personal account") - - request.user.logged_in_as_team = None - request.user.save() - response = HttpResponse(status=200) - response["HX-Refresh"] = "true" - return response - - team: Organization | None = Organization.objects.filter(id=team_id).first() - - if not team: - messages.error(request, "Team not found") - return render(request, "partials/messages_list.html") - - if request.user.logged_in_as_team == team: - messages.error(request, "You are already logged in for this team") - return render(request, "partials/messages_list.html") - - if not request.user.teams_leader_of.filter(id=team_id).exists() and not request.user.teams_joined.filter(id=team_id).exists(): - messages.error(request, "You are not a member of this team") - return render(request, "partials/messages_list.html") - - messages.success(request, f"Now signing into the organization '{team.name}'") - request.user.logged_in_as_team = team - request.user.save() - - response = HttpResponse(status=200) - response["HX-Refresh"] = "true" - return response - # return render(request, "components/+logged_in_for.html") - - -def get_dropdown(request: HtmxHttpRequest): - if not request.htmx: - return HttpResponse("Invalid Request", status=405) - - return render(request, "base/topbar/_organizations_list.html") diff --git a/backend/core/api/teams/urls.py b/backend/core/api/teams/urls.py deleted file mode 100644 index ef6988400..000000000 --- a/backend/core/api/teams/urls.py +++ /dev/null @@ -1,58 +0,0 @@ -from django.urls import path - -from . import kick, switch_team, invites, leave, create, edit_permissions -from .create_user import create_user_endpoint - -urlpatterns = [ - path("edit_permissions/", edit_permissions.edit_user_permissions_endpoint, name="edit_permissions"), - path( - "kick/", - kick.kick_user, - name="kick", - ), - path( - "switch_team//", - switch_team.switch_team, - name="switch_team", - ), - path( - "switch_team/", - switch_team.switch_team, - name="switch_team input", - ), - path( - "create_user/", - create_user_endpoint, - name="create_user", - ), - # INVITES # - path( - "invite/", - invites.send_user_team_invite, - name="invite", - ), - path( - "join//accept/", - invites.accept_team_invite, - name="join accept", - ), - path( - "join//decline/", - invites.decline_team_invite, - name="join decline", - ), - # LEAVE TEAM # - path( - "leave//confirm/", - leave.leave_team_confirmed, - name="leave confirm", - ), - path( - "create/", - create.create_team, - name="create", - ), - path("get_dropdown/", switch_team.get_dropdown, name="get_dropdown"), -] - -app_name = "teams" diff --git a/backend/core/api/urls.py b/backend/core/api/urls.py deleted file mode 100644 index 1249767a7..000000000 --- a/backend/core/api/urls.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import annotations - -from django.urls import include -from django.urls import path - -urlpatterns = [ - path("base/", include("backend.core.api.base.urls")), - path("teams/", include("backend.core.api.teams.urls")), - path("settings/", include("backend.core.api.settings.urls")), - path("quotas/", include("backend.core.api.quotas.urls")), - path("clients/", include("backend.clients.api.urls")), - path("emails/", include("backend.core.api.emails.urls")), - path("maintenance/", include("backend.core.api.maintenance.urls")), - path("landing_page/", include("backend.core.api.landing_page.urls")), - path("public/", include("backend.core.api.public.urls")), - path("", include("backend.finance.api.urls")), -] - -app_name = "api" diff --git a/backend/core/data/default_feature_flags.py b/backend/core/data/default_feature_flags.py deleted file mode 100644 index fa5905367..000000000 --- a/backend/core/data/default_feature_flags.py +++ /dev/null @@ -1,26 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass - - -@dataclass -class FeatureFlag: - name: str - description: str - default: bool - - -default_feature_flags: list[FeatureFlag] = [ - FeatureFlag(name="areSignupsEnabled", description="Are new account creations allowed", default=True), - FeatureFlag( - name="isInvoiceSchedulingEnabled", - description="Invoice Scheduling allows for clients to create invoice schedules that send and invoice at a specific date.", - default=False, - ), - FeatureFlag(name="areUserEmailsAllowed", description="Are users allowed to send emails from YOUR DOMAIN to customers", default=False), - FeatureFlag( - name="areInvoiceRemindersEnabled", - description="Invoice Reminders allow for clients to be reminded to pay an invoice.", - default=False, - ), -] diff --git a/backend/core/data/default_quota_limits.py b/backend/core/data/default_quota_limits.py deleted file mode 100644 index 2e1791ffa..000000000 --- a/backend/core/data/default_quota_limits.py +++ /dev/null @@ -1,186 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from typing import Literal - - -@dataclass -class QuotaItem: - slug: str - name: str - description: str - default_value: int - adjustable: bool - period: Literal[ - "forever", - "per_month", - "per_minute", - "per_hour", - "per_day", - "per_client", - "per_invoice", - "per_team", - "per_quota", - "per_bulk_send", - "per_email", - ] - - -@dataclass -class QuotaGroup: - name: str - items: list[QuotaItem] - - -default_quota_limits: list[QuotaGroup] = [ - QuotaGroup( - "invoices", - [ - QuotaItem( - slug="count", - name="Creations per month", - description="Amount of invoices created per month", - default_value=100, - period="per_month", - adjustable=True, - ), - QuotaItem( - slug="schedules", - name="Schedules per month", - description="Amount of invoice scheduled sends allowed per month", - default_value=100, - period="per_month", - adjustable=True, - ), - QuotaItem( - slug="access_codes", - name="Created access codes", - description="Amount of invoice access codes allowed per invoice", - default_value=3, - period="per_invoice", - adjustable=True, - ), - ], - ), - QuotaGroup( - "receipts", - [ - QuotaItem( - slug="count", - name="Created receipts", - description="Amount of receipts stored per month", - default_value=100, - period="per_month", - adjustable=True, - ) - ], - ), - QuotaGroup( - "clients", - [ - QuotaItem( - slug="count", - name="Created clients", - description="Amount of clients stored in total", - default_value=40, - period="forever", - adjustable=True, - ) - ], - ), - QuotaGroup( - "teams", - [ - QuotaItem( - slug="count", - name="Created teams", - description="Amount of teams created in total", - default_value=3, - period="forever", - adjustable=True, - ), - QuotaItem( - slug="joined", - name="Joined teams", - description="Amount of teams that you have joined in total", - default_value=5, - period="forever", - adjustable=True, - ), - QuotaItem( - slug="user_count", - name="Users per team", - description="Amount of users per team", - default_value=10, - period="per_team", - adjustable=True, - ), - ], - ), - QuotaGroup( - "quota_increase", - [ - QuotaItem( - slug="request", - name="Quota Increase Request", - description="Amount of increase requests allowed per quota", - default_value=1, - period="per_quota", - adjustable=False, - ), - QuotaItem( - slug="requests_per_month_per_quota", - name="Quota Increase Requests per month", - description="Amount of increase requests allowed per month per quota", - period="per_quota", - default_value=1, - adjustable=False, - ), - ], - ), - QuotaGroup( - "emails", - [ - QuotaItem( - slug="single-count", - name="Single Email Sends", - description="Amount of single email sends allowed per month", - period="per_month", - default_value=10, - adjustable=True, - ), - QuotaItem( - slug="bulk-count", - name="Bulk Email Sends", - description="Amount of 'Bulk Emails' allowed to be sent per month", - period="per_month", - default_value=1, - adjustable=True, - ), - QuotaItem( - slug="bulk-max_sends", - name="Bulk Email Maximum Emails", - description="Maximum amount of emails allowed to be sent per 'Bulk' request", - period="per_bulk_send", - default_value=10, - adjustable=True, - ), - QuotaItem( - slug="email_character_count", - name="Maximum Character Count", - description="Maximum amount of characters allowed in an email", - period="per_email", - default_value=1000, - adjustable=True, - ), - QuotaItem( - slug="complaints", - name="Complaints allowed", - description="Maximum amount of complaints allowed before your account will be blocked from sending emails", - period="forever", - default_value=2, - adjustable=True, - ), - ], - ), -] diff --git a/backend/core/management/commands/auto.py b/backend/core/management/commands/auto.py deleted file mode 100644 index ff7659659..000000000 --- a/backend/core/management/commands/auto.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.core.management.base import BaseCommand -from backend.core.service.maintenance.expire.run import expire_and_cleanup_objects - - -class Command(BaseCommand): - """ - Runs automation scripts to make sure objects are up to date, expired objects are deleted, etc. - """ - - def handle(self, *args, **kwargs): - self.stdout.write("Running expire + cleanup script...") - self.stdout.write(expire_and_cleanup_objects()) diff --git a/backend/core/management/commands/contributors.json b/backend/core/management/commands/contributors.json deleted file mode 100644 index 2699ea5a7..000000000 --- a/backend/core/management/commands/contributors.json +++ /dev/null @@ -1,266 +0,0 @@ -[ - { - "name": "Trey", - "username": "TreyWW", - "role": "Project Lead", - "tags": [ - "👑", - "🖥" - ] - }, - { - "name": "Slawek Bierwiaczonek", - "username": "Domejko", - "role": "Development & CI & Bug Fixing", - "tags": [ - "🧪", - "🖥", - "🐞" - ] - }, - { - "name": "Sergey G", - "username": "introkun", - "role": "Development & CI", - "tags": [ - "♻", - "🧪", - "🖥", - "🎨" - ] - }, - { - "name": "Jacob", - "username": "Z3nKrypt", - "role": "Documentation", - "tags": [ - "📖" - ] - }, - { - "name": "Tom", - "username": "tomkinane", - "role": "Design", - "tags": [ - "🎨" - ] - }, - { - "name": "SharonAliyas5573", - "username": "SharonAliyas5573", - "role": "Development", - "tags": [ - "🖥" - ] - }, - { - "name": "romana-la", - "username": "romana-la", - "role": "Documentation", - "tags": [ - "📖" - ] - }, - { - "name": "flyingdev", - "username": "flyingdev", - "role": "CI", - "tags": [ - "🧪" - ] - }, - { - "name": "chavi362", - "username": "chavi362", - "role": "Documentation", - "tags": [ - "📖" - ] - }, - { - "name": "bermr", - "username": "bermr", - "role": "CI", - "tags": [ - "🧪" - ] - }, - { - "name": "PhilipZara", - "username": "PhilipZara", - "role": "Design", - "tags": [ - "🎨" - ] - }, - { - "name": "Tianrui-Luo", - "username": "Tianrui-Luo", - "role": "Development", - "tags": [ - "🖥" - ] - }, - { - "name": "HarryHuCodes", - "username": "HarryHuCodes", - "role": "Development", - "tags": [ - "🖥" - ] - }, - { - "name": "Nuova", - "username": "Nuovaxu", - "role": "Development", - "tags": [ - "🖥" - ] - }, - { - "name": "HessTaha", - "username": "HessTaha", - "role": "CI-CD", - "tags": [ - "🐳" - ] - }, - { - "name": "wnm210", - "username": "wnm210", - "role": "Design", - "tags": [ - "🎨" - ] - }, - { - "name": "Matt", - "username": "matthewjuarez1", - "role": "Full Stack", - "tags": [ - "🖥", - "🎨" - ] - }, - { - "name": "SBMOYO", - "username": "SBMOYO", - "role": "CI", - "tags": [ - "🧪" - ] - }, - { - "name": "Kevin Liu", - "username": "kliu6151", - "role": "Development", - "tags": [ - "🖥" - ] - }, - { - "name": "Jehad Altoutou", - "username": "HappyLife2", - "role": "Design", - "tags": [ - "🎨" - ] - }, - { - "name": "Samuel P", - "username": "spalominor", - "role": "Design", - "tags": [ - "🎨" - ] - }, - { - "name": "Sabari Ragavendra CK", - "username": "CKsabari2001", - "role": "Layout", - "tags": [ - "🎨" - ] - }, - { - "name": "atulanand25", - "username": "atulanand25", - "role": "Full Stack", - "tags": [ - "🎨", - "🖥", - "🐞" - ] - }, - { - "name": "ryansurf", - "username": "ryansurf", - "role": "Bug Fixes", - "tags": [ - "🐞" - ] - }, - { - "name": "David", - "username": "blocage", - "role": "Refactoring", - "tags": "♻" - }, - { - "name": "Guillermo", - "username": "glizondo", - "role": "Bug Fixes", - "tags": [ - "🐞" - ] - }, - { - "name": "Marvin Lopez", - "username": "marvinl803", - "role": "Full Stack", - "tags": [ - "🎨", - "🖥" - ] - }, - { - "name": "Artem Kolpakov", - "username": "artkolpakov", - "role": "Bug Fixes", - "tags": [ - "🐞" - ] - }, - { - "name": "Yadu", - "username": "Yadu-M", - "role": "Development", - "tags": [ - "🖥" - ] - }, - { - "name": "Vatsal", - "username": "vatsaaaal", - "role": "Development", - "tags": [ - "🖥" - ] - }, - { - "name": "Hussein", - "username": "hussein-mamane", - "role": "Development", - "tags": [ - "🖥" - ] - }, - { - "name": "pvvramakrishnarao234", - "username": "pvvramakrishnarao234", - "role": "Development", - "tags": [ - "🖥" - ] - } -] diff --git a/backend/core/management/commands/contributors.py b/backend/core/management/commands/contributors.py deleted file mode 100644 index 8c2915eb6..000000000 --- a/backend/core/management/commands/contributors.py +++ /dev/null @@ -1,295 +0,0 @@ -import json -import os -from typing import TypedDict - -from django.core.management.base import BaseCommand -from django.utils.termcolors import colorize - - -class ContributorsItem(TypedDict): - name: str - username: str - role: str - tags: list[str] - - -class Command(BaseCommand): - """ - Adds contributors HTML table to README.md file. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.script_dir = os.path.dirname(os.path.abspath(__file__)) - self.contributors_json_path = os.path.join(self.script_dir, "contributors.json") - self.readme_path = "README.md" - - def add_arguments(self, parser): - parser.add_argument("action", type=str, help="sync, list, add, edit") - - parser.add_argument("--sort", type=str, help="Sort by: name, username or role") - parser.add_argument("--limit", type=int, default=20, help="Limit results by amount") - - parser.add_argument("name", type=str, nargs="?", help="users human/readable name") - parser.add_argument("--name", type=str, help="users human/readable name") - parser.add_argument("username", type=str, nargs="?", help="github username") - parser.add_argument("--username", type=str, help="github username") - parser.add_argument("role", type=str, nargs="?", help="role in team") - parser.add_argument("--role", type=str, help="role in team") - parser.add_argument("tags", type=str, nargs="*", help="comma separated list of tags") - parser.add_argument("--tags", type=str, help="comma separated list of tags") - - def handle(self, *args, **kwargs): - action = kwargs["action"] - - if action == "sync": - self.sync_contributors() - elif action == "list": - self.list_contributors(*args, **kwargs) - elif action == "add": - self.add_contributor(*args, **kwargs) - elif action == "help": - self.stdout.write( - colorize( - """ -Please provide valid action. -- sync -- list --sort --limit -- add - to use multi-word usernames or names, surround with quotes - tags can be space separated - to change the order use --, e.g. --name bob - """, - fg="red", - opts=("bold",), - ) - ) - else: - self.stdout.write(colorize("Please provide valid action. \n - sync \n - list \n - add \n - help", fg="red", opts=("bold",))) - - def add_contributor(self, *args, **kwargs): - name: str = kwargs.get("name") - username: str = kwargs.get("username") - role: str = kwargs.get("role") - tags: list[str] = kwargs.get("tags") - - if not name or not username or not role or not tags: - return self.stdout.write(colorize("Please provide name, username, role and tags", fg="red", opts=("bold",))) - - if not all([t in ["👑", "🖥", "🎨", "📖", "🐳", "♻", "🐞"] for t in tags]): - return self.stdout.write( - colorize(f"Please provide valid tags. Valid tags are: 👑, 🖥, 🎨, 📖, 🐳, ♻, 🐞", fg="red", opts=("bold",)) - ) - - contributors_data: list[ContributorsItem] = self._read_contributor_file() - - for user in contributors_data: - if user["username"] == username: - return self.stdout.write(colorize("User already exists", fg="red", opts=("bold",))) - - contributor_obj = ContributorsItem(name=name, username=username, role=role, tags=tags) - - if contributors_data: - contributors_data.append(contributor_obj) - else: - return self.stdout.write( - colorize("contributors.json file not found. Please make sure the file exists.", fg="red", opts=("bold",)) - ) - - self._save_contributors_file(contributors_data) - - def list_contributors(self, *args, **kwargs): - contributors_data: list[ContributorsItem] | None = self._read_contributor_file() - - if not contributors_data: - return - - if kwargs.get("sort") == "name": - contributors_data = sorted(contributors_data, key=lambda d: d.get("name", "")) - elif kwargs.get("sort") == "username": - contributors_data = sorted(contributors_data, key=lambda d: d.get("username", "")) - elif kwargs.get("sort") == "role": - contributors_data = sorted(contributors_data, key=lambda d: d.get("role", "")) - - if limit := kwargs.get("limit"): - bef_count = len(contributors_data) - contributors_data = contributors_data[:limit] - aft_count = len(contributors_data) - - max_w_name = max(len(d.get("name", "")) + 4 for d in contributors_data) - max_w_username = max(len(d.get("username", "")) + 4 for d in contributors_data) - max_w_role = max(len(d.get("role", "")) + 4 for d in contributors_data) - - row_str = "{:<{max_w_name}} {:<{max_w_username}} {:<{max_w_role}} {:<10}" - - header = row_str.format( - "Name", "Username", "Role", "Tags", max_w_name=max_w_name, max_w_username=max_w_username, max_w_role=max_w_role - ) - - self.stdout.write(header) - - for user in contributors_data: - row = row_str.format( - user.get("name"), - user.get("username"), - user.get("role"), - " ".join(user.get("tags")), - max_w_name=max_w_name, - max_w_username=max_w_username, - max_w_role=max_w_role, - ) - self.stdout.write(row) - - if limit: - # noinspection PyUnboundLocalVariable - self.stdout.write(f"\nShowing {aft_count} of {bef_count} contributors\n") - - def sync_contributors(self): - contributors_data = self._read_contributor_file() - - if not contributors_data: - return - - # Path to contributors.json file in the same directory as the script - - # HTML template for each contributor entry - contributor_template = """ - - - -
- - {name} - -
-
- {tags} - - """ - - # Function to generate HTML for a contributor - def generate_contributor_html(contributor): - tags_html = "".join( - [ - f'{tag_icon}' - for tag_icon, tag_link, tag_title in [ - ( - "👑", - f'https://github.com/TreyWW/MyFinances/pulls?q=user%3A{contributor["username"]}', - "Project Lead", - ), - ( - "🖥", - f'https://github.com/TreyWW/MyFinances/pulls?q=is%3Apr+author%3A{contributor["username"]}', - "Backend", - ), - ( - "📖", - f'https://github.com/TreyWW/MyFinances/pulls?q=is%3Apr+author%3A{contributor["username"]}', - "Documentation", - ), - ( - "🎨", - f'https://github.com/TreyWW/MyFinances/pulls?q=is%3Apr+author%3A{contributor["username"]}', - "Frontend", - ), - ( - "🐞", - f'https://github.com/TreyWW/MyFinances/pulls?q=is%3Apr+author%3A{contributor["username"]}', - "Bug Fixes", - ), - ( - "🧪", - f'https://github.com/TreyWW/MyFinances/pulls?q=is%3Apr+author%3A{contributor["username"]}', - "Added Tests", - ), - ( - "🐳", - f'https://github.com/TreyWW/MyFinances/pulls?q=is%3Apr+author%3A{contributor["username"]}', - "Docker", - ), - ( - "♻", - f'https://github.com/TreyWW/MyFinances/pulls?q=is%3Apr+author%3A{contributor["username"]}', - "Refactored Files", - ), - ] - if tag_icon in contributor.get("tags", []) - ] - ) - return contributor_template.format( - username=contributor["username"], - name=contributor["name"], - role=contributor.get("role"), - tags=tags_html, - ) - - # Generate HTML for contributors - contributors_html = "" - users_per_row = 5 - for index, contributor in enumerate(contributors_data, start=1): - if (index - 1) % users_per_row == 0 and index > 1: - contributors_html += "" - contributors_html += f"{generate_contributor_html(contributor)}" - - # Wrap the entire content in a table row - contributors_html = f"{contributors_html}" - - try: - # Read the README.md file - with open(self.readme_path, encoding="utf-8") as readme_file: - readme_content = readme_file.read() - except FileNotFoundError: - self.stderr.write(self.style.ERROR("README.md file not found. Please make sure the file exists.")) - return - - # Insert the generated HTML between the comments - start_comment = "" - end_comment = "" - start_index = readme_content.find(start_comment) + len(start_comment) - end_index = readme_content.find(end_comment) - - new_readme_content = readme_content[:start_index] + "\n\n" + contributors_html + "
\n" + readme_content[end_index:] - - self._save_readme_file(new_readme_content) - - self.stdout.write(self.style.SUCCESS("HTML table inserted into README.md successfully.")) - - def _read_contributor_file(self) -> list[ContributorsItem] | None: - try: - # Load JSON data from contributors.json file - with open(self.contributors_json_path, encoding="utf-8") as json_file: - contributors_json = json_file.read() - return json.loads(contributors_json) - except FileNotFoundError: - self.stderr.write(self.style.ERROR("contributors.json file not found. Please make sure the file exists.")) - return None - except json.JSONDecodeError: - self.stderr.write(self.style.ERROR("Error decoding JSON data from contributors.json file. Please check the file contents.")) - return None - - def _read_readme_file(self) -> str | None: - try: - # Read the README.md file - with open(self.readme_path, encoding="utf-8") as readme_file: - return readme_file.read() - except FileNotFoundError: - self.stderr.write(self.style.ERROR("README.md file not found. Please make sure the file exists.")) - return None - - def _save_contributors_file(self, contributors_data: list[ContributorsItem]): - try: - # Save JSON data to contributors.json file - with open(self.contributors_json_path, "w", encoding="utf-8") as json_file: - json.dump(contributors_data, json_file, indent=4, ensure_ascii=False) - except FileNotFoundError: - self.stderr.write(self.style.ERROR("contributors.json file not found. Please make sure the file exists.")) - return - except json.JSONDecodeError: - self.stderr.write(self.style.ERROR("Error encoding JSON data to contributors.json file. Please check the file contents.")) - return - - def _save_readme_file(self, new_readme_content): - with open(self.readme_path, "w", encoding="utf-8") as readme_file: - readme_file.write(new_readme_content) diff --git a/backend/core/management/commands/feature_flags.py b/backend/core/management/commands/feature_flags.py deleted file mode 100644 index 3c68e2e4b..000000000 --- a/backend/core/management/commands/feature_flags.py +++ /dev/null @@ -1,55 +0,0 @@ -from django.core.management.base import BaseCommand -from django.db.models.functions import Length -from django.utils.termcolors import colorize -from backend.models import FeatureFlags - - -class Command(BaseCommand): - help = "Manage the feature flag statuses" - - def add_arguments(self, parser): - parser.add_argument("action", type=str, help="enable, disable or list") - parser.add_argument("flag", type=str, nargs="?", help="feature flag name") - # parser.add_argument("-f", type=str, dest="flag", help="feature flag name") - - # - def handle(self, *args, **kwargs): - if kwargs["action"] == "list": - flags = FeatureFlags.objects.annotate(name_len=Length("name"), description_len=Length("description")) - width = flags.order_by("-name_len").first().name_len + 4 - description_width = flags.order_by("-description_len").first().description_len + 4 - - header = "{:<{width}} {:<10} {:<{description_width}} {:<20}".format( - "Name", "Enabled", "Description", "Last updated", width=width, description_width=description_width - ) - self.stdout.write("Feature flags:") - self.stdout.write(header) - - for flag in FeatureFlags.objects.all(): - value = "✔" if flag.value else "❌" - - formatted_date = flag.updated_at.strftime("%Y-%m-%d %H:%M:%S") - row = "{:<{width}} {:<10} {:<{description_width}} {:<20}".format( - flag.name, value, flag.description or "No description", formatted_date, width=width, description_width=description_width - ) - self.stdout.write(row) - return - - if not kwargs["flag"]: - self.stdout.write( - colorize("Please provide a feature flag name with `feature_flags enable|disable `", fg="red", opts=("bold",)) - ) - return - - try: - flag = FeatureFlags.objects.get(name=kwargs["flag"]) - - if kwargs["action"] == "enable": - flag.enable() - self.stdout.write(f"[👍] Feature flag {kwargs['flag']} has been enabled") - elif kwargs["action"] == "disable": - flag.disable() - self.stdout.write(f"[👍] Feature flag {kwargs['flag']} has been disabled") - except FeatureFlags.DoesNotExist: - self.stdout.write(colorize("Feature flag with the name of `{kwargs['flag']}` does not exist", fg="red", opts=("bold",))) - return diff --git a/backend/core/management/commands/generate_aws_scheduler_apikey.py b/backend/core/management/commands/generate_aws_scheduler_apikey.py deleted file mode 100644 index c502797ec..000000000 --- a/backend/core/management/commands/generate_aws_scheduler_apikey.py +++ /dev/null @@ -1,32 +0,0 @@ -import uuid -from django.core.management.base import BaseCommand -from backend.core.api.public import APIAuthToken - - -class Command(BaseCommand): - """ - Generates an API key for the AWS EventBridge API. - """ - - def handle(self, *args, **kwargs): - token = APIAuthToken(service=APIAuthToken.AdministratorServiceTypes.AWS_API_DESTINATION, name=str(uuid.uuid4())) - raw_key: str = token.generate_key() - token.save() - - self.stdout.write( - f""" - NOTE: Keep this key secret. It is used to authenticate your API requests with the AWS EventBridge API. - - Your API Key: {raw_key} - - To use this API Key for development you can use: - - pulumi config set api_destination-api_key {raw_key} - pulumi up - - If you would like to use it for production use: - pulumi stack select production - pulumi config set api_destination-api_key {raw_key} - pulumi up - """ - ) diff --git a/backend/core/management/commands/lint.py b/backend/core/management/commands/lint.py deleted file mode 100644 index cb401e7bf..000000000 --- a/backend/core/management/commands/lint.py +++ /dev/null @@ -1,32 +0,0 @@ -import subprocess - -from django.core.management import BaseCommand -from django.utils.termcolors import colorize - - -class Command(BaseCommand): - help = "Run linters" - requires_system_checks = [] - requires_migrations_checks = False - - def add_arguments(self, parser): - parser.add_argument("action", type=str, nargs="?", help="djlint or black") # parser.add_argument("-f", type=str, dest="flag", - - def handle(self, *args, **kwargs): - if kwargs["action"] == "djlint": - djlint() - elif kwargs["action"] == "black": - black() - else: - self.stdout.write(colorize("Linting with: BLACK FORMATTER", fg="green", opts=("bold",))) - black() - self.stdout.write(colorize("Linting with: DJLINT", fg="green", opts=("bold",))) - djlint() - - -def djlint(): - subprocess.run(["djlint", "./frontend/templates/", "--reformat"]) - - -def black(): - subprocess.run(["black", "./"]) diff --git a/backend/core/management/commands/navbar_refresh.py b/backend/core/management/commands/navbar_refresh.py deleted file mode 100644 index 40757f193..000000000 --- a/backend/core/management/commands/navbar_refresh.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.core.management.base import BaseCommand -from django.core.cache import cache - - -class Command(BaseCommand): - """ - Deletes the "navbar_items" cache and prints a message to the standard output. - """ - - def handle(self, *args, **kwargs): - cache.delete("navbar_items") - self.stdout.write("Cleared cache\n") diff --git a/backend/core/management/commands/test_urls.py b/backend/core/management/commands/test_urls.py deleted file mode 100644 index 51c1dcc14..000000000 --- a/backend/core/management/commands/test_urls.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.core.management.base import BaseCommand -from django.core.management import call_command - - -class Command(BaseCommand): - help = "Runs URL verification tests." - - def handle(self, *args, **options): - call_command("test", "backend.tests.urls.verify_urls") diff --git a/backend/core/management/commands/test_views.py b/backend/core/management/commands/test_views.py deleted file mode 100644 index 97b4a0847..000000000 --- a/backend/core/management/commands/test_views.py +++ /dev/null @@ -1,33 +0,0 @@ -import os -from django.core.management.base import BaseCommand -from django.core.management import call_command - - -class Command(BaseCommand): - help = "Runs verification tests for view files in backend/tests/views." - - def add_arguments(self, parser): - parser.add_argument( - "test_label", - nargs="?", - type=str, - help="Test label for a specific view file.", - ) - - def handle(self, *args, **options): - test_dir = "backend/tests/views" - test_label = options["test_label"] - - if test_label: - self.run_test("backend.tests.views." + test_label) - else: - for root, dirs, files in os.walk(test_dir): - for file in files: - if file.endswith(".py") and file != "__init__.py": - test_module = file.replace(".py", "") - test_label = f"MAIN.tests.views.{test_module}" - self.run_test(test_label) - - def run_test(self, test_label): - self.stdout.write(self.style.SUCCESS(f"Running tests for {test_label}")) - call_command("test", test_label) diff --git a/backend/core/management/scheduled_tasks/update_all_schedules.py b/backend/core/management/scheduled_tasks/update_all_schedules.py deleted file mode 100644 index 02b55b1d6..000000000 --- a/backend/core/management/scheduled_tasks/update_all_schedules.py +++ /dev/null @@ -1,23 +0,0 @@ -import threading -from backend.finance.models import InvoiceRecurringProfile -from backend.core.service.boto3.scheduler.update_schedule import update_boto_schedule - - -# thread = threading.Thread(target=self._send_message, args=(func_name, args, kwargs)) -# threads = list() -# thread.start() - -# def fetch_and_update_profile(profile: InvoiceRecurringProfile): -# update_boto_schedule(profile.id) - - -def refresh_all_schedules_statuses(): - print("REFRESHING ALL SCHEDULE STATUSES!!!!!!!!!!!!!!!!!!") - threads: list = [] - - all_recurring_profiles = InvoiceRecurringProfile.objects.filter(active=True).all() - - for profile in all_recurring_profiles: - x = threading.Thread(target=update_boto_schedule, args=(profile.id,)) - threads.append(x) - x.start() diff --git a/backend/core/models.py b/backend/core/models.py deleted file mode 100644 index 4f5d71867..000000000 --- a/backend/core/models.py +++ /dev/null @@ -1,723 +0,0 @@ -from __future__ import annotations - -import itertools -import typing -from datetime import datetime, timedelta -from typing import Literal, Union -from uuid import uuid4 - -from django.contrib.auth.hashers import make_password -from django.contrib.auth.models import AbstractUser, UserManager -from django.core.files.storage import storages, FileSystemStorage -from django.db import models -from django.db.models import Count, QuerySet -from django.utils import timezone -from django.utils.crypto import get_random_string -from storages.backends.s3 import S3Storage - - -def _public_storage(): - return storages["public_media"] - - -def _private_storage() -> FileSystemStorage | S3Storage: - return storages["private_media"] - - -def RandomCode(length=6): - return get_random_string(length=length).upper() - - -def RandomAPICode(length=89): - return get_random_string(length=length).lower() - - -def upload_to_user_separate_folder(instance, filename, optional_actor=None) -> str: - instance_name = instance._meta.verbose_name.replace(" ", "-") - - print(instance, filename) - - if optional_actor: - if isinstance(optional_actor, User): - return f"{instance_name}/users/{optional_actor.id}/{filename}" - elif isinstance(optional_actor, Organization): - return f"{instance_name}/orgs/{optional_actor.id}/{filename}" - return f"{instance_name}/global/{filename}" - - if hasattr(instance, "user") and hasattr(instance.user, "id"): - return f"{instance_name}/users/{instance.user.id}/{filename}" - elif hasattr(instance, "organization") and hasattr(instance.organization, "id"): - return f"{instance_name}/orgs/{instance.organization.id}/{filename}" - return f"{instance_name}/global/{filename}" - - -def USER_OR_ORGANIZATION_CONSTRAINT(): - return models.CheckConstraint( - name=f"%(app_label)s_%(class)s_check_user_or_organization", - check=(models.Q(user__isnull=True, organization__isnull=False) | models.Q(user__isnull=False, organization__isnull=True)), - ) - - -M = typing.TypeVar("M", bound=models.Model) - - -class CustomUserManager(UserManager): - def get_queryset(self): - return ( - super() - .get_queryset() - .select_related("user_profile", "logged_in_as_team") - .annotate(notification_count=(Count("user_notifications"))) - ) - - -class User(AbstractUser): - objects: CustomUserManager = CustomUserManager() # type: ignore - - logged_in_as_team = models.ForeignKey("Organization", on_delete=models.SET_NULL, null=True, blank=True) - stripe_customer_id = models.CharField(max_length=255, null=True, blank=True) - entitlements = models.JSONField(null=True, blank=True, default=list) # list of strings e.g. ["invoices"] - awaiting_email_verification = models.BooleanField(default=True) - require_change_password = models.BooleanField(default=False) # does user need to change password upon next login - - class Role(models.TextChoices): - # NAME DJANGO ADMIN NAME - DEV = "DEV", "Developer" - STAFF = "STAFF", "Staff" - USER = "USER", "User" - TESTER = "TESTER", "Tester" - - role = models.CharField(max_length=10, choices=Role.choices, default=Role.USER) - - @property - def name(self): - return self.first_name - - @property - def teams_apart_of(self): - return set(itertools.chain(self.teams_joined.all(), self.teams_leader_of.all())) - - @property - def is_org(self): - return False - - -def add_3hrs_from_now(): - return timezone.now() + timezone.timedelta(hours=3) - - -class ActiveManager(models.Manager): - """Manager to return only active objects.""" - - def get_queryset(self): - return super().get_queryset().filter(active=True) - - -class ExpiredManager(models.Manager): - """Manager to return only expired (inactive) objects.""" - - def get_queryset(self): - now = timezone.now() - return super().get_queryset().filter(expires__isnull=False, expires__lte=now) - - -class ExpiresBase(models.Model): - """Base model for handling expiration logic.""" - - expires = models.DateTimeField("Expires", null=True, blank=True, help_text="When the item will expire") - active = models.BooleanField(default=True) - - # Default manager that returns only active items - objects = ActiveManager() - - # Custom manager to get expired/inactive objects - expired_objects = ExpiredManager() - - # Fallback All objects - all_objects = models.Manager() - - def deactivate(self) -> None: - """Manually deactivate the object.""" - self.active = False - self.save() - - def delete_if_expired_for(self, days: int = 14) -> bool: - """Delete the object if it has been expired for a certain number of days.""" - if self.expires and self.expires <= timezone.now() - timedelta(days=days): - self.delete() - return True - return False - - @property - def remaining_active_time(self): - """Return the remaining time until expiration, or None if already expired or no expiration set.""" - if not self.has_expired: - return self.expires - timezone.now() - return None - - @property - def has_expired(self): - return self.expires and self.expires <= timezone.now() - - def is_active(self): - return self.active - - class Meta: - abstract = True - - -class VerificationCodes(ExpiresBase): - class ServiceTypes(models.TextChoices): - CREATE_ACCOUNT = "create_account", "Create Account" - RESET_PASSWORD = "reset_password", "Reset Password" - - uuid = models.UUIDField(default=uuid4, editable=False, unique=True) # This is the public identifier - token = models.TextField(default=RandomCode, editable=False) # This is the private token (should be hashed) - - user = models.ForeignKey(User, on_delete=models.CASCADE) - created = models.DateTimeField(auto_now_add=True) - service = models.CharField(max_length=14, choices=ServiceTypes.choices) - - def __str__(self): - return self.user.username - - def hash_token(self): - self.token = make_password(self.token) - self.save() - return True - - class Meta: - verbose_name = "Verification Code" - verbose_name_plural = "Verification Codes" - - -class UserSettings(models.Model): - class CoreFeatures(models.TextChoices): - INVOICES = "invoices", "Invoices" - RECEIPTS = "receipts", "Receipts" - EMAIL_SENDING = "email_sending", "Email Sending" - MONTHLY_REPORTS = "monthly_reports", "Monthly Reports" - - CURRENCIES = { - "GBP": {"name": "British Pound Sterling", "symbol": "£"}, - "EUR": {"name": "Euro", "symbol": "€"}, - "USD": {"name": "United States Dollar", "symbol": "$"}, - "JPY": {"name": "Japanese Yen", "symbol": "¥"}, - "INR": {"name": "Indian Rupee", "symbol": "₹"}, - "AUD": {"name": "Australian Dollar", "symbol": "$"}, - "CAD": {"name": "Canadian Dollar", "symbol": "$"}, - } - user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="user_profile") - dark_mode = models.BooleanField(default=True) - currency = models.CharField( - max_length=3, - default="GBP", - choices=[(code, info["name"]) for code, info in CURRENCIES.items()], - ) - profile_picture = models.ImageField( - upload_to="profile_pictures/", - storage=_public_storage, - blank=True, - null=True, - ) - - disabled_features = models.JSONField(default=list) - - @property - def profile_picture_url(self): - if self.profile_picture and hasattr(self.profile_picture, "url"): - return self.profile_picture.url - return "" - - def get_currency_symbol(self): - return self.CURRENCIES.get(self.currency, {}).get("symbol", "$") - - def has_feature(self, feature: str) -> bool: - return feature not in self.disabled_features - - def __str__(self): - return self.user.username - - class Meta: - verbose_name = "User Settings" - verbose_name_plural = "User Settings" - - -class Organization(models.Model): - name = models.CharField(max_length=100, unique=True) - leader = models.ForeignKey(User, on_delete=models.CASCADE, related_name="teams_leader_of") - members = models.ManyToManyField(User, related_name="teams_joined") - - stripe_customer_id = models.CharField(max_length=255, null=True, blank=True) - entitlements = models.JSONField(null=True, blank=True, default=list) # list of strings e.g. ["invoices"] - - def is_owner(self, user: User) -> bool: - return self.leader == user - - def is_logged_in_as_team(self, request) -> bool: - if isinstance(request.auth, User): - return False - - if request.auth and request.auth.organization_id == self.id: - return True - return False - - @property - def is_authenticated(self): - return True - - @property - def is_org(self): - return True - - -class TeamMemberPermission(models.Model): - team = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="permissions") - user = models.OneToOneField("backend.User", on_delete=models.CASCADE, related_name="team_permissions") - scopes = models.JSONField("Scopes", default=list, help_text="List of permitted scopes") - - class Meta: - unique_together = ("team", "user") - - -class TeamInvitation(ExpiresBase): - code = models.CharField(max_length=10) - team = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="team_invitations") - user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="team_invitations") - invited_by = models.ForeignKey(User, on_delete=models.CASCADE) - - def is_active(self): - return self.active - - def set_expires(self): - self.expires = timezone.now() + timezone.timedelta(days=14) - - def save(self, *args, **kwargs): - if not self.code: - self.code = RandomCode(10) - self.set_expires() - super().save() - - def __str__(self): - return self.team.name - - class Meta: - verbose_name = "Team Invitation" - verbose_name_plural = "Team Invitations" - - -class OwnerBaseManager(models.Manager): - def create(self, **kwargs): - # Handle the 'owner' argument dynamically in `create()` - owner = kwargs.pop("owner", None) - if isinstance(owner, User): - kwargs["user"] = owner - kwargs["organization"] = None - elif isinstance(owner, Organization): - kwargs["organization"] = owner - kwargs["user"] = None - return super().create(**kwargs) - - def filter(self, *args, **kwargs): - # Handle the 'owner' argument dynamically in `filter()` - owner = kwargs.pop("owner", None) - if isinstance(owner, User): - kwargs["user"] = owner - elif isinstance(owner, Organization): - kwargs["organization"] = owner - return super().filter(*args, **kwargs) - - -class OwnerBase(models.Model): - user = models.ForeignKey("backend.User", on_delete=models.CASCADE, null=True, blank=True) - organization = models.ForeignKey("backend.Organization", on_delete=models.CASCADE, null=True, blank=True) - - objects = OwnerBaseManager() - - class Meta: - abstract = True - constraints = [ - USER_OR_ORGANIZATION_CONSTRAINT(), - ] - - @property - def owner(self) -> User | Organization: - """ - Property to dynamically get the owner (either User or Team) - """ - if hasattr(self, "user") and self.user: - return self.user - elif hasattr(self, "team") and self.team: - return self.team - return self.organization # type: ignore[return-value] - # all responses WILL have either a user or org so this will handle all - - @owner.setter - def owner(self, value: User | Organization) -> None: - if isinstance(value, User): - self.user = value - self.organization = None - elif isinstance(value, Organization): - self.user = None - self.organization = value - else: - raise ValueError("Owner must be either a User or a Organization") - - def save(self, *args, **kwargs): - if hasattr(self, "owner") and not self.user and not self.organization: - if isinstance(self.owner, User): - self.user = self.owner - elif isinstance(self.owner, Organization): - self.organization = self.owner - super().save(*args, **kwargs) - - @classmethod - def filter_by_owner(cls: typing.Type[M], owner: Union[User, Organization]) -> QuerySet[M]: - """ - Class method to filter objects by owner (either User or Organization) - """ - if isinstance(owner, User): - return cls.objects.filter(user=owner) # type: ignore[attr-defined] - elif isinstance(owner, Organization): - return cls.objects.filter(organization=owner) # type: ignore[attr-defined] - else: - raise ValueError("Owner must be either a User or an Organization") - - @property - def is_team(self): - return isinstance(self.owner, Organization) - - -class PasswordSecret(ExpiresBase): - user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="password_secrets") - secret = models.TextField(max_length=300) - - -class Notification(models.Model): - action_choices = [ - ("normal", "Normal"), - ("modal", "Modal"), - ("redirect", "Redirect"), - ] - - user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_notifications") - message = models.CharField(max_length=100) - action = models.CharField(max_length=10, choices=action_choices, default="normal") - action_value = models.CharField(max_length=100, null=True, blank=True) - extra_type = models.CharField(max_length=100, null=True, blank=True) - extra_value = models.CharField(max_length=100, null=True, blank=True) - date = models.DateTimeField(auto_now_add=True) - - -class AuditLog(OwnerBase): - action = models.CharField(max_length=300) - date = models.DateTimeField(auto_now_add=True) - - class Meta: - constraints: list = [] - - def __str__(self): - return f"{self.action} - {self.date}" - - -class LoginLog(models.Model): - class ServiceTypes(models.TextChoices): - MANUAL = "manual" - MAGIC_LINK = "magic_link" - - user = models.ForeignKey(User, on_delete=models.CASCADE) - service = models.CharField(max_length=14, choices=ServiceTypes.choices, default="manual") - date = models.DateTimeField(auto_now_add=True) - - -class Error(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE) - error = models.CharField(max_length=250, null=True) - error_code = models.CharField(max_length=100, null=True) - error_colour = models.CharField(max_length=25, default="danger") - date = models.DateTimeField(auto_now=True) - - def __str__(self): - return str(self.user_id) - - -class TracebackError(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True) - error = models.CharField(max_length=5000, null=True) - date = models.DateTimeField(auto_now=True) - - def __str__(self): - return str(self.error) - - -class FeatureFlags(models.Model): - name = models.CharField(max_length=100, editable=False, unique=True) - description = models.TextField(max_length=500, null=True, blank=True, editable=False) - value = models.BooleanField(default=False) - updated_at = models.DateTimeField(auto_now=True) - - class Meta: - verbose_name = "Feature Flag" - verbose_name_plural = "Feature Flags" - - def __str__(self): - return self.name - - def enable(self): - self.value = True - self.save() - - def disable(self): - self.value = False - self.save() - - -class QuotaLimit(models.Model): - class LimitTypes(models.TextChoices): - PER_MONTH = "per_month" - PER_DAY = "per_day" - PER_CLIENT = "per_client" - PER_INVOICE = "per_invoice" - PER_TEAM = "per_team" - PER_QUOTA = "per_quota" - FOREVER = "forever" - - slug = models.CharField(max_length=100, unique=True, editable=False) - name = models.CharField(max_length=100, editable=False) - description = models.TextField(max_length=500, null=True, blank=True) - value = models.IntegerField() - updated_at = models.DateTimeField(auto_now=True) - adjustable = models.BooleanField(default=True) - limit_type = models.CharField(max_length=20, choices=LimitTypes.choices, default=LimitTypes.PER_MONTH) - - class Meta: - verbose_name = "Quota Limit" - verbose_name_plural = "Quota Limits" - - def __str__(self): - return self.name - - def get_quota_limit(self, user: User, quota_limit: QuotaLimit | None = None): - user_quota_override: QuotaOverrides | QuotaLimit - try: - if quota_limit: - user_quota_override = quota_limit - else: - user_quota_override = self.quota_overrides.get(user=user) - return user_quota_override.value - except QuotaOverrides.DoesNotExist: - return self.value - - def get_period_usage(self, user: User): - if self.limit_type == "forever": - return self.quota_usage.filter(user=user, quota_limit=self).count() - elif self.limit_type == "per_month": - return self.quota_usage.filter(user=user, quota_limit=self, created_at__month=datetime.now().month).count() - elif self.limit_type == "per_day": - return self.quota_usage.filter(user=user, quota_limit=self, created_at__day=datetime.now().day).count() - else: - return "Not available" - - def strict_goes_above_limit(self, user: User, extra: str | int | None = None, add: int = 0) -> bool: - current: Union[int, None, QuerySet[QuotaUsage], Literal["Not Available"]] - - current = self.strict_get_quotas(user, extra) - current = current.count() if current != "Not Available" else None - return current + add >= self.get_quota_limit(user) if current else False - - def strict_get_quotas( - self, user: User, extra: str | int | None = None, quota_limit: QuotaLimit | None = None - ) -> QuerySet[QuotaUsage] | Literal["Not Available"]: - """ - Gets all usages of a quota - :return: QuerySet of quota usages OR "Not Available" if utilisation isn't available (e.g. per invoice you can't get in total) - """ - current = None - if quota_limit is not None: - quota_lim = quota_limit.quota_usage - else: - quota_lim = QuotaUsage.objects.filter(user=user, quota_limit=self) # type: ignore[assignment] - - if self.limit_type == "forever": - current = self.quota_usage.filter(user=user, quota_limit=self) - elif self.limit_type == "per_month": - current_month = timezone.now().month - current_year = timezone.now().year - current = quota_lim.filter(created_at__year=current_year, created_at__month=current_month) - elif self.limit_type == "per_day": - current_day = timezone.now().day - current_month = timezone.now().month - current_year = timezone.now().year - current = quota_lim.filter(created_at__year=current_year, created_at__month=current_month, created_at__day=current_day) - elif self.limit_type in ["per_client", "per_invoice", "per_team", "per_receipt", "per_quota"] and extra: - current = quota_lim.filter(extra_data=extra) - else: - return "Not Available" - return current - - @classmethod - @typing.no_type_check - def delete_quota_usage(cls, quota_limit: str | QuotaLimit, user: User, extra, timestamp=None): - quota_limit = cls.objects.get(slug=quota_limit) if isinstance(quota_limit, str) else quota_limit - - all_usages = quota_limit.strict_get_quotas(user, extra) - closest_obj = None - - if all_usages.count() > 1 and timestamp: - earliest: QuotaUsage | None = all_usages.filter(created_at__gte=timestamp).order_by("created_at").first() - latest: QuotaUsage | None = all_usages.filter(created_at__lte=timestamp).order_by("created_at").last() - - if earliest and latest: - time_until_soonest_obj = abs(earliest.created_at - timestamp) - time_since_most_recent_obj = abs(latest.created_at - timestamp) - if time_until_soonest_obj < time_since_most_recent_obj: - closest_obj = earliest - else: - closest_obj = latest - - if earliest and latest and closest_obj: - closest_obj.delete() - elif all_usages.count() > 1: - earliest = all_usages.order_by("created_at").first() - if earliest: - earliest.delete() - else: - first = all_usages.first() - if first: - first.delete() - - -class QuotaOverrides(OwnerBase): - quota_limit = models.ForeignKey(QuotaLimit, on_delete=models.CASCADE, related_name="quota_overrides") - value = models.IntegerField() - updated_at = models.DateTimeField(auto_now=True) - created_at = models.DateTimeField(auto_now_add=True) - - class Meta: - verbose_name = "Quota Override" - verbose_name_plural = "Quota Overrides" - - def __str__(self): - return f"{self.user}" - - -class QuotaUsage(OwnerBase): - quota_limit = models.ForeignKey(QuotaLimit, on_delete=models.CASCADE, related_name="quota_usage") - created_at = models.DateTimeField(auto_now_add=True) - extra_data = models.IntegerField(null=True, blank=True) # id of Limit Type - - class Meta: - verbose_name = "Quota Usage" - verbose_name_plural = "Quota Usage" - - def __str__(self): - return f"{self.user} quota usage for {self.quota_limit_id}" - - @classmethod - def create_str(cls, user: User, limit: str | QuotaLimit, extra_data: str | int | None = None): - try: - quota_limit = limit if isinstance(limit, QuotaLimit) else QuotaLimit.objects.get(slug=limit) - except QuotaLimit.DoesNotExist: - return "Not Found" - - Notification.objects.create( - user=user, - action="redirect", - action_value=f"/dashboard/quotas/{quota_limit.slug.split('-')[0]}/", - message=f"You have reached the limit for {quota_limit.name}", - ) - - return cls.objects.create(user=user, quota_limit=quota_limit, extra_data=extra_data) - - @classmethod - def get_usage(self, user: User, limit: str | QuotaLimit): - try: - ql: QuotaLimit = QuotaLimit.objects.get(slug=limit) if isinstance(limit, str) else limit - except QuotaLimit.DoesNotExist: - return "Not Found" - - return self.objects.filter(user=user, quota_limit=ql).count() - - -class QuotaIncreaseRequest(OwnerBase): - class StatusTypes(models.TextChoices): - PENDING = "pending" - APPROVED = "approved" - REJECTED = "rejected" - - requester = models.ForeignKey(User, on_delete=models.CASCADE, related_name="quota_increase_requests") - - quota_limit = models.ForeignKey(QuotaLimit, on_delete=models.CASCADE, related_name="quota_increase_requests") - reason = models.CharField(max_length=1000) - new_value = models.IntegerField() - current_value = models.IntegerField() - updated_at = models.DateTimeField(auto_now=True) - created_at = models.DateTimeField(auto_now_add=True) - status = models.CharField(max_length=20, choices=StatusTypes.choices, default=StatusTypes.PENDING) - - class Meta: - verbose_name = "Quota Increase Request" - verbose_name_plural = "Quota Increase Requests" - - def __str__(self): - return f"{self.owner}" - - -class EmailSendStatus(OwnerBase): - STATUS_CHOICES = [ - (status, status.title()) - for status in [ - "send", - "reject", - "bounce", - "complaint", - "delivery", - "open", - "click", - "rendering_failure", - "delivery_delay", - "subscription", - "failed_to_send", - "pending", - ] - ] - - sent_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="emails_sent") - - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - updated_status_at = models.DateTimeField(auto_now_add=True) - - recipient = models.TextField() - aws_message_id = models.CharField(max_length=100, null=True, blank=True, editable=False) - status = models.CharField(max_length=20, choices=STATUS_CHOICES) - - class Meta: - constraints = [USER_OR_ORGANIZATION_CONSTRAINT()] - - -class FileStorageFile(OwnerBase): - file = models.FileField(upload_to=upload_to_user_separate_folder, storage=_private_storage) - file_uri_path = models.CharField(max_length=500) # relative path not including user folder/media - last_edited_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, editable=False, related_name="files_edited") - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - - __original_file = None - __original_file_uri_path = None - - def __init__(self, *args, **kwargs): - super(FileStorageFile, self).__init__(*args, **kwargs) - self.__original_file = self.file - self.__original_file_uri_path = self.file_uri_path - - -class MultiFileUpload(OwnerBase): - files = models.ManyToManyField(FileStorageFile, related_name="multi_file_uploads") - started_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - finished_at = models.DateTimeField(null=True, blank=True, editable=False) - uuid = models.UUIDField(default=uuid4, editable=False, unique=True) - - def is_finished(self): - return self.finished_at is not None diff --git a/backend/core/service/__init__.py b/backend/core/service/__init__.py deleted file mode 100644 index 36dfed184..000000000 --- a/backend/core/service/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from backend.core.service.boto3.handler import BOTO3_HANDLER diff --git a/backend/core/service/api_keys/delete.py b/backend/core/service/api_keys/delete.py deleted file mode 100644 index 0b172694a..000000000 --- a/backend/core/service/api_keys/delete.py +++ /dev/null @@ -1,18 +0,0 @@ -from backend.models import User, Organization -from backend.core.service.api_keys.get import get_api_key_by_name -from backend.core.api.public import APIAuthToken - - -def delete_api_key(request, owner: User | Organization, key: str | None | APIAuthToken) -> bool | str: - if isinstance(owner, Organization) and "api_keys:write" not in owner.permissions.get(user=request.user).scopes: - return "No permission to delete key" - - if not isinstance(key, APIAuthToken): - key: APIAuthToken | None = get_api_key_by_name(owner, key) # type: ignore[no-redef, arg-type] - - if not key: - return "Key not found" - - key.deactivate() # type: ignore[union-attr] - - return True diff --git a/backend/core/service/api_keys/generate.py b/backend/core/service/api_keys/generate.py deleted file mode 100644 index 81f38a542..000000000 --- a/backend/core/service/api_keys/generate.py +++ /dev/null @@ -1,103 +0,0 @@ -from django.core.exceptions import ValidationError - -from backend.core.api.public import APIAuthToken -from backend.models import User, Organization -from backend.core.service.permissions.scopes import validate_scopes - - -def generate_public_api_key( - request, - owner: User | Organization, - api_key_name: str | None, - permissions: list, - *, - expires=None, - description=None, - administrator_toggle: bool = False, - administrator_type: str | None = None, -) -> tuple[APIAuthToken | None, str]: - if not validate_name(api_key_name): - return None, "Invalid key name" - - if not validate_description(description): - return None, "Invalid description" - - if api_key_exists_under_name(owner, api_key_name): - return None, "A key with this name already exists in your account" - - if validate_scopes(permissions).failed: # or not has_permission_to_create(request, owner): - return None, "Invalid permissions" - - administrator_service_type = None - - if request.user.is_superuser: - if administrator_toggle: - if administrator_type not in [option[0] for option in APIAuthToken.AdministratorServiceTypes.choices]: - return None, "Invalid administration type" - administrator_service_type = administrator_type - - token = APIAuthToken( - name=api_key_name, - description=description, - expires=expires, - scopes=permissions, - administrator_service_type=administrator_service_type, - ) # type: ignore[arg-type, misc] - - raw_key: str = token.generate_key() - - if isinstance(owner, Organization): - token.organization = owner - else: - token.user = owner - - try: - token.full_clean() - except ValidationError as validation_errors: - field, error_list = next(iter(validation_errors.error_dict.items())) - - field = "Permissions" if field == "scopes" else field.title() - - if isinstance(error_list[0], ValidationError): - error_message = error_list[0].messages[0] - else: - error_message = error_list[0] - - return None, f"{field}: {error_message}" - - token.save() - - return token, raw_key - - -def validate_name(name: str | None) -> bool: - """ - Require name not already exist under account - """ - if not name: - return False - return len(name) <= 64 - - -def validate_description(description: str | None) -> bool: - """ - Accept any description - Reject description longer than 255 characters - """ - return not description or len(description) <= 255 - - -def api_key_exists_under_name(owner: User | Organization, name: str | None) -> bool: - """ - Check if API key exists under a given name - """ - return APIAuthToken.filter_by_owner(owner).filter(name=name, active=True).exists() - - -def has_permission_to_create(request, owner: User | Organization) -> bool: - if isinstance(owner, User): - return True - - if owner.permissions.filter(user=request.user).exists() and "api_keys:write" in owner.permissions.get(user=request.user).scopes: - return True - return False diff --git a/backend/core/service/api_keys/get.py b/backend/core/service/api_keys/get.py deleted file mode 100644 index 7d2439962..000000000 --- a/backend/core/service/api_keys/get.py +++ /dev/null @@ -1,10 +0,0 @@ -from backend.core.api.public import APIAuthToken -from backend.models import User, Organization - - -def get_api_key_by_name(owner: User | Organization, key_name: str) -> APIAuthToken | None: - return APIAuthToken.filter_by_owner(owner).filter(name=key_name, active=True).first() - - -def get_api_key_by_id(owner: User | Organization, key_id: str | int) -> APIAuthToken | None: - return APIAuthToken.filter_by_owner(owner).filter(id=key_id, active=True).first() diff --git a/backend/core/service/base/breadcrumbs.py b/backend/core/service/base/breadcrumbs.py deleted file mode 100644 index 6e0739ad6..000000000 --- a/backend/core/service/base/breadcrumbs.py +++ /dev/null @@ -1,85 +0,0 @@ -from typing import Optional, Any - -from django.http import HttpRequest -from django.urls import resolve, reverse -from django.urls.exceptions import NoReverseMatch - -ALL_ITEMS: dict[str, tuple[str, Optional[str], Optional[str]]] = { - "dashboard": ("Dashboard", "dashboard", "house"), - "finance:invoices:dashboard": ("Invoices", "finance:invoices:single:dashboard", "file-invoice"), - "finance:invoices:single:dashboard": ("Single", "finance:invoices:single:dashboard", "file-invoice"), - "finance:invoices:single:create": ("Create (single)", "finance:invoices:single:create", None), - "finance:invoices:recurring:dashboard": ("Recurring", "finance:invoices:recurring:dashboard", "refresh"), - "finance:invoices:recurring:create": ("Create (recurring)", "finance:invoices:recurring:create", None), - "finance:invoices:single:edit": ("Edit", None, "pencil"), - "finance:invoices:single:overview": ("Invoice", None, None), - "receipts dashboard": ("Receipts", "receipts dashboard", "file-invoice"), - "teams:dashboard": ("Teams", "teams:dashboard", "users"), - "settings:dashboard": ("Settings", "settings:dashboard", "gear"), - "clients:dashboard": ("Clients", "clients:dashboard", "users"), - "clients:create": ("Create", "clients:create", None), - "reports:dashboard": ("Monthly Reports", "reports:dashboard", "chart-line"), -} - -ALL_BREADCRUMBS: dict[str, str | tuple] = { - "dashboard": "dashboard", - "teams:dashboard": ("dashboard", "teams:dashboard"), - "receipts dashboard": ("dashboard", "receipts dashboard"), - "finance:invoices:single:dashboard": ("dashboard", "finance:invoices:dashboard", "finance:invoices:single:dashboard"), - "finance:invoices:single:create": ("dashboard", "finance:invoices:dashboard", "finance:invoices:single:create"), - "finance:invoices:recurring:dashboard": ("dashboard", "finance:invoices:dashboard", "finance:invoices:recurring:dashboard"), - "finance:invoices:recurring:create": ("dashboard", "finance:invoices:dashboard", "finance:invoices:recurring:create"), - "finance:invoices:single:edit": ("dashboard", "finance:invoices:dashboard", "finance:invoices:single:edit"), - "finance:invoices:single:overview": ("dashboard", "finance:invoices:dashboard", "finance:invoices:single:overview"), - "clients:dashboard": ("dashboard", "clients:dashboard"), - "clients:create": ("dashboard", "clients:dashboard", "clients:create"), - "settings:dashboard": ("dashboard", "settings:dashboard"), - "reports:dashboard": ("dashboard", "reports:dashboard"), -} - - -def get_item(name: str, url_name: Optional[str] = None, icon: Optional[str] = None, kwargs: dict = {}, *, request=None) -> dict: - """ - Create a breadcrumb item dictionary. - Parameters: - - name (str): The name of the breadcrumb item. - - url_name (str): The URL name used for generating the URL using Django's reverse function. - - icon (Optional[str]): The icon associated with the breadcrumb item (default is None). - Returns: - Dict[str, Any]: A dictionary representing the breadcrumb item. - """ - - if request: - rev_kwargs = {kwarg: request.resolver_match.kwargs.get(kwarg) for url, kwarg in kwargs.items() if url == url_name if kwargs} - else: - rev_kwargs = {} - return { - "name": name, - "url": reverse(url_name, kwargs=rev_kwargs if rev_kwargs else {}) if url_name else "", - "icon": icon, - } - - -def generate_breadcrumbs(*breadcrumb_list: str, request=None) -> list[dict[Any, Any] | None]: - """ - Generate a list of breadcrumb items based on the provided list of breadcrumb names. - Parameters: - - breadcrumb_list (str): Variable number of strings representing the names of the breadcrumbs. - Returns: - List[Dict[str, Any]]: A list of dictionaries representing the breadcrumb items. - """ - return [ - get_item(*ALL_ITEMS.get(breadcrumb, (None, None, None)), request=request) - for breadcrumb in breadcrumb_list - if breadcrumb in ALL_ITEMS - ] - - -def get_breadcrumbs(*, request: HttpRequest | None = None, url: str | None = None): - current_url_name: str | Any = request.resolver_match.view_name if request and request.resolver_match else None # type: ignore[ union-attr] - if url: - try: - current_url_name = resolve(url).view_name - except NoReverseMatch: - return {"breadcrumb": []} - return {"breadcrumb": generate_breadcrumbs(*ALL_BREADCRUMBS.get(current_url_name, []), request=request)} diff --git a/backend/core/service/clients/__init__.py b/backend/core/service/clients/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/defaults/__init__.py b/backend/core/service/defaults/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/file_storage/__init__.py b/backend/core/service/file_storage/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/__init__.py b/backend/core/service/invoices/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/common/__init__.py b/backend/core/service/invoices/common/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/common/create/__init__.py b/backend/core/service/invoices/common/create/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/common/create/services/__init__.py b/backend/core/service/invoices/common/create/services/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/common/emails/__init__.py b/backend/core/service/invoices/common/emails/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/recurring/__init__.py b/backend/core/service/invoices/recurring/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/recurring/create/__init__.py b/backend/core/service/invoices/recurring/create/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/recurring/generation/__init__.py b/backend/core/service/invoices/recurring/generation/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/recurring/schedules/__init__.py b/backend/core/service/invoices/recurring/schedules/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/recurring/validate/__init__.py b/backend/core/service/invoices/recurring/validate/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/recurring/webhooks/__init__.py b/backend/core/service/invoices/recurring/webhooks/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/single/__init__.py b/backend/core/service/invoices/single/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/invoices/single/create/__init__.py b/backend/core/service/invoices/single/create/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/maintenance/__init__.py b/backend/core/service/maintenance/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/maintenance/expire/__init__.py b/backend/core/service/maintenance/expire/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/maintenance/expire/run.py b/backend/core/service/maintenance/expire/run.py deleted file mode 100644 index 04c71b416..000000000 --- a/backend/core/service/maintenance/expire/run.py +++ /dev/null @@ -1,38 +0,0 @@ -from datetime import timedelta -from typing import Type - -from django.db import models -from django.db.models import QuerySet - -from backend.models import TeamInvitation, InvoiceURL, PasswordSecret - -from django.utils import timezone - -""" -Every model MUST have the field "expires" as: - -expires = models.DateTimeField(null=True, blank=True) -""" - - -def expire_and_cleanup_objects() -> str: - deactivated_items: int = 0 - deleted_items: int = 0 - - model_list: list[Type[models.Model]] = [TeamInvitation, InvoiceURL, PasswordSecret] - - now = timezone.now() - - for model in model_list: - # Delete objects that have been inactive and expired for more than 14 days - over_14_days_expired = model.all_objects.filter(expires__lte=now - timedelta(days=14)) # type: ignore[attr-defined] - deleted_items += over_14_days_expired.count() - over_14_days_expired.delete() - - # Deactivate expired items that got missed - to_deactivate: QuerySet[models.Model] = model.all_objects.filter(expires__lte=now, active=True) # type: ignore[attr-defined] - - deactivated_items += to_deactivate.count() - to_deactivate.update(active=False) - - return f"Deactivated {deactivated_items} objects and deleted {deleted_items} objects." diff --git a/backend/core/service/permissions/__init__.py b/backend/core/service/permissions/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/permissions/scopes.py b/backend/core/service/permissions/scopes.py deleted file mode 100644 index f9e4212d3..000000000 --- a/backend/core/service/permissions/scopes.py +++ /dev/null @@ -1,34 +0,0 @@ -from backend.core.api.public.permissions import SCOPE_DESCRIPTIONS, SCOPES -from backend.core.types.requests import WebRequest -from backend.core.utils.dataclasses import BaseServiceResponse - - -class PermissionScopesServiceResponse(BaseServiceResponse[None]): - response: None = None - - -def get_permissions_from_request(request: WebRequest) -> list: - scopes = [ - f"{group}:{perm}" - for group, items in SCOPE_DESCRIPTIONS.items() - if (perm := request.POST.get(f"permission_{group}")) in items["options"] - ] - - scopes.extend(f"{group}:read" for group, items in SCOPE_DESCRIPTIONS.items() if request.POST.get(f"permission_{group}") == "write") - - return scopes - - -def validate_scopes(permissions: list[str]) -> PermissionScopesServiceResponse: - """ - Validate permissions are valid - """ - if not permissions: - return PermissionScopesServiceResponse(True) - - invalid_permissions: list[str] = [permission for permission in permissions if permission not in SCOPES] - - if invalid_permissions: - return PermissionScopesServiceResponse(False, error_message=f"Invalid permissions: {', '.join(invalid_permissions)}") - - return PermissionScopesServiceResponse(True) diff --git a/backend/core/service/reports/__init__.py b/backend/core/service/reports/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/settings/__init__.py b/backend/core/service/settings/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/settings/update.py b/backend/core/service/settings/update.py deleted file mode 100644 index ce7fda38c..000000000 --- a/backend/core/service/settings/update.py +++ /dev/null @@ -1,36 +0,0 @@ -from backend.models import UserSettings -from PIL import Image - -from backend.core.utils.dataclasses import BaseServiceResponse - - -class UpdateProfilePictureServiceResponse(BaseServiceResponse[str]): ... - - -def update_profile_picture(profile_picture, user_profile: UserSettings) -> UpdateProfilePictureServiceResponse: - if not profile_picture: - return UpdateProfilePictureServiceResponse(error_message="Invalid or unsupported image file") - - try: - # Max file size is 10MB (Change the first number to determine the size in MB) - max_file_size = 10 * 1024 * 1024 - - if profile_picture.size is None: - return UpdateProfilePictureServiceResponse(error_message="File size not found") - - if profile_picture.size > max_file_size: - return UpdateProfilePictureServiceResponse(error_message="File size should be up to 10MB") - - img = Image.open(profile_picture) - img.verify() - - if img.format is None or img.format.lower() not in ["jpeg", "png", "jpg"]: - return UpdateProfilePictureServiceResponse( - error_message="Unsupported image format. We support only JPEG, JPG, PNG, if you have a good extension, your file just got renamed." - ) - - user_profile.profile_picture = profile_picture - user_profile.save() - return UpdateProfilePictureServiceResponse(True, "Successfully updated profile picture") - except (FileNotFoundError, Image.UnidentifiedImageError): - return UpdateProfilePictureServiceResponse(error_message="Invalid or unsupported image file") diff --git a/backend/core/service/settings/view.py b/backend/core/service/settings/view.py deleted file mode 100644 index 433a88b1b..000000000 --- a/backend/core/service/settings/view.py +++ /dev/null @@ -1,54 +0,0 @@ -from django.db.models import QuerySet - -from backend.core.api.public import APIAuthToken -from backend.models import UserSettings -from backend.core.service.defaults.get import get_account_defaults -from backend.core.types.requests import WebRequest - - -def validate_page(page: str | None) -> bool: - return not page or page in ["profile", "account", "api_keys", "account_defaults", "account_security", "email_templates"] - - -def get_user_profile(request: WebRequest) -> UserSettings: - try: - usersettings = request.user.user_profile - except UserSettings.DoesNotExist: - # Create a new UserSettings object - usersettings = UserSettings.objects.create(user=request.user) - return usersettings - - -def get_api_keys(request: WebRequest) -> QuerySet[APIAuthToken]: - return APIAuthToken.filter_by_owner(request.actor).filter(active=True).only("created", "name", "last_used", "description", "expires") - - -def account_page_context(request: WebRequest, context: dict) -> None: - user_profile = get_user_profile(request) - context.update({"currency_signs": user_profile.CURRENCIES, "currency": user_profile.currency}) - - -def api_keys_page_context(request: WebRequest, context: dict) -> None: - api_keys = get_api_keys(request) - context.update({"api_keys": api_keys}) - - -def account_defaults_context(request: WebRequest, context: dict) -> None: - context.update({"account_defaults": get_account_defaults(request.actor)}) - - -def email_templates_context(request: WebRequest, context: dict) -> None: - acc_defaults = get_account_defaults(request.actor) - context.update( - { - "account_defaults": acc_defaults, - "email_templates": { - "recurring_invoices": { - "invoice_created": acc_defaults.email_template_recurring_invoices_invoice_created, - "invoice_overdue": acc_defaults.email_template_recurring_invoices_invoice_overdue, - "invoice_cancelled": acc_defaults.email_template_recurring_invoices_invoice_cancelled, - } - }, - } - ) - print(context.get("email_templates")) diff --git a/backend/core/service/teams/__init__.py b/backend/core/service/teams/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/teams/create_user.py b/backend/core/service/teams/create_user.py deleted file mode 100644 index 72e40518d..000000000 --- a/backend/core/service/teams/create_user.py +++ /dev/null @@ -1,65 +0,0 @@ -from textwrap import dedent - -from django.urls import reverse -from django.utils.crypto import get_random_string - -from backend.core.models import User, Organization, TeamMemberPermission -from backend.core.utils.dataclasses import BaseServiceResponse -from settings.helpers import send_email - - -class CreateUserServiceResponse(BaseServiceResponse[User]): ... - - -def create_user_service( - request, email: str, team: Organization, first_name: str, last_name: str, permissions: list[str] -) -> CreateUserServiceResponse: - - if not first_name: - return CreateUserServiceResponse(error_message="Please enter a valid first name") - - if not email: - return CreateUserServiceResponse(error_message="Please enter a valid user email") - - if User.objects.filter(email=email).exists(): - return CreateUserServiceResponse(error_message="This user already exists, invite them instead!") - - temporary_password = get_random_string(length=8) - - user: User = User.objects.create_user(email=email, first_name=first_name, last_name=last_name, username=email) - user.set_password(temporary_password) - user.awaiting_email_verification = False - user.require_change_password = True - user.save() - - send_email( - destination=email, - subject="You have been invited to join an organization", - content=dedent( - f""" - Hi {user.first_name or "User"}, - - You have been invited by {request.user.email} to join the organization '{team.name}'. - - Your account email is: {email} - Your temporary password is: {temporary_password} - - We suggest that you change your password as soon as you login, however no other user including the organization have - access to this password. - - Upon login, you will be added to the \"{team.name}\" organization. However, if required, you may leave at any point. - - Login to your new account using this link: - {request.build_absolute_uri(reverse("auth:login"))} - - Didn't give permission to be added to this organization? You can safely ignore the email, no actions can be done on - behalf of you without your permission. - """ - ), - ) - - team.members.add(user) - - TeamMemberPermission.objects.create(user=user, team=team, scopes=permissions) - - return CreateUserServiceResponse(True, response=user) diff --git a/backend/core/service/teams/fetch.py b/backend/core/service/teams/fetch.py deleted file mode 100644 index fa4bd177d..000000000 --- a/backend/core/service/teams/fetch.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.db.models import QuerySet - -from backend.models import Organization -from backend.core.types.requests import WebRequest - - -def get_all_users_teams(request: WebRequest) -> QuerySet[Organization]: - return request.user.teams_joined.all() | request.user.teams_leader_of.all() diff --git a/backend/core/service/teams/permissions.py b/backend/core/service/teams/permissions.py deleted file mode 100644 index afc515195..000000000 --- a/backend/core/service/teams/permissions.py +++ /dev/null @@ -1,47 +0,0 @@ -from backend.models import User, Organization, TeamMemberPermission -from backend.core.service.permissions.scopes import validate_scopes -from backend.core.utils.dataclasses import BaseServiceResponse - - -class EditMemberPermissionsServiceResponse(BaseServiceResponse[None]): - response: None = None - - -def edit_member_permissions(receiver: User, team: Organization | None, permissions: list) -> EditMemberPermissionsServiceResponse: - if not validate_receiver(receiver, team): - return EditMemberPermissionsServiceResponse(error_message="Invalid key name") - - if (scopes_response := validate_scopes(permissions)).failed: - return EditMemberPermissionsServiceResponse(error_message=scopes_response.error) - - if not team: - return EditMemberPermissionsServiceResponse(error_message="Invalid team, something went wrong") - - user_team_perms: TeamMemberPermission | None = team.permissions.filter(user=receiver).first() - - if not user_team_perms: - team.permissions.add(TeamMemberPermission.objects.create(user=receiver, team=team, scopes=permissions)) - else: - user_team_perms.scopes = permissions - user_team_perms.save() - - return EditMemberPermissionsServiceResponse(True) - - -def validate_receiver(receiver: User | None, team: Organization | None) -> bool: - """ - Make sure receiver is in team and not already owner - """ - - if not receiver: - return False - - if not team: - return False - - if not team.members.filter(id=receiver.id).first(): - return False - - if not team.leader == receiver: - return True - return False diff --git a/backend/core/service/webhooks/__init__.py b/backend/core/service/webhooks/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/webhooks/auth.py b/backend/core/service/webhooks/auth.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/service/webhooks/get_url.py b/backend/core/service/webhooks/get_url.py deleted file mode 100644 index 53a95e884..000000000 --- a/backend/core/service/webhooks/get_url.py +++ /dev/null @@ -1,7 +0,0 @@ -import os - -from django.urls import reverse - - -def get_global_webhook_response_url(): - return os.environ.get("SITE_URL", default="http://127.0.0.1:8000") + reverse("api:public:webhooks:receive_global") diff --git a/backend/core/signals/__init__.py b/backend/core/signals/__init__.py deleted file mode 100644 index 6e946f549..000000000 --- a/backend/core/signals/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -from . import migrations -from . import signals diff --git a/backend/core/signals/migrations.py b/backend/core/signals/migrations.py deleted file mode 100644 index 3adafca2f..000000000 --- a/backend/core/signals/migrations.py +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import annotations - -import logging - -from django.db.models.signals import post_migrate -from django.dispatch import receiver - -from backend.core.data.default_feature_flags import default_feature_flags -from backend.core.data.default_quota_limits import default_quota_limits -from backend.models import FeatureFlags, QuotaLimit - - -@receiver(post_migrate) -def update_feature_flags(**kwargs): - for feature_flag in default_feature_flags: - existing_item = FeatureFlags.objects.filter(name=feature_flag.name).first() - - if existing_item: - name, value, description = ( - existing_item.name, - existing_item.value, - existing_item.description, - ) - - existing_item.name = name - existing_item.description = description - - if existing_item.name != name or existing_item.description != description: - existing_item.save() - logging.info(f"Updated feature flag: {feature_flag.name}") - else: - FeatureFlags.objects.create(name=feature_flag.name, value=feature_flag.default, description=feature_flag.description) - logging.info(f"Added feature flag: {feature_flag.name}") - - -@receiver(post_migrate) -def update_quota_limits(**kwargs): - for group in default_quota_limits: - for item in group.items: - existing = QuotaLimit.objects.filter(slug=f"{group.name}-{item.slug}").first() - if existing: - name, value, adjustable, description, limit_type = ( - existing.name, - existing.value, - existing.adjustable, - existing.description, - existing.limit_type, - ) - existing.name = item.name - existing.adjustable = item.adjustable - existing.description = item.description - existing.limit_type = item.period - if ( - item.name != name - or item.default_value != value - or item.adjustable != adjustable - or item.description != description - or item.period != limit_type - ): - logging.info(f"Updated QuotaLimit {item.name}") - existing.save() - else: - QuotaLimit.objects.create( - name=item.name, - slug=f"{group.name}-{item.slug}", - value=item.default_value, - adjustable=item.adjustable, - description=item.description, - limit_type=item.period, - ) - logging.info(f"Added QuotaLimit {item.name}") diff --git a/backend/core/signals/signals.py b/backend/core/signals/signals.py deleted file mode 100644 index 751a51d3d..000000000 --- a/backend/core/signals/signals.py +++ /dev/null @@ -1,98 +0,0 @@ -from __future__ import annotations - -from django.core.cache import cache -from django.core.cache.backends.redis import RedisCacheClient - -cache: RedisCacheClient = cache -from django.core.files.storage import default_storage -from django.db.models.signals import pre_save, post_delete, post_save, pre_delete -from django.dispatch import receiver -from django.urls import reverse - -import settings.settings -from backend.models import UserSettings, Receipt, User, FeatureFlags, VerificationCodes -from settings.helpers import send_email - - -@receiver(pre_save, sender=UserSettings) -def delete_old_profile_picture(sender, instance, **kwargs): - if not instance.pk: - return - - try: - old_profile = UserSettings.objects.get(pk=instance.pk) - except UserSettings.DoesNotExist: - return - - if old_profile.profile_picture and old_profile.profile_picture != instance.profile_picture: - # If the profile picture has been updated, delete the old file - old_profile.profile_picture.delete(save=False) - - -@receiver(post_delete, sender=UserSettings) -def set_profile_picture_to_none(sender, instance, **kwargs): - # Check if the file exists in the storage - if instance.profile_picture and default_storage.exists(instance.profile_picture.name): - instance.profile_picture.delete(save=False) - - -@receiver(pre_delete, sender=Receipt) -def delete_old_receipts(sender, instance, **kwargs): - # Check if the file exists in the storage - if instance.image and default_storage.exists(instance.image.name): - instance.image.delete(save=False) - instance.image = None - instance.save() - - -@receiver(post_save, sender=User) -def user_account_create_make_usersettings(sender, instance, created, **kwargs): - if created: - try: - users_settings = instance.user_profile - except UserSettings.DoesNotExist: - users_settings = None - - if not users_settings: - UserSettings.objects.create(user=instance) - - -@receiver(post_save, sender=FeatureFlags) -def refresh_feature_cache(sender, instance: FeatureFlags, **kwargs): - feature = instance.name - key = f"myfinances:feature_flag:{feature}" - - cached_value = cache.get(key) - - if cached_value: - return cache.delete(key) - - -@receiver(post_save, sender=User) -def send_welcome_email(sender, instance: User, created, **kwargs): - if created: - email_message = f""" - Welcome to MyFinances{f", {instance.first_name}" if instance.first_name else ""}! - - We're happy to have you join us. We are still in development and working on the core features. - - In app we have a live chat, so please drop us a message or email support@myfinances.cloud if you have any queries. - - Thank you for using MyFinances! - """ - magic_link = VerificationCodes.objects.create(user=instance, service="create_account") - token_plain = magic_link.token - magic_link.hash_token() - magic_link_url = settings.settings.SITE_URL + reverse( - "auth:login create_account verify", kwargs={"uuid": magic_link.uuid, "token": token_plain} - ) - email_message += f""" - To start with, you must first **verify this email** so that we can link your account to this email. - Click the link below to activate your account, no details are required, once pressed you're all set! - - Verify Link: {magic_link_url} - """ - - email = send_email(destination=instance.email, subject="Welcome to MyFinances", content=email_message) - - # User.send_welcome_email(instance) diff --git a/backend/core/types/__init__.py b/backend/core/types/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/types/emails.py b/backend/core/types/emails.py deleted file mode 100644 index cb1abc08c..000000000 --- a/backend/core/types/emails.py +++ /dev/null @@ -1,45 +0,0 @@ -from dataclasses import dataclass, field -from typing import TypedDict - -from mypy_boto3_sesv2.type_defs import SendEmailResponseTypeDef, SendBulkEmailResponseTypeDef - -from backend.core.utils.dataclasses import BaseServiceResponse - - -class SingleEmailSendServiceResponse(BaseServiceResponse[SendEmailResponseTypeDef]): ... - - -class BulkEmailSendServiceResponse(BaseServiceResponse[SendBulkEmailResponseTypeDef]): ... - - -class SingleTemplatedEmailContent(TypedDict): - template_name: str - template_data: dict | str - - -@dataclass(frozen=False) -class SingleEmailInput: - destination: str | list[str] - subject: str | None - content: str | SingleTemplatedEmailContent - ConfigurationSetName: str | None = None - from_address: str | None = None - from_address_name_prefix: str | None = None - - -@dataclass -class BulkEmailEmailItem: - destination: str - template_data: dict | str - cc: list[str] = field(default_factory=list) - bcc: list[str] = field(default_factory=list) - - -@dataclass(frozen=False) -class BulkTemplatedEmailInput: - email_list: list[BulkEmailEmailItem] - template_name: str - default_template_data: dict | str - ConfigurationSetName: str | None = None - from_address: str | None = None - from_address_name_prefix: str | None = None diff --git a/backend/core/types/htmx.py b/backend/core/types/htmx.py deleted file mode 100644 index d4d968897..000000000 --- a/backend/core/types/htmx.py +++ /dev/null @@ -1,24 +0,0 @@ -from django.contrib.auth.models import AnonymousUser -from django.core.handlers.wsgi import WSGIRequest -from django.http import HttpRequest -from django_htmx.middleware import HtmxDetails - -from backend.models import User, Organization - - -class HtmxHttpRequest(HttpRequest): - htmx: HtmxDetails - user: User - no_retarget: bool | None - - -class UnauthorizedHttpRequest(HttpRequest): - user: AnonymousUser - htmx: HtmxDetails - no_retarget: bool | None - - -class HtmxAnyHttpRequest(HttpRequest): - user: User | AnonymousUser - htmx: HtmxDetails - no_retarget: bool | None diff --git a/backend/core/types/requests.py b/backend/core/types/requests.py deleted file mode 100644 index 7120180e4..000000000 --- a/backend/core/types/requests.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Any - -from django.contrib.auth.models import AnonymousUser -from django.http import HttpRequest -from django_htmx.middleware import HtmxDetails - -from backend.models import User, Organization - - -class WebRequest(HttpRequest): - user: User - team: Organization | None - team_id: int | None - actor: User | Organization - - users_subscription: Any | None - - htmx: HtmxDetails - no_retarget: bool | None diff --git a/backend/core/utils/__init__.py b/backend/core/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/utils/calendar.py b/backend/core/utils/calendar.py deleted file mode 100644 index a16bf7f25..000000000 --- a/backend/core/utils/calendar.py +++ /dev/null @@ -1,24 +0,0 @@ -import datetime - -from django.utils import timezone - - -def get_months_text() -> list[str]: - return [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ] - - -def timezone_now() -> datetime.datetime: - return timezone.now() diff --git a/backend/core/utils/dataclasses.py b/backend/core/utils/dataclasses.py deleted file mode 100644 index 7255afd1c..000000000 --- a/backend/core/utils/dataclasses.py +++ /dev/null @@ -1,134 +0,0 @@ -from dataclasses import dataclass -from typing import TypeVar, Any, Optional, Generic - -T = TypeVar("T") - - -def extract_to_dataclass(request, class_type: T, request_types: list[str], *args, **kwargs) -> T: - """ - - Turn kwargs from Key:Value and get request.POST.get(value) and set class.key = request.POST.get(value) - - Usage: - - from pydantic.dataclasses import dataclass - from typing import Optional - - @dataclass - class MyView: - name: str - age: int - different: bool - non_required: Optional[str] - - def myview(request): - try: - data = extract_to_dataclass(request, MyView, ["post"], "name", "age", diff_bool="different") - except pydantic.ValidationError: - pass - """ - data: dict = {} - if "get" in request_types: - if args: - data |= {key: request.GET.get(key) for key in args} - - if kwargs: - data |= {key: request.GET.get(value) for key, value in kwargs.items()} - - if "post" in request_types: - if args: - data |= {key: request.POST.get(key) for key in args} - - if kwargs: - data |= {key: request.POST.get(value) for key, value in kwargs.items()} - - if "headers" in request_types: - if args: - data |= {key: request.headers.get(key) for key in args} - - if kwargs: - data |= {key: request.headers.get(value) for key, value in kwargs.items()} - - if isinstance(class_type, type): - return class_type(**data) - else: - raise TypeError("class_type must be a class") - - -class BaseServiceResponse(Generic[T]): - _success: bool = False - _response: Optional[T] = None - _error_message: str = "" - _status_code: Optional[int] = None - - def __init__(self, success: bool = False, response: Optional[T] = None, error_message: str = "", status_code: Optional[int] = None): - self._success = success - self._response = response - self._error_message = error_message - self._status_code = status_code - - @property - def success(self) -> bool: - if not isinstance(self._success, bool): - raise TypeError("success must be a boolean") - - return self._success - - @property - def response(self) -> T: - if self._response is None: - raise TypeError("response must be present if it was a successful response") - return self._response - - @property - def error_message(self) -> str: - if not isinstance(self._error_message, str): - raise TypeError("error_message must be a string") - return self._error_message - - @property - def status_code(self) -> int: - if not isinstance(self._status_code, int): - raise TypeError("status code must be an integer") - return self._status_code - - @property - def failed(self) -> bool: - return not self.success - - @property - def error(self) -> str: - return self.error_message if self.failed else "Unknown error" - - def __post_init__(self): - if self.success and self.response is None: - raise ValueError("Response cannot be None when success is True.") - if not self.success and self.response is not None: - raise ValueError("Response must be None when success is False.") - if not self.success and not self.error_message: - raise ValueError("Error message cannot be empty when success is False.") - - -# * BaseServiceResponse Usage - -# from backend.utils.dataclasses import BaseServiceResponse -# -# -# class XyzServiceResponse(BaseServiceResponse[ResponseObject]): -# response: Optional[ResponseObject] = None -# or -# ... - -# * Return Response - -# return CreateClientServiceResponse(False, error_message="my error") -# return CreateClientServiceResponse(False, ClientObject) - -# * View Usage -# -# client_response: CreateClientServiceResponse = create_client(request) -# -# if client_response.failed: -# print(client_response.error) -# else: -# print(client_response.response) # < ClientObject> diff --git a/backend/core/utils/feature_flags.py b/backend/core/utils/feature_flags.py deleted file mode 100644 index 297ef1e5f..000000000 --- a/backend/core/utils/feature_flags.py +++ /dev/null @@ -1,29 +0,0 @@ -from backend.models import FeatureFlags -from django.core.cache import cache -from django.core.cache.backends.redis import RedisCacheClient - -cache: RedisCacheClient = cache - - -def get_feature_status(feature, should_use_cache=True): - if should_use_cache: - key = f"myfinances:feature_flag:{feature}" - cached_value = cache.get(key) - if cached_value: - return cached_value - - value = FeatureFlags.objects.filter(name=feature).first() - if value: - if should_use_cache: - cache.set(key, value.value, timeout=300) - return value.value - else: - return False - - -def set_cache(key, value, timeout=300): - cache.set(key, value, timeout=timeout) - - -def get_cache(key): - return cache.get(key) diff --git a/backend/core/utils/http_utils.py b/backend/core/utils/http_utils.py deleted file mode 100644 index 95446893b..000000000 --- a/backend/core/utils/http_utils.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.http import HttpResponseRedirect - - -def redirect_to_last_visited(request, fallback_url="dashboard"): - """ - Redirects user to the last visited URL stored in session. - If no previous URL is found, redirects to the fallback URL. - :param request: HttpRequest object - :param fallback_url: URL to redirect to if no previous URL found - :return: HttpResponseRedirect object - """ - try: - last_visited_url = request.session.get("last_visited", fallback_url) - return HttpResponseRedirect(last_visited_url) - except KeyError: - return HttpResponseRedirect(fallback_url) diff --git a/backend/core/utils/quota_limit_ops.py b/backend/core/utils/quota_limit_ops.py deleted file mode 100644 index 3b57e62fa..000000000 --- a/backend/core/utils/quota_limit_ops.py +++ /dev/null @@ -1,42 +0,0 @@ -from django.contrib import messages -from django.http import HttpResponse, HttpResponseRedirect -from django.shortcuts import render -from django.urls import reverse - -from backend.models import QuotaLimit - - -def quota_usage_check_under( - request, limit: str | QuotaLimit, extra_data: str | int | None = None, api=False, htmx=False, add: int = 0 -) -> bool | HttpResponse | HttpResponseRedirect: - try: - quota_limit = QuotaLimit.objects.get(slug=limit) if isinstance(limit, str) else limit - except QuotaLimit.DoesNotExist: - return True - - if not quota_limit.strict_goes_above_limit(request.user, extra=extra_data, add=add): - return True - - if api and htmx: - messages.error(request, f"You have reached the quota limit for this service '{quota_limit.name}'") - return render(request, "base/toast.html", {"autohide": False}) - elif api: - return HttpResponse(status=403, content=f"You have reached the quota limit for this service '{quota_limit.name}'") - messages.error(request, f"You have reached the quota limit for this service '{quota_limit.name}'") - try: - last_visited_url = request.session["last_visited"] - current_url = request.build_absolute_uri() - if last_visited_url != current_url: - return HttpResponseRedirect(last_visited_url) - except KeyError: - pass - return HttpResponseRedirect(reverse("dashboard")) - - -def render_quota_error(request, quota_limit): - messages.error(request, f"You have reached the quota limit for this service '{quota_limit.slug}'") - return render(request, "partials/messages_list.html", {"autohide": False}) - - -def render_quota_error_response(quota_limit): - return HttpResponse(status=403, content=f"You have reached the quota limit for this service '{quota_limit.slug}'") diff --git a/backend/core/utils/service_retry.py b/backend/core/utils/service_retry.py deleted file mode 100644 index 8c7579382..000000000 --- a/backend/core/utils/service_retry.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Callable, TypeVar -from backend.core.utils.dataclasses import BaseServiceResponse - -T = TypeVar("T", bound=BaseServiceResponse) - - -def retry_handler(function: Callable[..., T], *args, retry_max_attempts: int = 3, **kwargs) -> T: - attempts: int = 0 - - while attempts < retry_max_attempts: - response: T = function(*args, **kwargs) - - if response.failed: - attempts += 1 - if attempts == retry_max_attempts: - return response - continue - return response - return response diff --git a/backend/core/views/__init__.py b/backend/core/views/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/views/auth/__init__.py b/backend/core/views/auth/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/views/auth/create_account.py b/backend/core/views/auth/create_account.py deleted file mode 100644 index 69822ee51..000000000 --- a/backend/core/views/auth/create_account.py +++ /dev/null @@ -1,97 +0,0 @@ -from django.contrib import messages -from django.contrib.auth import authenticate -from django.core.exceptions import ValidationError -from django.core.validators import validate_email -from django.db.models import Q -from django.shortcuts import redirect, render -from django.views import View - -from backend.core.models import User -from backend.core.utils.feature_flags import get_feature_status -from settings.settings import ( - SOCIAL_AUTH_GITHUB_ENABLED, - SOCIAL_AUTH_GOOGLE_OAUTH2_ENABLED, -) - - -class CreateAccountChooseView(View): - def get(self, request): - if request.user.is_authenticated: - return redirect("dashboard") - SIGNUPS_ENABLED = get_feature_status("areSignupsEnabled") - if not SIGNUPS_ENABLED: - messages.error(request, "New account signups are currently disabled") - return redirect("auth:login") - return render( - request, - "pages/auth/create_account_choose.html", - { - "github_enabled": SOCIAL_AUTH_GITHUB_ENABLED, - "google_enabled": SOCIAL_AUTH_GOOGLE_OAUTH2_ENABLED, - }, - ) - - -class CreateAccountManualView(View): - def get(self, request): - if request.user.is_authenticated: - return redirect("dashboard") - SIGNUPS_ENABLED = get_feature_status("areSignupsEnabled") - if not SIGNUPS_ENABLED: - messages.error(request, "New account signups are currently disabled") - return redirect("auth:login") - return render(request, "pages/auth/create_account_manual.html") - - def post(self, request): - if request.user.is_authenticated: - return redirect("dashboard") - SIGNUPS_ENABLED = get_feature_status("areSignupsEnabled") - if not SIGNUPS_ENABLED: - messages.error(request, "New account signups are currently disabled") - return redirect("auth:login") - - email = request.POST.get("email") - password = request.POST.get("password") - password_confirm = request.POST.get("confirm_password") - - if password != password_confirm: - messages.error(request, "Passwords don't match") - return render( - request, - "pages/auth/create_account_manual.html", - {"attempted_email": email}, - ) - - try: - validate_email(email) - except ValidationError: - messages.error(request, "Invalid email") - return render(request, "pages/auth/create_account_manual.html") - - emails_taken = User.objects.filter(Q(username=email) | Q(email=email)).exists() - - if emails_taken: - messages.error(request, "Email is already taken") - return render(request, "pages/auth/create_account_manual.html") - - if len(password) < 6: - messages.error(request, "Password must be at least 6 characters") - return render(request, "pages/auth/create_account_manual.html") - - created_user = User.objects.create_user(email=email, username=email, password=password) - created_user.awaiting_email_verification = True - created_user.save() - # created_user.is_active = False - # created_user.save() - - user = authenticate(request, username=email, password=password) - if not user: - messages.error(request, "Something went wrong") - return render(request, "pages/auth/create_account_manual.html") - - # login(request, user) - messages.success( - request, - "Successfully created account. Please verify your account via the email we are " "sending you now!", - ) - return redirect("auth:login") diff --git a/backend/core/views/auth/helpers.py b/backend/core/views/auth/helpers.py deleted file mode 100644 index e59ee1841..000000000 --- a/backend/core/views/auth/helpers.py +++ /dev/null @@ -1,3 +0,0 @@ -def generate_magic_link(user): - - return "" diff --git a/backend/core/views/auth/login.py b/backend/core/views/auth/login.py deleted file mode 100644 index d4771fb4a..000000000 --- a/backend/core/views/auth/login.py +++ /dev/null @@ -1,275 +0,0 @@ -from textwrap import dedent - -import django_ratelimit -from django.contrib import messages -from django.contrib.auth import login, logout, authenticate -from django.contrib.auth.hashers import check_password -from django.core.exceptions import ValidationError -from django.core.validators import validate_email -from django.http import HttpRequest, HttpResponse -from django.shortcuts import render, redirect -from django.urls import resolve, reverse -from django.utils.http import url_has_allowed_host_and_scheme -from django.urls.exceptions import Resolver404 -from django.utils.http import url_has_allowed_host_and_scheme -from django.utils.decorators import method_decorator -from django.views import View -from django.views.decorators.http import require_GET, require_POST -from django_ratelimit.decorators import ratelimit - -from backend.decorators import not_authenticated -from backend.models import LoginLog, User, VerificationCodes, AuditLog -from backend.core.views.auth.verify import create_magic_link -from backend.core.types.htmx import HtmxAnyHttpRequest -from settings.helpers import send_email, ARE_EMAILS_ENABLED - -from settings.settings import ( - SOCIAL_AUTH_GITHUB_ENABLED, - SOCIAL_AUTH_GOOGLE_OAUTH2_ENABLED, -) - - -@require_GET -@not_authenticated -def login_initial_page(request: HttpRequest): - redirect_url = request.GET.get("next") - - return render( - request, - "pages/auth/login_initial.html", - {"github_enabled": SOCIAL_AUTH_GITHUB_ENABLED, "next": redirect_url, "google_enabled": SOCIAL_AUTH_GOOGLE_OAUTH2_ENABLED}, - ) - - -@not_authenticated -@require_POST -def login_manual(request: HttpRequest): - email = request.POST.get("email") - password = request.POST.get("password") - redirect_url = request.POST.get("next", "") - - if not email: - messages.error(request, "Please enter an email") - return redirect_to_login("", redirect_url) - - try: - validate_email(email) - except ValidationError: - messages.error(request, "Please enter a valid email") - return redirect_to_login("", redirect_url) - - if not password: - messages.error(request, "Please enter a password") - return redirect_to_login(email, redirect_url) - - user = authenticate(request, username=email, password=password) - - if not user: - messages.error(request, "Incorrect email or password") - return redirect_to_login(email, redirect_url) - - if user.awaiting_email_verification and ARE_EMAILS_ENABLED: # type: ignore[attr-defined] - messages.error(request, "You must verify your email before logging in.") - return redirect_to_login(email, redirect_url) - - login(request, user) - - if user.require_change_password: # type: ignore[attr-defined] - messages.warning(request, "You have been requested by an administrator to change your account password.") - return redirect("settings:change_password") - - if url_has_allowed_host_and_scheme(redirect_url, allowed_hosts=None): - try: - resolve(redirect_url) - return redirect(redirect_url) - except Resolver404: - return redirect("dashboard") - else: - return redirect("dashboard") - - -def redirect_to_login(email: str, redirect_url: str): - if not url_has_allowed_host_and_scheme(redirect_url, allowed_hosts=None): - redirect_url = reverse("dashboard") - return redirect(f"{reverse('auth:login')}?email={email}&next={redirect_url}") - - -def render_error_toast_message(request: HttpRequest, message: str) -> HttpResponse: - messages.error(request, message) - return render_toast_message(request) - - -def render_toast_message(request: HttpRequest) -> HttpResponse: - return render(request, "base/toasts.html") # htmx will handle the toast - - -class MagicLinkRequestView(View): - @method_decorator(ratelimit(key="post:email", method=django_ratelimit.UNSAFE, rate="5/m")) - @method_decorator(ratelimit(key="post:email", method=django_ratelimit.UNSAFE, rate="10/5m")) - @method_decorator(ratelimit(key="ip", method=django_ratelimit.UNSAFE, rate="2/m")) - @method_decorator(ratelimit(key="ip", method=django_ratelimit.UNSAFE, rate="3/10m")) - @method_decorator(ratelimit(key="ip", method=django_ratelimit.UNSAFE, rate="6/1h")) - def post(self, request: HtmxAnyHttpRequest) -> HttpResponse | bool: - if request.user.is_authenticated: - return redirect("dashboard") - if not request.htmx: - return redirect("auth:login") - - email = request.POST.get("email") - try: - user = User.objects.get(email=email) - except User.DoesNotExist: - return self.send_message(request) - - if not user.is_active: - return self.send_message(request, "This account is not currently active.", False) - - magic_link, plain_token = create_magic_link(user, service="login") - self.send_magic_link_email(request, user, str(magic_link.uuid), plain_token) - self.send_message(request, should_redirect=False) - return render(request, "pages/auth/magic_link_waiting.html", {"email": request.POST.get("email")}) - - def send_message( - self, request: HttpRequest, message: str = "", success: bool = True, should_redirect: bool = True - ) -> HttpResponse | bool: - message = message or "If this is a valid email address, we have sent you an email! Keep this tab open!" - if success: - messages.success(request, message) - else: - messages.error(request, message) - - if should_redirect: - return render_toast_message(request) - else: - return True - - def send_magic_link_email(self, request: HttpRequest, user: User, uuid: str, plain_token: str) -> None: - magic_link_url = request.build_absolute_uri(reverse("auth:login magic_link verify", kwargs={"uuid": uuid, "token": plain_token})) - - send_email( - destination=user.email, - subject="Login Request", - content=dedent( - f""" - Hi {user.first_name if user.first_name else "User"}, - - A login request was made on your MyFinances account. If this was not you, please ignore - this email. - - If you would like to login, please use the following link: \n {magic_link_url} - """ - ), - ) - - -class MagicLinkWaitingView(View): - def post(self, request: HtmxAnyHttpRequest) -> HttpResponse: - if request.user.is_authenticated: - return redirect("dashboard") - if not request.htmx: - return redirect("auth:login") - return render(request, "pages/auth/magic_link_waiting.html", {"email": request.POST.get("email")}) - - -class MagicLinkVerifyView(View): - def get(self, request: HttpRequest, uuid: str, token: str) -> HttpResponse: - if request.user.is_authenticated: - return redirect("dashboard") - - magic_link = get_magic_link(uuid) - - magic_link_valid, magic_link_msg = is_magiclink_valid(magic_link, token) - if not magic_link_valid: - messages.error(request, magic_link_msg) - return redirect("auth:login") - - # user = magic_link.user - # magic_link.delete() - # user.backend = "backend.auth_backends.EmailInsteadOfUsernameBackend" - # login(request, magic_link.user) - - return render(request, "pages/auth/magic_link_verify.html", {"uuid": uuid, "token": token}) - - # - # messages.success(request, "Successfully logged in") - # # TODO: Add page to make sure they click an extra "verify request btn" - # return redirect("dashboard") - - -class MagicLinkVerifyDecline(View): - def post(self, request: HtmxAnyHttpRequest, uuid: str, token: str) -> HttpResponse: - if request.user.is_authenticated or not request.htmx: - return redirect("dashboard") - - magic_link = get_magic_link(uuid) - magic_link_valid, magic_link_msg = is_magiclink_valid(magic_link, token) - - if not magic_link_valid or magic_link is None: - messages.error(request, magic_link_msg) - return render_toast_message(request) - else: - user = magic_link.user - magic_link.delete() - - AuditLog.objects.create(user=user, action="magic link declined") - messages.success(request, "Successfully declined the magic link verification request.") - return render(request, "pages/auth/_magic_link_partial.html", {"declined": True}) - - -class MagicLinkVerifyAccept(View): - def post(self, request: HtmxAnyHttpRequest, uuid: str, token: str) -> HttpResponse: - if request.user.is_authenticated or not request.htmx: - return redirect("dashboard") - - magic_link = get_magic_link(uuid) - magic_link_valid, magic_link_msg = is_magiclink_valid(magic_link, token) - - if not magic_link_valid or magic_link is None: - messages.error(request, magic_link_msg) - return render_toast_message(request) - else: - user = magic_link.user - magic_link.delete() - - user.backend = "backend.auth_backends.EmailInsteadOfUsernameBackend" # type: ignore[attr-defined] - LoginLog.objects.create(user=user, service=LoginLog.ServiceTypes.MAGIC_LINK) - AuditLog.objects.create(user=user, action="magic link accepted") - login(request, magic_link.user) - - messages.success(request, "Successfully accepted the magic link verification request.") - return render(request, "pages/auth/_magic_link_partial.html", {"accepted": True}) - - -def is_magiclink_valid(magic_link: VerificationCodes | None, token: str) -> tuple[bool, str]: - if not magic_link: - return False, "Invalid magic link" - - if not magic_link.is_active(): - return False, "This link has expired" - - if not check_password(token, magic_link.token): - return False, "Invalid magic link" - - return True, "" - - -def get_magic_link(uuid: str) -> VerificationCodes | None: - try: - return VerificationCodes.objects.get(uuid=uuid, service="login") - except VerificationCodes.DoesNotExist: - return None - - -def logout_view(request): - logout(request) - - messages.success(request, "You've now been logged out.") - - return redirect("auth:login") # + "?next=" + request.POST.get('next')) - - -@not_authenticated -def forgot_password_page(request: HttpRequest): - if request.user.is_authenticated: - return redirect("dashboard") - return render(request, "pages/auth/forgot_password.html") diff --git a/backend/core/views/auth/passwords/__init__.py b/backend/core/views/auth/passwords/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/views/auth/passwords/generate.py b/backend/core/views/auth/passwords/generate.py deleted file mode 100644 index 1067f8d37..000000000 --- a/backend/core/views/auth/passwords/generate.py +++ /dev/null @@ -1,105 +0,0 @@ -from datetime import datetime, timedelta, date - -from django.contrib import messages -from django.contrib.auth.hashers import make_password -from django.core.exceptions import ValidationError -from django.core.validators import validate_email -from django.shortcuts import redirect -from django.urls import reverse, resolve, NoReverseMatch -from django.utils import timezone -from django.utils.http import url_has_allowed_host_and_scheme - -from backend.models import User, PasswordSecret -from backend.core.models import RandomCode -from backend.core.types.htmx import HtmxHttpRequest -from settings import settings - - -def msg_if_valid_email_then_sent(request): - return messages.success( - request, - f"If this is a valid email address then we have sent you an email.\n Please check spam, if you cannot find the email press forgot password again.", - ) - - -def set_password_generate(request: HtmxHttpRequest): - if not request.user.is_superuser or not request.user.is_staff: - return redirect("dashboard") - - USER = request.GET.get("id") - NEXT = request.GET.get("next") or "index" - - if USER is None or not USER.isnumeric(): - messages.error(request, "User ID must be a valid integer") - return redirect("dashboard") - - USER_OBJ = User.objects.filter(id=USER).first() - - if not USER_OBJ: - messages.error(request, f"User not found") - return redirect("dashboard") - CODE = RandomCode(40) - HASHED_CODE = make_password(CODE, salt=settings.SECRET_KEY) - - PWD_SECRET, created = PasswordSecret.objects.update_or_create( - user=USER_OBJ, - defaults={"expires": date.today() + timedelta(days=3), "secret": HASHED_CODE}, - ) - PWD_SECRET.save() - messages.error( - request, - f'Successfully created a code. {CODE}', - ) - - if url_has_allowed_host_and_scheme(NEXT, allowed_hosts=None): - try: - resolve(NEXT) - return redirect(NEXT) - except NoReverseMatch: - return redirect("dashboard") - else: - return redirect("dashboard") - - -def password_reset(request: HtmxHttpRequest): - EMAIL = request.POST.get("email") - - # if not EMAIL_SERVER_ENABLED: - # messages.error(request, "Unfortunately our email server is not currently available.") - - if not EMAIL: - msg_if_valid_email_then_sent(request) - return redirect("login forgot_password") - - try: - validate_email(EMAIL) - except ValidationError as e: - msg_if_valid_email_then_sent(request) - return redirect("login forgot_password") - - USER = User.objects.filter(email=EMAIL).first() - - if not USER: - msg_if_valid_email_then_sent(request) - return redirect("login forgot_password") - - PasswordSecret.objects.filter(user=USER).all().delete() - - CODE = RandomCode(40) - HASHED_CODE = make_password(CODE) - expires_date = date.today() + timedelta(days=3) - expires_datetime = timezone.make_aware(datetime.combine(expires_date, datetime.min.time())) - - PasswordSecret.objects.create(user=USER, expires=expires_datetime, secret=HASHED_CODE) - - # SEND_SENDGRID_EMAIL(USER.email, "Password Reset" ,f""" - # My Finances | Password Reset - # You've now got a new password reset code. - # - # Reset Here: {request.build_absolute_uri(reverse('user set password', args=(CODE,)))} - # """, request=request) - print(f"code is {CODE}") - - msg_if_valid_email_then_sent(request) - - return redirect("login forgot_password") diff --git a/backend/core/views/auth/passwords/set.py b/backend/core/views/auth/passwords/set.py deleted file mode 100644 index ad79e3893..000000000 --- a/backend/core/views/auth/passwords/set.py +++ /dev/null @@ -1,45 +0,0 @@ -from django.contrib import messages -from django.contrib.auth.hashers import check_password -from django.http import ( - HttpRequest, -) -from django.shortcuts import redirect -from django.utils import timezone -from django.views.decorators.http import require_POST - -from backend.decorators import not_authenticated -from backend.models import PasswordSecret - - -@not_authenticated -@require_POST -def set_password_set(request: HttpRequest, secret): - password = request.POST.get("password", "") - if len(password) > 7: - SECRET_RETURNED = PasswordSecret.objects.all() - - for SECRET in SECRET_RETURNED: - if SECRET.expires is not None and SECRET.expires < timezone.now(): - SECRET.delete() - continue - elif check_password(secret, SECRET.secret): - USER = SECRET.user - USER.set_password(password) - USER.save() - SECRET.delete() - messages.success(request, "Successfully changed your password.") - return redirect("auth:login") - - messages.error( - request, - "Invalid password code. The code has either expired or was not entered correctly." - "Please contact an administrator for support.", - ) - return redirect("auth:login") - - else: - messages.error(request, "No code provided. Please contact an administrator for support.") - return redirect("auth:login") - - messages.error(request, "Sorry, somethging went wrong!") - return redirect("auth:login") diff --git a/backend/core/views/auth/passwords/view.py b/backend/core/views/auth/passwords/view.py deleted file mode 100644 index 24c50fe15..000000000 --- a/backend/core/views/auth/passwords/view.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.contrib import messages -from django.contrib.auth.hashers import check_password -from django.http import HttpRequest -from django.shortcuts import render, redirect -from django.utils import timezone - -from backend.core.models import PasswordSecret -from backend.decorators import not_authenticated - - -@not_authenticated -def set_password(request: HttpRequest, secret): - SECRET_RETURNED = PasswordSecret.objects.all() - SECRET_RETURNED.filter(expires__lte=timezone.now()).all().delete() - - for SECRET in SECRET_RETURNED: - if check_password(secret, SECRET.secret): - return render(request, "pages/reset_password.html", {"secret": secret}) - - messages.error(request, "Invalid or expired password reset code") - return redirect("dashboard") diff --git a/backend/core/views/auth/urls.py b/backend/core/views/auth/urls.py deleted file mode 100644 index 1cc2839b2..000000000 --- a/backend/core/views/auth/urls.py +++ /dev/null @@ -1,75 +0,0 @@ -from django.urls import path - -from . import login, create_account, verify -from .passwords import view as passwords_view, generate as passwords_generate, set as passwords_set - -urlpatterns = [ - path("login/", login.login_initial_page, name="login"), - path("login/manual/", login.login_manual, name="login manual"), - path("login/magic_link/request/", login.MagicLinkRequestView.as_view(), name="login magic_link request"), - path("login/magic_link/request/wait/", login.MagicLinkWaitingView.as_view(), name="login magic_link request wait"), - path("login/magic_link/verify///", login.MagicLinkVerifyView.as_view(), name="login magic_link verify"), - path( - "login/magic_link/verify///accept/", - login.MagicLinkVerifyAccept.as_view(), - name="login magic_link verify accept", - ), - path( - "login/magic_link/verify///decline/", - login.MagicLinkVerifyDecline.as_view(), - name="login magic_link verify decline", - ), - path( - "login/forgot_password/", - login.forgot_password_page, - name="login forgot_password", - ), - path("logout/", login.logout_view, name="logout"), - path( - "create_account/", - create_account.CreateAccountChooseView.as_view(), - name="login create_account", - ), - path( - "create_account/manual/", - create_account.CreateAccountManualView.as_view(), - name="login create_account manual", - ), - path( - "create_account/verify///", - verify.create_account_verify, - name="login create_account verify", - ), - path( - "create_account/verify/resend/", - verify.resend_verification_code, - name="login create_account verify resend", - ), - # path( - # "login/magic_link//", - # login.magic_link, - # name="login magic_link", - # ) - path( - "reset-password/", - passwords_generate.password_reset, - name="user set password reset", - ), - path( - "set-password//", - passwords_view.set_password, - name="user set password", - ), - path( - "set-password//set/", - passwords_set.set_password_set, - name="user set password set", - ), - path( - "admin/generate-password/", - passwords_generate.set_password_generate, - name="admin set password generate", - ), -] - -app_name = "auth" diff --git a/backend/core/views/auth/verify.py b/backend/core/views/auth/verify.py deleted file mode 100644 index df2eafe39..000000000 --- a/backend/core/views/auth/verify.py +++ /dev/null @@ -1,92 +0,0 @@ -from textwrap import dedent - -from django.contrib import messages -from django.contrib.auth.hashers import check_password -from django.shortcuts import redirect -from django.urls import reverse -from django.views.decorators.http import require_POST -from django_ratelimit.decorators import ratelimit - -from backend.models import VerificationCodes, User, TracebackError -from settings import settings -from settings.helpers import send_email, ARE_EMAILS_ENABLED - - -def create_account_verify(request, uuid, token): - object = VerificationCodes.objects.filter(uuid=uuid, service="create_account").first() - - if not object: - messages.error(request, "Invalid URL") # Todo: add some way a user can resend code? - return redirect("auth:login create_account") - - if not object.is_active(): - messages.error(request, "This code has already expired") # Todo: add some way a user can resend code? - return redirect("auth:login create_account") - - if not object.user.awaiting_email_verification: - messages.error(request, "Your email has already been verified. You can login.") - return redirect("auth:login") - - if not check_password(token, object.token): - messages.error(request, "This verification token is invalid.") - return redirect("auth:login create_account") - - user = object.user - user.is_active = True - user.awaiting_email_verification = False - user.save() - object.delete() - - messages.success(request, "Successfully verified your email! You can now login.") - return redirect("auth:login") - - -def create_magic_link(user: User, service: str) -> tuple[VerificationCodes, str]: - magic_link = VerificationCodes.objects.create(user=user, service=service) - token_plain = magic_link.token - magic_link.hash_token() - return magic_link, token_plain - - -@ratelimit(group="resend_verification_code", key="ip", rate="1/m") -@ratelimit(group="resend_verification_code", key="ip", rate="3/25m") -@ratelimit(group="resend_verification_code", key="ip", rate="10/6h") -@ratelimit(group="resend_verification_code", key="post:email", rate="1/m") -@ratelimit(group="resend_verification_code", key="post:email", rate="3/25m") -@require_POST -def resend_verification_code(request): - email = request.POST.get("email") - if not email: - messages.error(request, "Invalid resend verification request") - return redirect("auth:login") - if not ARE_EMAILS_ENABLED: - messages.error(request, "Emails are currently disabled.") - TracebackError.objects.create(error="Emails are currently disabled.") - return redirect("auth:login create_account") - try: - user = User.objects.get(email=email) - except User.DoesNotExist: - messages.error(request, "Invalid resend verification request") - return redirect("auth:login create_account") - VerificationCodes.objects.filter(user=user, service="create_account").delete() - magic_link = create_magic_link(user, "create_account") - magic_link_url = settings.SITE_URL + reverse("auth:login create_account verify", kwargs={"uuid": magic_link.uuid, "token": token_plain}) - - send_email( - destination=email, - subject="Verify your email", - content=dedent( - f""" - Hi {user.first_name if user.first_name else "User"}, - - Verification for your email has been requested to link this email to your MyFinances account. - If this wasn't you, you can simply ignore this email. - - If it was you, you can complete the verification by clicking the link below. - Verify Link: {magic_link_url} - """ - ), - ) - - messages.success(request, "Verification email sent, check your inbox or spam!") - return redirect("auth:login") diff --git a/backend/core/views/emails/__init__.py b/backend/core/views/emails/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/views/emails/dashboard.py b/backend/core/views/emails/dashboard.py deleted file mode 100644 index 1dffbc89e..000000000 --- a/backend/core/views/emails/dashboard.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import annotations - -from django.http import HttpResponse -from django.shortcuts import render - -from backend.decorators import feature_flag_check, web_require_scopes -from backend.core.types.htmx import HtmxHttpRequest - - -@feature_flag_check("areUserEmailsAllowed", status=True) -@web_require_scopes("emails:read", False, False, "dashboard") -def dashboard(request: HtmxHttpRequest) -> HttpResponse: - return render(request, "pages/emails/dashboard.html", {}) diff --git a/backend/core/views/emails/urls.py b/backend/core/views/emails/urls.py deleted file mode 100644 index 1ed17a05d..000000000 --- a/backend/core/views/emails/urls.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import annotations - -from django.urls import path - -from . import dashboard - -urlpatterns = [ - path( - "", - dashboard.dashboard, - name="dashboard", - ), -] - -app_name = "emails" diff --git a/backend/core/views/other/__init__.py b/backend/core/views/other/__init__.py deleted file mode 100644 index 064a9bbc5..000000000 --- a/backend/core/views/other/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .index import index -from .errors import universal, e_403 diff --git a/backend/core/views/other/errors.py b/backend/core/views/other/errors.py deleted file mode 100644 index 5cc555a7c..000000000 --- a/backend/core/views/other/errors.py +++ /dev/null @@ -1,63 +0,0 @@ -import traceback - -from django.contrib import messages -from django.http import HttpRequest -from django.shortcuts import redirect -from django_ratelimit.exceptions import Ratelimited - -from backend.models import TracebackError, AuditLog - - -def universal(request: HttpRequest, exception=None): - messages.error( - request, - "Sorry, something went wrong on our end! We've contacted our team, please email us if this issue continues.", - ) - traceback.print_exc() - exec_error = traceback.format_exc() - print(f"WAS A TRACEBACK ERROR: EXCEPTION: {exception}") - - if len(exec_error) > 4999: - return - - if request.user.is_authenticated: - messages.error(request, "Sorry, something went wrong!") - TracebackError(user=request.user, error=exec_error).save() - else: - TracebackError(error=exec_error).save() - - return redirect("dashboard") - - -def e_403(request: HttpRequest, exception=None): - if isinstance(exception, Ratelimited): - messages.error( - request, - "Woah, slow down there. You've been temporarily blocked from this page due to extreme requests.", - ) - user_ip = request.META.get("REMOTE_ADDR") - user_id = f"User #{request.user.id}" if request.user.is_authenticated else "Not logged in" - action = f"{user_ip} | Ratelimited | {user_id}" - auditlog = AuditLog(action=action) - if request.user.is_authenticated: - auditlog.user = request.user - auditlog.save() - return redirect("auth:login") - else: - messages.error( - request, - "Sorry, something went wrong on our end!" "We've contacted our team, please email us if this issue continues.", - ) - traceback.print_exc() - exec_error = traceback.format_exc() - print(f"WAS A TRACEBACK ERROR: EXCEPTION: {exception}") - - if len(exec_error) > 4999: - return - - if request.user.is_authenticated: - TracebackError(user=request.user, error=exec_error).save() - else: - TracebackError(error=exec_error).save() - - return redirect("dashboard") diff --git a/backend/core/views/other/index.py b/backend/core/views/other/index.py deleted file mode 100644 index e3b45f23a..000000000 --- a/backend/core/views/other/index.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.http import HttpRequest -from django.shortcuts import render -from login_required import login_not_required - - -def index(request: HttpRequest): - return render(request, "pages/landing/index.html") - - -@login_not_required -def pricing(request: HttpRequest): - return render(request, "pages/landing/pricing.html") - - -def dashboard(request: HttpRequest): - return render(request, "pages/dashboard.html") diff --git a/backend/core/views/quotas/__init__.py b/backend/core/views/quotas/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/views/quotas/view.py b/backend/core/views/quotas/view.py deleted file mode 100644 index 1671067e5..000000000 --- a/backend/core/views/quotas/view.py +++ /dev/null @@ -1,28 +0,0 @@ -from django.http import HttpResponse -from django.shortcuts import render - -from backend.decorators import superuser_only -from backend.models import QuotaIncreaseRequest, QuotaLimit -from backend.core.types.htmx import HtmxHttpRequest - - -def quotas_page(request: HtmxHttpRequest) -> HttpResponse: - groups = list(QuotaLimit.objects.values_list("slug", flat=True).distinct()) - - quotas = {q.split("-")[0] for q in groups if q.split("-")} - - return render( - request, - "pages/quotas/dashboard.html", - {"quotas": quotas}, - ) - - -def quotas_list(request: HtmxHttpRequest, group: str) -> HttpResponse: - return render(request, "pages/quotas/list.html", {"group": group}) - - -@superuser_only -def view_quota_increase_requests(request: HtmxHttpRequest) -> HttpResponse: - requests = QuotaIncreaseRequest.objects.filter(status="pending").order_by("-created_at") - return render(request, "pages/quotas/view_requests.html", {"requests": requests}) diff --git a/backend/core/views/settings/__init__.py b/backend/core/views/settings/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/views/settings/teams.py b/backend/core/views/settings/teams.py deleted file mode 100644 index 5134fe578..000000000 --- a/backend/core/views/settings/teams.py +++ /dev/null @@ -1,81 +0,0 @@ -from typing import Optional - -from django.db.models import When, Case, BooleanField, QuerySet -from django.shortcuts import render - -from backend.models import Organization, User -from backend.core.service.teams.fetch import get_all_users_teams -from backend.core.types.requests import WebRequest - - -def teams_dashboard(request: WebRequest): - context: dict[str, str | int] = {} - - users_team: Optional[Organization] = request.user.logged_in_as_team - - if not users_team: - user_with_counts = User.objects.prefetch_related("teams_joined", "teams_leader_of").get(pk=request.user.pk) - return render( - request, - "pages/settings/teams/main.html", - context - | { - "team": None, - "team_count": user_with_counts.teams_joined.count() + user_with_counts.teams_leader_of.count(), - }, - ) - - try: - team = ( - Organization.objects.annotate( - is_leader=Case( - When(leader=request.user, then=True), - default=False, - output_field=BooleanField(), - ), - ) - .prefetch_related("members", "permissions") - .get(id=users_team.id) - ) - - user_permissions: dict[User, list] = {} - - for member in team.members.all(): - member_perms = list(team.permissions.filter(user=member).values_list("scopes", flat=True)) - - if len(member_perms) > 0: - user_permissions[member] = member_perms[0] - else: - user_permissions[member] = [] - - except Organization.DoesNotExist: - user_with_counts = User.objects.prefetch_related("teams_joined", "teams_leader_of").get(pk=request.user.pk) - return render( - request, - "pages/settings/teams/main.html", - context - | { - "team": None, - "team_count": user_with_counts.teams_joined.count() + user_with_counts.teams_leader_of.count(), - }, - ) - - return render( - request, - "pages/settings/teams/main.html", - context | {"team": team, "user_permissions": user_permissions}, - ) - - -def login_to_team_page(request: WebRequest, all_teams: QuerySet[Organization]): - print(all_teams) - return render(request, "pages/settings/teams/login_to_team.html", {"team_list": all_teams}) - - -def teams_dashboard_handler(request: WebRequest): - all_teams: QuerySet[Organization] = get_all_users_teams(request) - logged_in_team: Organization | None = request.user.logged_in_as_team - - if not logged_in_team: - return login_to_team_page(request, all_teams) - return teams_dashboard(request) diff --git a/backend/core/views/settings/urls.py b/backend/core/views/settings/urls.py deleted file mode 100644 index a56449426..000000000 --- a/backend/core/views/settings/urls.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.urls import path - -from backend.core.views.settings.view import change_password, view_settings_page_endpoint - -urlpatterns = [ - path("", view_settings_page_endpoint, name="dashboard"), - path("/", view_settings_page_endpoint, name="dashboard with page"), - path( - "profile/change_password/", - change_password, - name="change_password", - ), -] - -app_name = "settings" diff --git a/backend/core/views/settings/view.py b/backend/core/views/settings/view.py deleted file mode 100644 index 9df835c65..000000000 --- a/backend/core/views/settings/view.py +++ /dev/null @@ -1,84 +0,0 @@ -from django.views.decorators.http import require_http_methods -from django.contrib.auth import update_session_auth_hash -from django.contrib import messages -from django.shortcuts import redirect -from django.shortcuts import render - -from backend.core.service.settings.view import ( - validate_page, - account_page_context, - api_keys_page_context, - account_defaults_context, - email_templates_context, -) -from backend.core.types.requests import WebRequest - - -@require_http_methods(["GET"]) -def view_settings_page_endpoint(request: WebRequest, page: str | None = None): - if not validate_page(page): - messages.error(request, "Invalid settings page") - if request.htmx: - return render(request, "base/toast.html") - return redirect("settings:dashboard") - - context: dict = {} - - match page: - case "account": - account_page_context(request, context) - case "api_keys": - api_keys_page_context(request, context) - case "account_defaults": - account_defaults_context(request, context) - case "email_templates": - email_templates_context(request, context) - - template = f"pages/settings/pages/{page or 'profile'}.html" - - if not page or not request.GET.get("on_main"): - context["page_template"] = template - return render(request, "pages/settings/main.html", context) - - response = render(request, template, context) - - response.no_retarget = True # type: ignore[attr-defined] - return response - - -def change_password(request: WebRequest): - if request.method == "POST": - current_password = request.POST.get("current_password") - password = request.POST.get("password") - confirm_password = request.POST.get("confirm_password") - - error = validate_password_change(request.user, current_password, password, confirm_password) - - if error: - messages.error(request, error) - return redirect("settings:change_password") - - # If no errors, update the password - request.user.set_password(password) - request.user.save() - update_session_auth_hash(request, request.user) - messages.success(request, "Successfully changed your password.") - return redirect("settings:dashboard") - - return render(request, "pages/reset_password.html", {"type": "change"}) - - -def validate_password_change(user, current_password, new_password, confirm_password): - if not user.check_password(current_password): - return "Incorrect current password" - - if new_password != confirm_password: - return "Passwords don't match" - - if not new_password: - return "Something went wrong, no password was provided." - - if len(new_password) < 8 or len(new_password) > 128: - return "Password must be between 8 and 128 characters." - - return None diff --git a/backend/core/views/teams/__init__.py b/backend/core/views/teams/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/views/teams/urls.py b/backend/core/views/teams/urls.py deleted file mode 100644 index 4de008fd8..000000000 --- a/backend/core/views/teams/urls.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.urls import path - -from backend.core.views.settings.teams import teams_dashboard_handler - -urlpatterns = [ - path( - "", - teams_dashboard_handler, - name="dashboard", - ), -] - -app_name = "teams" diff --git a/backend/core/webhooks/__init__.py b/backend/core/webhooks/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/webhooks/invoices/__init__.py b/backend/core/webhooks/invoices/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/core/api/emails/__init__.py b/backend/data/__init__.py similarity index 100% rename from backend/core/api/emails/__init__.py rename to backend/data/__init__.py diff --git a/backend/core/data/default_email_templates.py b/backend/data/default_email_templates.py similarity index 100% rename from backend/core/data/default_email_templates.py rename to backend/data/default_email_templates.py diff --git a/backend/decorators.py b/backend/decorators.py index 7cc91414c..ec65eb6a2 100644 --- a/backend/decorators.py +++ b/backend/decorators.py @@ -11,9 +11,9 @@ from django.shortcuts import render from django.urls import reverse -from backend.core.models import QuotaLimit, TeamMemberPermission -from backend.core.types.requests import WebRequest -from backend.core.utils.feature_flags import get_feature_status +from backend.models import TeamMemberPermission +from core.types.requests import WebRequest +from core.utils.feature_flags import get_feature_status logger = logging.getLogger(__name__) @@ -138,36 +138,36 @@ def wrapper(request, *args, **kwargs): return decorator -def quota_usage_check(limit: str | QuotaLimit, extra_data: str | int | None = None, api=False, htmx=False): - def decorator(view_func): - @wraps(view_func) - def wrapper(request, *args, **kwargs): - try: - quota_limit = QuotaLimit.objects.get(slug=limit) if isinstance(limit, str) else limit - except QuotaLimit.DoesNotExist: - return view_func(request, *args, **kwargs) - - if not quota_limit.strict_goes_above_limit(request.user, extra=extra_data): - return view_func(request, *args, **kwargs) - - if api and htmx: - messages.error(request, f"You have reached the quota limit for this service '{quota_limit.slug}'") - return render(request, "partials/messages_list.html", {"autohide": False}) - elif api: - return HttpResponse(status=403, content=f"You have reached the quota limit for this service '{quota_limit.slug}'") - messages.error(request, f"You have reached the quota limit for this service '{quota_limit.slug}'") - try: - last_visited_url = request.session["last_visited"] - current_url = request.build_absolute_uri() - if last_visited_url != current_url: - return HttpResponseRedirect(last_visited_url) - except KeyError: - pass - return HttpResponseRedirect(reverse("dashboard")) - - return wrapper - - return decorator +# def quota_usage_check(limit: str | QuotaLimit, extra_data: str | int | None = None, api=False, htmx=False): +# def decorator(view_func): +# @wraps(view_func) +# def wrapper(request, *args, **kwargs): +# try: +# quota_limit = QuotaLimit.objects.get(slug=limit) if isinstance(limit, str) else limit +# except QuotaLimit.DoesNotExist: +# return view_func(request, *args, **kwargs) +# +# if not quota_limit.strict_goes_above_limit(request.user, extra=extra_data): +# return view_func(request, *args, **kwargs) +# +# if api and htmx: +# messages.error(request, f"You have reached the quota limit for this service '{quota_limit.slug}'") +# return render(request, "partials/messages_list.html", {"autohide": False}) +# elif api: +# return HttpResponse(status=403, content=f"You have reached the quota limit for this service '{quota_limit.slug}'") +# messages.error(request, f"You have reached the quota limit for this service '{quota_limit.slug}'") +# try: +# last_visited_url = request.session["last_visited"] +# current_url = request.build_absolute_uri() +# if last_visited_url != current_url: +# return HttpResponseRedirect(last_visited_url) +# except KeyError: +# pass +# return HttpResponseRedirect(reverse("dashboard")) +# +# return wrapper +# +# return decorator not_logged_in = not_authenticated diff --git a/backend/finance/api/invoices/create/services/add_service.py b/backend/finance/api/invoices/create/services/add_service.py index 4d7244218..744c69cb9 100644 --- a/backend/finance/api/invoices/create/services/add_service.py +++ b/backend/finance/api/invoices/create/services/add_service.py @@ -1,8 +1,8 @@ from django.shortcuts import render from django.views.decorators.http import require_http_methods -from backend.core.service.invoices.common.create.services.add import add -from backend.core.types.requests import WebRequest +from backend.finance.service.invoices.common.create.services.add import add +from core.types.requests import WebRequest @require_http_methods(["POST"]) diff --git a/backend/finance/api/invoices/create/set_destination.py b/backend/finance/api/invoices/create/set_destination.py index 8902a1bb7..a028f7586 100644 --- a/backend/finance/api/invoices/create/set_destination.py +++ b/backend/finance/api/invoices/create/set_destination.py @@ -2,9 +2,9 @@ from django.shortcuts import render from django.views.decorators.http import require_http_methods -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest from backend.models import Client -from backend.core.types.htmx import HtmxHttpRequest +from core.types.htmx import HtmxHttpRequest to_get = ["name", "address", "city", "country", "company", "is_representative", "email"] diff --git a/backend/finance/api/invoices/delete.py b/backend/finance/api/invoices/delete.py index 16b85bafa..9f18fb340 100644 --- a/backend/finance/api/invoices/delete.py +++ b/backend/finance/api/invoices/delete.py @@ -6,8 +6,8 @@ from django.views.decorators.http import require_http_methods from backend.decorators import web_require_scopes -from backend.models import Invoice, QuotaLimit -from backend.core.types.htmx import HtmxHttpRequest +from backend.models import Invoice +from core.types.htmx import HtmxHttpRequest @require_http_methods(["DELETE"]) diff --git a/backend/finance/api/invoices/edit.py b/backend/finance/api/invoices/edit.py index 6357f3c3e..c99bc08d7 100644 --- a/backend/finance/api/invoices/edit.py +++ b/backend/finance/api/invoices/edit.py @@ -8,7 +8,7 @@ from backend.decorators import web_require_scopes from backend.finance.models import Invoice -from backend.core.types.htmx import HtmxHttpRequest +from core.types.htmx import HtmxHttpRequest @require_http_methods(["POST"]) diff --git a/backend/finance/api/invoices/fetch.py b/backend/finance/api/invoices/fetch.py index 2a945a0e4..ccbdb0bb7 100644 --- a/backend/finance/api/invoices/fetch.py +++ b/backend/finance/api/invoices/fetch.py @@ -3,8 +3,8 @@ from backend.decorators import web_require_scopes from backend.finance.models import Invoice -from backend.core.types.htmx import HtmxHttpRequest -from backend.core.service.invoices.common.fetch import get_context +from core.types.htmx import HtmxHttpRequest +from backend.finance.service.invoices.common.fetch import get_context @require_http_methods(["GET"]) diff --git a/backend/finance/api/invoices/manage.py b/backend/finance/api/invoices/manage.py index 78bba95a9..e917df75d 100644 --- a/backend/finance/api/invoices/manage.py +++ b/backend/finance/api/invoices/manage.py @@ -8,7 +8,7 @@ from backend.decorators import web_require_scopes from backend.finance.models import Invoice -from backend.core.types.htmx import HtmxHttpRequest +from core.types.htmx import HtmxHttpRequest class PreviewContext(TypedDict): diff --git a/backend/finance/api/invoices/recurring/delete.py b/backend/finance/api/invoices/recurring/delete.py index 5157c6e50..a5c857da8 100644 --- a/backend/finance/api/invoices/recurring/delete.py +++ b/backend/finance/api/invoices/recurring/delete.py @@ -7,9 +7,9 @@ from backend.decorators import web_require_scopes from backend.finance.models import InvoiceRecurringProfile -from backend.core.service.asyn_tasks.tasks import Task -from backend.core.service.boto3.scheduler.delete_schedule import delete_boto_schedule -from backend.core.types.requests import WebRequest +from backend.boto3.async_tasks.tasks import Task +from backend.boto3.scheduler.delete_schedule import delete_boto_schedule +from core.types.requests import WebRequest @require_http_methods(["DELETE"]) diff --git a/backend/finance/api/invoices/recurring/edit.py b/backend/finance/api/invoices/recurring/edit.py index 36c384a4d..228e0aeb2 100644 --- a/backend/finance/api/invoices/recurring/edit.py +++ b/backend/finance/api/invoices/recurring/edit.py @@ -7,9 +7,9 @@ from backend.decorators import web_require_scopes, has_entitlements from backend.finance.models import InvoiceRecurringProfile -from backend.core.service.invoices.recurring.get import get_invoice_profile -from backend.core.service.invoices.recurring.validate.frequencies import validate_and_update_frequency -from backend.core.types.requests import WebRequest +from backend.finance.service.invoices.recurring.get import get_invoice_profile +from backend.finance.service.invoices.recurring.validate.frequencies import validate_and_update_frequency +from core.types.requests import WebRequest @require_http_methods(["POST"]) diff --git a/backend/finance/api/invoices/recurring/fetch.py b/backend/finance/api/invoices/recurring/fetch.py index abee2dbf4..ec5cc5619 100644 --- a/backend/finance/api/invoices/recurring/fetch.py +++ b/backend/finance/api/invoices/recurring/fetch.py @@ -4,8 +4,8 @@ from backend.decorators import web_require_scopes from backend.finance.models import InvoiceRecurringProfile -from backend.core.service.invoices.common.fetch import get_context -from backend.core.types.requests import WebRequest +from backend.finance.service.invoices.common.fetch import get_context +from core.types.requests import WebRequest @require_http_methods(["GET"]) diff --git a/backend/finance/api/invoices/recurring/generate_next_invoice_now.py b/backend/finance/api/invoices/recurring/generate_next_invoice_now.py index a51f168d5..4c85ed88c 100644 --- a/backend/finance/api/invoices/recurring/generate_next_invoice_now.py +++ b/backend/finance/api/invoices/recurring/generate_next_invoice_now.py @@ -4,9 +4,9 @@ from backend.decorators import web_require_scopes, htmx_only from backend.finance.models import InvoiceRecurringProfile -from backend.core.service.defaults.get import get_account_defaults -from backend.core.service.invoices.recurring.generation.next_invoice import safe_generate_next_invoice_service -from backend.core.types.requests import WebRequest +from backend.finance.service.defaults.get import get_account_defaults +from backend.finance.service.invoices.recurring.generation.next_invoice import safe_generate_next_invoice_service +from core.types.requests import WebRequest import logging diff --git a/backend/finance/api/invoices/recurring/poll.py b/backend/finance/api/invoices/recurring/poll.py index 5f6b7b0be..00e3bcc56 100644 --- a/backend/finance/api/invoices/recurring/poll.py +++ b/backend/finance/api/invoices/recurring/poll.py @@ -7,11 +7,11 @@ from backend.decorators import web_require_scopes, htmx_only from backend.finance.models import InvoiceRecurringProfile -from backend.core.service.asyn_tasks.tasks import Task -from backend.core.service.boto3.scheduler.create_schedule import create_boto_schedule -from backend.core.service.boto3.scheduler.get import get_boto_schedule +from backend.boto3.async_tasks.tasks import Task +from backend.boto3.scheduler.create_schedule import create_boto_schedule +from backend.boto3.scheduler.get import get_boto_schedule -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest def return_create_schedule(recurring_schedule): diff --git a/backend/finance/api/invoices/recurring/update_status.py b/backend/finance/api/invoices/recurring/update_status.py index 49cf302b8..3c41c7da7 100644 --- a/backend/finance/api/invoices/recurring/update_status.py +++ b/backend/finance/api/invoices/recurring/update_status.py @@ -1,4 +1,3 @@ -from typing import Literal from django.conf import settings from django.contrib import messages from django.http import HttpRequest, HttpResponse @@ -6,15 +5,16 @@ from django.views.decorators.http import require_POST from backend.decorators import web_require_scopes -from backend.finance.models import InvoiceRecurringProfile -from backend.core.service.asyn_tasks.tasks import Task -from backend.core.service.boto3.scheduler.create_schedule import create_boto_schedule -from backend.core.service.boto3.scheduler.get import get_boto_schedule -from backend.core.service.boto3.scheduler.pause import pause_boto_schedule -from backend.core.types.requests import WebRequest +from backend.boto3.async_tasks.tasks import Task +from backend.boto3.scheduler.create_schedule import create_boto_schedule +from backend.boto3.scheduler.get import get_boto_schedule +from backend.boto3.scheduler.pause import pause_boto_schedule +from core.types.requests import WebRequest from datetime import timedelta, datetime +from backend.models import InvoiceRecurringProfile + @require_POST @web_require_scopes("invoices:write", True, True) diff --git a/backend/finance/api/invoices/reminders/fetch.py b/backend/finance/api/invoices/reminders/fetch.py index d4afe81db..49047d630 100644 --- a/backend/finance/api/invoices/reminders/fetch.py +++ b/backend/finance/api/invoices/reminders/fetch.py @@ -6,7 +6,7 @@ from backend.decorators import feature_flag_check, web_require_scopes from backend.finance.models import Invoice -from backend.core.types.htmx import HtmxHttpRequest +from core.types.htmx import HtmxHttpRequest @require_GET diff --git a/backend/finance/api/products/create.py b/backend/finance/api/products/create.py index a1ba8b7d8..f8ee5321d 100644 --- a/backend/finance/api/products/create.py +++ b/backend/finance/api/products/create.py @@ -3,7 +3,7 @@ from backend.finance.api.products.fetch import fetch_products from backend.decorators import web_require_scopes from backend.finance.models import InvoiceProduct -from backend.core.types.htmx import HtmxHttpRequest +from core.types.htmx import HtmxHttpRequest @web_require_scopes("invoices:write", True, True) diff --git a/backend/finance/api/products/fetch.py b/backend/finance/api/products/fetch.py index 5de0d1c5f..4586d8d49 100644 --- a/backend/finance/api/products/fetch.py +++ b/backend/finance/api/products/fetch.py @@ -3,7 +3,7 @@ from backend.decorators import web_require_scopes from backend.finance.models import InvoiceProduct -from backend.core.types.htmx import HtmxHttpRequest +from core.types.htmx import HtmxHttpRequest @web_require_scopes("invoices:read", True, True) diff --git a/backend/finance/api/receipts/delete.py b/backend/finance/api/receipts/delete.py index 748ddece1..d60bc963b 100644 --- a/backend/finance/api/receipts/delete.py +++ b/backend/finance/api/receipts/delete.py @@ -6,7 +6,7 @@ from backend.decorators import web_require_scopes from backend.models import Receipt -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest @require_http_methods(["DELETE"]) diff --git a/backend/finance/api/receipts/fetch.py b/backend/finance/api/receipts/fetch.py index 84f56a053..ca5e7d370 100644 --- a/backend/finance/api/receipts/fetch.py +++ b/backend/finance/api/receipts/fetch.py @@ -3,7 +3,7 @@ from backend.decorators import web_require_scopes from backend.models import Receipt -from backend.core.types.htmx import HtmxHttpRequest +from core.types.htmx import HtmxHttpRequest @web_require_scopes("receipts:read", True, True) diff --git a/backend/finance/api/receipts/new.py b/backend/finance/api/receipts/new.py index b7b1e20e9..aa9076a7f 100644 --- a/backend/finance/api/receipts/new.py +++ b/backend/finance/api/receipts/new.py @@ -5,8 +5,8 @@ from django.views.decorators.http import require_http_methods from backend.decorators import web_require_scopes, has_entitlements -from backend.models import Receipt, QuotaUsage -from backend.core.types.requests import WebRequest +from backend.models import Receipt +from core.types.requests import WebRequest @require_http_methods(["POST"]) @@ -50,7 +50,7 @@ def receipt_create(request: WebRequest): receipts = Receipt.filter_by_owner(owner=request.actor).order_by("-date") receipt = Receipt(**receipt_data) - QuotaUsage.create_str(request.user, "receipts-count", receipt.id) + # QuotaUsage.create_str(request.user, "receipts-count", receipt.id) receipt.save() # r = requests.post( # "https://ocr.asprise.com/api/receipt", diff --git a/backend/finance/api/reports/fetch.py b/backend/finance/api/reports/fetch.py index ab2528829..024b554d0 100644 --- a/backend/finance/api/reports/fetch.py +++ b/backend/finance/api/reports/fetch.py @@ -2,7 +2,7 @@ from django.shortcuts import render from backend.models import MonthlyReport -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest def fetch_reports_endpoint(request: WebRequest): diff --git a/backend/finance/api/reports/generate.py b/backend/finance/api/reports/generate.py index 4968ef8d1..c87e7c2c7 100644 --- a/backend/finance/api/reports/generate.py +++ b/backend/finance/api/reports/generate.py @@ -2,8 +2,8 @@ from django.shortcuts import render from backend.decorators import web_require_scopes -from backend.core.service.reports.generate import generate_report -from backend.core.types.requests import WebRequest +from backend.finance.service.reports.generate import generate_report +from core.types.requests import WebRequest @web_require_scopes("invoices:write", True, True) diff --git a/backend/finance/models.py b/backend/finance/models.py index 67da42c99..464010443 100644 --- a/backend/finance/models.py +++ b/backend/finance/models.py @@ -11,7 +11,7 @@ from backend.clients.models import Client, DefaultValues from backend.managers import InvoiceRecurringProfile_WithItemsManager -from backend.core.models import OwnerBase, UserSettings, _private_storage, USER_OR_ORGANIZATION_CONSTRAINT, User, ExpiresBase, Organization +from backend.models import OwnerBase, UserSettings, _private_storage, USER_OR_ORGANIZATION_CONSTRAINT, User, ExpiresBase, Organization class BotoSchedule(models.Model): diff --git a/backend/core/api/healthcheck/__init__.py b/backend/finance/service/__init__.py similarity index 100% rename from backend/core/api/healthcheck/__init__.py rename to backend/finance/service/__init__.py diff --git a/backend/core/api/landing_page/__init__.py b/backend/finance/service/clients/__init__.py similarity index 100% rename from backend/core/api/landing_page/__init__.py rename to backend/finance/service/clients/__init__.py diff --git a/backend/core/service/clients/create.py b/backend/finance/service/clients/create.py similarity index 90% rename from backend/core/service/clients/create.py rename to backend/finance/service/clients/create.py index 788e65cec..341aeb180 100644 --- a/backend/core/service/clients/create.py +++ b/backend/finance/service/clients/create.py @@ -1,6 +1,6 @@ from backend.clients.models import Client -from backend.core.service.clients.validate import validate_client_create -from backend.core.utils.dataclasses import BaseServiceResponse +from backend.finance.service.clients.validate import validate_client_create +from core.utils.dataclasses import BaseServiceResponse class CreateClientServiceResponse(BaseServiceResponse[Client]): ... diff --git a/backend/core/service/clients/delete.py b/backend/finance/service/clients/delete.py similarity index 88% rename from backend/core/service/clients/delete.py rename to backend/finance/service/clients/delete.py index 01265b0a4..b60727bcc 100644 --- a/backend/core/service/clients/delete.py +++ b/backend/finance/service/clients/delete.py @@ -1,8 +1,8 @@ -from backend.core.service.clients.validate import validate_client +from backend.finance.service.clients.validate import validate_client from django.core.exceptions import ValidationError, PermissionDenied from backend.models import Client, AuditLog -from backend.core.utils.dataclasses import BaseServiceResponse +from core.utils.dataclasses import BaseServiceResponse class DeleteClientServiceResponse(BaseServiceResponse[None]): diff --git a/backend/core/service/clients/get.py b/backend/finance/service/clients/get.py similarity index 91% rename from backend/core/service/clients/get.py rename to backend/finance/service/clients/get.py index 8d87f2ec3..3db1115ac 100644 --- a/backend/core/service/clients/get.py +++ b/backend/finance/service/clients/get.py @@ -1,7 +1,7 @@ from django.db.models import Q, QuerySet from backend.models import Client, Organization -from backend.core.utils.dataclasses import BaseServiceResponse +from core.utils.dataclasses import BaseServiceResponse class FetchClientServiceResponse(BaseServiceResponse[QuerySet[Client]]): ... diff --git a/backend/core/service/clients/validate.py b/backend/finance/service/clients/validate.py similarity index 100% rename from backend/core/service/clients/validate.py rename to backend/finance/service/clients/validate.py diff --git a/backend/core/api/maintenance/__init__.py b/backend/finance/service/defaults/__init__.py similarity index 100% rename from backend/core/api/maintenance/__init__.py rename to backend/finance/service/defaults/__init__.py diff --git a/backend/core/service/defaults/get.py b/backend/finance/service/defaults/get.py similarity index 100% rename from backend/core/service/defaults/get.py rename to backend/finance/service/defaults/get.py diff --git a/backend/core/service/defaults/update.py b/backend/finance/service/defaults/update.py similarity index 97% rename from backend/core/service/defaults/update.py rename to backend/finance/service/defaults/update.py index 55072044f..56cc304ae 100644 --- a/backend/core/service/defaults/update.py +++ b/backend/finance/service/defaults/update.py @@ -1,8 +1,8 @@ from PIL import Image from backend.models import DefaultValues -from backend.core.types.requests import WebRequest -from backend.core.utils.dataclasses import BaseServiceResponse +from core.types.requests import WebRequest +from core.utils.dataclasses import BaseServiceResponse class ClientDefaultsServiceResponse(BaseServiceResponse[DefaultValues]): ... diff --git a/backend/core/api/public/endpoints/Invoices/__init__.py b/backend/finance/service/invoices/__init__.py similarity index 100% rename from backend/core/api/public/endpoints/Invoices/__init__.py rename to backend/finance/service/invoices/__init__.py diff --git a/backend/core/api/public/endpoints/__init__.py b/backend/finance/service/invoices/common/__init__.py similarity index 100% rename from backend/core/api/public/endpoints/__init__.py rename to backend/finance/service/invoices/common/__init__.py diff --git a/backend/core/api/public/endpoints/clients/__init__.py b/backend/finance/service/invoices/common/create/__init__.py similarity index 100% rename from backend/core/api/public/endpoints/clients/__init__.py rename to backend/finance/service/invoices/common/create/__init__.py diff --git a/backend/core/service/invoices/common/create/create.py b/backend/finance/service/invoices/common/create/create.py similarity index 92% rename from backend/core/service/invoices/common/create/create.py rename to backend/finance/service/invoices/common/create/create.py index 43588dd76..546655604 100644 --- a/backend/core/service/invoices/common/create/create.py +++ b/backend/finance/service/invoices/common/create/create.py @@ -1,8 +1,8 @@ from django.contrib import messages -from backend.models import Invoice, InvoiceRecurringProfile, InvoiceItem, Client, QuotaUsage, DefaultValues -from backend.core.service.defaults.get import get_account_defaults -from backend.core.types.requests import WebRequest +from backend.models import Invoice, InvoiceRecurringProfile, InvoiceItem, Client, DefaultValues +from backend.finance.service.defaults.get import get_account_defaults +from core.types.requests import WebRequest def create_invoice_items(request: WebRequest): @@ -65,6 +65,6 @@ def save_invoice_common(request: WebRequest, invoice_items, invoice: Invoice | I invoice.save() invoice.items.set(invoice_items) - QuotaUsage.create_str(request.user, "invoices-count", invoice.id) + # QuotaUsage.create_str(request.user, "invoices-count", invoice.id) return invoice diff --git a/backend/core/service/invoices/common/create/get_page.py b/backend/finance/service/invoices/common/create/get_page.py similarity index 92% rename from backend/core/service/invoices/common/create/get_page.py rename to backend/finance/service/invoices/common/create/get_page.py index a3d5cf63e..b2d60b475 100644 --- a/backend/core/service/invoices/common/create/get_page.py +++ b/backend/finance/service/invoices/common/create/get_page.py @@ -3,10 +3,10 @@ from django.core.exceptions import PermissionDenied, ValidationError from backend.models import Client, InvoiceProduct, DefaultValues -from backend.core.service.clients.validate import validate_client -from backend.core.service.defaults.get import get_account_defaults -from backend.core.types.requests import WebRequest -from backend.core.utils.dataclasses import BaseServiceResponse +from backend.finance.service.clients.validate import validate_client +from backend.finance.service.defaults.get import get_account_defaults +from core.types.requests import WebRequest +from core.utils.dataclasses import BaseServiceResponse class CreateInvoiceContextTuple(NamedTuple): diff --git a/backend/core/api/public/endpoints/webhooks/__init__.py b/backend/finance/service/invoices/common/create/services/__init__.py similarity index 100% rename from backend/core/api/public/endpoints/webhooks/__init__.py rename to backend/finance/service/invoices/common/create/services/__init__.py diff --git a/backend/core/service/invoices/common/create/services/add.py b/backend/finance/service/invoices/common/create/services/add.py similarity index 94% rename from backend/core/service/invoices/common/create/services/add.py rename to backend/finance/service/invoices/common/create/services/add.py index 11f56502b..42f0fb36b 100644 --- a/backend/core/service/invoices/common/create/services/add.py +++ b/backend/finance/service/invoices/common/create/services/add.py @@ -1,9 +1,9 @@ from django.http import JsonResponse -from backend.core.api.public.types import APIRequest -from backend.core.types.requests import WebRequest +from core.api.public.types import APIRequest +from core.types.requests import WebRequest from backend.finance.models import InvoiceProduct -from backend.core.types.htmx import HtmxHttpRequest +from core.types.htmx import HtmxHttpRequest def add(request: APIRequest | WebRequest): diff --git a/backend/core/api/public/helpers/__init__.py b/backend/finance/service/invoices/common/emails/__init__.py similarity index 100% rename from backend/core/api/public/helpers/__init__.py rename to backend/finance/service/invoices/common/emails/__init__.py diff --git a/backend/core/service/invoices/common/emails/on_create.py b/backend/finance/service/invoices/common/emails/on_create.py similarity index 87% rename from backend/core/service/invoices/common/emails/on_create.py rename to backend/finance/service/invoices/common/emails/on_create.py index f2a53a3ae..189071c4b 100644 --- a/backend/core/service/invoices/common/emails/on_create.py +++ b/backend/finance/service/invoices/common/emails/on_create.py @@ -2,12 +2,12 @@ from django.urls import reverse -from backend.core.data.default_email_templates import email_footer +from backend.data.default_email_templates import email_footer from backend.models import Invoice, EmailSendStatus, InvoiceURL -from backend.core.service.defaults.get import get_account_defaults -from backend.core.service.invoices.single.create_url import create_invoice_url -from backend.core.utils.dataclasses import BaseServiceResponse -from backend.core.utils.service_retry import retry_handler +from backend.finance.service.defaults.get import get_account_defaults +from backend.finance.service.invoices.single.create_url import create_invoice_url +from core.utils.dataclasses import BaseServiceResponse +from core.utils.service_retry import retry_handler from settings.helpers import send_email, get_var """ diff --git a/backend/core/service/invoices/common/fetch.py b/backend/finance/service/invoices/common/fetch.py similarity index 100% rename from backend/core/service/invoices/common/fetch.py rename to backend/finance/service/invoices/common/fetch.py diff --git a/backend/core/service/invoices/handler.py b/backend/finance/service/invoices/handler.py similarity index 100% rename from backend/core/service/invoices/handler.py rename to backend/finance/service/invoices/handler.py diff --git a/backend/core/api/public/serializers/__init__.py b/backend/finance/service/invoices/recurring/__init__.py similarity index 100% rename from backend/core/api/public/serializers/__init__.py rename to backend/finance/service/invoices/recurring/__init__.py diff --git a/backend/core/api/quotas/__init__.py b/backend/finance/service/invoices/recurring/create/__init__.py similarity index 100% rename from backend/core/api/quotas/__init__.py rename to backend/finance/service/invoices/recurring/create/__init__.py diff --git a/backend/core/service/invoices/recurring/create/get_page.py b/backend/finance/service/invoices/recurring/create/get_page.py similarity index 87% rename from backend/core/service/invoices/recurring/create/get_page.py rename to backend/finance/service/invoices/recurring/create/get_page.py index 32d48e872..053a1803b 100644 --- a/backend/core/service/invoices/recurring/create/get_page.py +++ b/backend/finance/service/invoices/recurring/create/get_page.py @@ -1,7 +1,7 @@ from datetime import date -from backend.core.service.invoices.common.create.get_page import global_get_invoice_context -from backend.core.types.requests import WebRequest +from backend.finance.service.invoices.common.create.get_page import global_get_invoice_context +from core.types.requests import WebRequest def get_invoice_context(request: WebRequest) -> dict: diff --git a/backend/core/service/invoices/recurring/create/save.py b/backend/finance/service/invoices/recurring/create/save.py similarity index 85% rename from backend/core/service/invoices/recurring/create/save.py rename to backend/finance/service/invoices/recurring/create/save.py index f9c2fac60..aae318609 100644 --- a/backend/core/service/invoices/recurring/create/save.py +++ b/backend/finance/service/invoices/recurring/create/save.py @@ -3,11 +3,11 @@ from django.contrib import messages from django.core.exceptions import ValidationError -from backend.models import InvoiceRecurringProfile, QuotaUsage -from backend.core.service.invoices.common.create.create import save_invoice_common -from backend.core.service.invoices.recurring.validate.frequencies import validate_and_update_frequency -from backend.core.types.requests import WebRequest -from backend.core.utils.dataclasses import BaseServiceResponse +from backend.models import InvoiceRecurringProfile +from backend.finance.service.invoices.common.create.create import save_invoice_common +from backend.finance.service.invoices.recurring.validate.frequencies import validate_and_update_frequency +from core.types.requests import WebRequest +from core.utils.dataclasses import BaseServiceResponse class SaveInvoiceServiceResponse(BaseServiceResponse[InvoiceRecurringProfile]): ... @@ -69,6 +69,6 @@ def save_invoice(request: WebRequest, invoice_items) -> SaveInvoiceServiceRespon invoice_profile.save() invoice_profile.items.set(invoice_items) - QuotaUsage.create_str(request.user, "invoices-count", invoice_profile.id) + # QuotaUsage.create_str(request.user, "invoices-count", invoice_profile.id) return SaveInvoiceServiceResponse(True, invoice_profile) diff --git a/backend/core/api/settings/__init__.py b/backend/finance/service/invoices/recurring/generation/__init__.py similarity index 100% rename from backend/core/api/settings/__init__.py rename to backend/finance/service/invoices/recurring/generation/__init__.py diff --git a/backend/core/service/invoices/recurring/generation/next_invoice.py b/backend/finance/service/invoices/recurring/generation/next_invoice.py similarity index 96% rename from backend/core/service/invoices/recurring/generation/next_invoice.py rename to backend/finance/service/invoices/recurring/generation/next_invoice.py index 0c041af41..de94c7115 100644 --- a/backend/core/service/invoices/recurring/generation/next_invoice.py +++ b/backend/finance/service/invoices/recurring/generation/next_invoice.py @@ -3,9 +3,9 @@ from django.db import transaction, IntegrityError from backend.models import Invoice, InvoiceRecurringProfile, DefaultValues, AuditLog -from backend.core.service.defaults.get import get_account_defaults -from backend.core.service.invoices.common.emails.on_create import on_create_invoice_email_service -from backend.core.utils.dataclasses import BaseServiceResponse +from backend.finance.service.defaults.get import get_account_defaults +from backend.finance.service.invoices.common.emails.on_create import on_create_invoice_email_service +from core.utils.dataclasses import BaseServiceResponse import logging diff --git a/backend/core/service/invoices/recurring/get.py b/backend/finance/service/invoices/recurring/get.py similarity index 88% rename from backend/core/service/invoices/recurring/get.py rename to backend/finance/service/invoices/recurring/get.py index a2cfde003..1ec817404 100644 --- a/backend/core/service/invoices/recurring/get.py +++ b/backend/finance/service/invoices/recurring/get.py @@ -1,6 +1,6 @@ from backend.finance.models import InvoiceRecurringProfile -from backend.core.types.requests import WebRequest -from backend.core.utils.dataclasses import BaseServiceResponse +from core.types.requests import WebRequest +from core.utils.dataclasses import BaseServiceResponse class GetRecurringSetServiceResponse(BaseServiceResponse[InvoiceRecurringProfile]): ... diff --git a/backend/core/api/teams/__init__.py b/backend/finance/service/invoices/recurring/schedules/__init__.py similarity index 100% rename from backend/core/api/teams/__init__.py rename to backend/finance/service/invoices/recurring/schedules/__init__.py diff --git a/backend/core/service/invoices/recurring/schedules/date_handlers.py b/backend/finance/service/invoices/recurring/schedules/date_handlers.py similarity index 98% rename from backend/core/service/invoices/recurring/schedules/date_handlers.py rename to backend/finance/service/invoices/recurring/schedules/date_handlers.py index bab910cf0..223a1e218 100644 --- a/backend/core/service/invoices/recurring/schedules/date_handlers.py +++ b/backend/finance/service/invoices/recurring/schedules/date_handlers.py @@ -1,4 +1,4 @@ -from backend.core.utils.dataclasses import BaseServiceResponse +from core.utils.dataclasses import BaseServiceResponse from datetime import date as Date diff --git a/backend/core/data/__init__.py b/backend/finance/service/invoices/recurring/validate/__init__.py similarity index 100% rename from backend/core/data/__init__.py rename to backend/finance/service/invoices/recurring/validate/__init__.py diff --git a/backend/core/service/invoices/recurring/validate/frequencies.py b/backend/finance/service/invoices/recurring/validate/frequencies.py similarity index 97% rename from backend/core/service/invoices/recurring/validate/frequencies.py rename to backend/finance/service/invoices/recurring/validate/frequencies.py index 515330f25..21416f8d9 100644 --- a/backend/core/service/invoices/recurring/validate/frequencies.py +++ b/backend/finance/service/invoices/recurring/validate/frequencies.py @@ -1,5 +1,5 @@ from backend.finance.models import InvoiceRecurringProfile -from backend.core.utils.dataclasses import BaseServiceResponse +from core.utils.dataclasses import BaseServiceResponse class ValidateFrequencyServiceResponse(BaseServiceResponse[None]): diff --git a/backend/core/management/__init__.py b/backend/finance/service/invoices/recurring/webhooks/__init__.py similarity index 100% rename from backend/core/management/__init__.py rename to backend/finance/service/invoices/recurring/webhooks/__init__.py diff --git a/backend/core/service/invoices/recurring/webhooks/webhook_apikey_auth.py b/backend/finance/service/invoices/recurring/webhooks/webhook_apikey_auth.py similarity index 86% rename from backend/core/service/invoices/recurring/webhooks/webhook_apikey_auth.py rename to backend/finance/service/invoices/recurring/webhooks/webhook_apikey_auth.py index 360a47efa..782174667 100644 --- a/backend/core/service/invoices/recurring/webhooks/webhook_apikey_auth.py +++ b/backend/finance/service/invoices/recurring/webhooks/webhook_apikey_auth.py @@ -1,6 +1,6 @@ -from backend.core.api.public import APIAuthToken -from backend.core.types.requests import WebRequest -from backend.core.utils.dataclasses import BaseServiceResponse +from core.api.public import APIAuthToken +from core.types.requests import WebRequest +from core.utils.dataclasses import BaseServiceResponse class APIAuthenticationServiceResponse(BaseServiceResponse[None]): diff --git a/backend/core/management/commands/__init__.py b/backend/finance/service/invoices/single/__init__.py similarity index 100% rename from backend/core/management/commands/__init__.py rename to backend/finance/service/invoices/single/__init__.py diff --git a/backend/core/management/scheduled_tasks/__init__.py b/backend/finance/service/invoices/single/create/__init__.py similarity index 100% rename from backend/core/management/scheduled_tasks/__init__.py rename to backend/finance/service/invoices/single/create/__init__.py diff --git a/backend/core/service/invoices/single/create/create.py b/backend/finance/service/invoices/single/create/create.py similarity index 89% rename from backend/core/service/invoices/single/create/create.py rename to backend/finance/service/invoices/single/create/create.py index 1eaca8105..fd6e178a8 100644 --- a/backend/core/service/invoices/single/create/create.py +++ b/backend/finance/service/invoices/single/create/create.py @@ -4,11 +4,12 @@ from django.core.exceptions import PermissionDenied, ValidationError from backend.finance.models import Invoice, InvoiceItem, Client, InvoiceProduct, DefaultValues -from backend.models import QuotaUsage -from backend.core.service.clients.validate import validate_client -from backend.core.service.defaults.get import get_account_defaults -from backend.core.service.invoices.common.create.create import save_invoice_common -from backend.core.types.requests import WebRequest + +# from backend.models import QuotaUsage +from backend.finance.service.clients.validate import validate_client +from backend.finance.service.defaults.get import get_account_defaults +from backend.finance.service.invoices.common.create.create import save_invoice_common +from core.types.requests import WebRequest def get_invoice_context(request: WebRequest) -> dict: @@ -99,6 +100,6 @@ def save_invoice(request: WebRequest, invoice_items): invoice.save() invoice.items.set(invoice_items) - QuotaUsage.create_str(request.user, "invoices-count", invoice.id) + # QuotaUsage.create_str(request.user, "invoices-count", invoice.id) return invoice diff --git a/backend/core/service/invoices/single/create/get_page.py b/backend/finance/service/invoices/single/create/get_page.py similarity index 81% rename from backend/core/service/invoices/single/create/get_page.py rename to backend/finance/service/invoices/single/create/get_page.py index 322551849..491bf3e34 100644 --- a/backend/core/service/invoices/single/create/get_page.py +++ b/backend/finance/service/invoices/single/create/get_page.py @@ -1,7 +1,7 @@ from datetime import date -from backend.core.service.invoices.common.create.get_page import global_get_invoice_context -from backend.core.types.requests import WebRequest +from backend.finance.service.invoices.common.create.get_page import global_get_invoice_context +from core.types.requests import WebRequest def get_invoice_context(request: WebRequest) -> dict: diff --git a/backend/core/service/invoices/single/create_pdf.py b/backend/finance/service/invoices/single/create_pdf.py similarity index 100% rename from backend/core/service/invoices/single/create_pdf.py rename to backend/finance/service/invoices/single/create_pdf.py diff --git a/backend/core/service/invoices/single/create_url.py b/backend/finance/service/invoices/single/create_url.py similarity index 80% rename from backend/core/service/invoices/single/create_url.py rename to backend/finance/service/invoices/single/create_url.py index 92250713d..5fe60c5c2 100644 --- a/backend/core/service/invoices/single/create_url.py +++ b/backend/finance/service/invoices/single/create_url.py @@ -1,6 +1,6 @@ from backend.finance.models import InvoiceURL, Invoice -from backend.core.models import User -from backend.core.utils.dataclasses import BaseServiceResponse +from backend.models import User +from core.utils.dataclasses import BaseServiceResponse class CreateInvoiceURLServiceResponse(BaseServiceResponse[InvoiceURL]): ... diff --git a/backend/core/service/invoices/single/get_invoice.py b/backend/finance/service/invoices/single/get_invoice.py similarity index 91% rename from backend/core/service/invoices/single/get_invoice.py rename to backend/finance/service/invoices/single/get_invoice.py index 7789b93a2..b88aaad9a 100644 --- a/backend/core/service/invoices/single/get_invoice.py +++ b/backend/finance/service/invoices/single/get_invoice.py @@ -1,5 +1,5 @@ from backend.finance.models import Invoice, Organization, User -from backend.core.utils.dataclasses import BaseServiceResponse +from core.utils.dataclasses import BaseServiceResponse class GetInvoiceServiceResponse(BaseServiceResponse[Invoice]): ... diff --git a/backend/core/service/api_keys/__init__.py b/backend/finance/service/reports/__init__.py similarity index 100% rename from backend/core/service/api_keys/__init__.py rename to backend/finance/service/reports/__init__.py diff --git a/backend/core/service/reports/generate.py b/backend/finance/service/reports/generate.py similarity index 96% rename from backend/core/service/reports/generate.py rename to backend/finance/service/reports/generate.py index f58ef6e8e..bfd43c974 100644 --- a/backend/core/service/reports/generate.py +++ b/backend/finance/service/reports/generate.py @@ -4,7 +4,7 @@ from django.db import transaction from backend.models import User, Organization, Invoice, MonthlyReport, MonthlyReportRow -from backend.core.utils.dataclasses import BaseServiceResponse +from core.utils.dataclasses import BaseServiceResponse class GenerateReportServiceResponse(BaseServiceResponse[MonthlyReport]): ... diff --git a/backend/core/service/reports/get.py b/backend/finance/service/reports/get.py similarity index 87% rename from backend/core/service/reports/get.py rename to backend/finance/service/reports/get.py index a9f139f99..5cef36d4b 100644 --- a/backend/core/service/reports/get.py +++ b/backend/finance/service/reports/get.py @@ -1,5 +1,5 @@ from backend.models import MonthlyReport, User, Organization -from backend.core.utils.dataclasses import BaseServiceResponse +from core.utils.dataclasses import BaseServiceResponse class GetReportServiceResponse(BaseServiceResponse[MonthlyReport]): ... diff --git a/backend/finance/signals/schedules.py b/backend/finance/signals/schedules.py index 5a101fb7b..50847c6ac 100644 --- a/backend/finance/signals/schedules.py +++ b/backend/finance/signals/schedules.py @@ -3,8 +3,8 @@ from django.dispatch import receiver from django.db.models.signals import post_save -from backend.core.service.boto3.scheduler.create_schedule import create_boto_schedule -from backend.core.service.boto3.scheduler.update_schedule import update_boto_schedule +from backend.boto3.scheduler.create_schedule import create_boto_schedule +from backend.boto3.scheduler.update_schedule import update_boto_schedule from backend.finance.models import InvoiceRecurringProfile diff --git a/backend/finance/views/invoices/handler.py b/backend/finance/views/invoices/handler.py index 73cd09c27..afb983a50 100644 --- a/backend/finance/views/invoices/handler.py +++ b/backend/finance/views/invoices/handler.py @@ -3,7 +3,7 @@ from django.http import HttpResponse from django.shortcuts import render -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest def invoices_core_handler(request: WebRequest, template_name: str, start_context: Dict[str, Any] | None = None, **kwargs) -> HttpResponse: diff --git a/backend/finance/views/invoices/recurring/create.py b/backend/finance/views/invoices/recurring/create.py index abc4f086a..c0e215362 100644 --- a/backend/finance/views/invoices/recurring/create.py +++ b/backend/finance/views/invoices/recurring/create.py @@ -4,13 +4,13 @@ from backend.finance.models import InvoiceRecurringProfile from backend.decorators import web_require_scopes -from backend.core.service import BOTO3_HANDLER -from backend.core.service.asyn_tasks.tasks import Task -from backend.core.service.boto3.scheduler.create_schedule import create_boto_schedule -from backend.core.service.invoices.common.create.create import create_invoice_items -from backend.core.service.invoices.recurring.create.get_page import get_invoice_context -from backend.core.service.invoices.recurring.create.save import save_invoice -from backend.core.types.requests import WebRequest +from backend.boto3.handler import BOTO3_HANDLER +from backend.boto3.async_tasks.tasks import Task +from backend.boto3.scheduler.create_schedule import create_boto_schedule +from backend.finance.service.invoices.common.create.create import create_invoice_items +from backend.finance.service.invoices.recurring.create.get_page import get_invoice_context +from backend.finance.service.invoices.recurring.create.save import save_invoice +from core.types.requests import WebRequest from backend.finance.views.invoices.handler import invoices_core_handler diff --git a/backend/finance/views/invoices/recurring/dashboard.py b/backend/finance/views/invoices/recurring/dashboard.py index 8ff75ff2b..04bf31d2f 100644 --- a/backend/finance/views/invoices/recurring/dashboard.py +++ b/backend/finance/views/invoices/recurring/dashboard.py @@ -1,7 +1,7 @@ from django.views.decorators.http import require_http_methods from backend.decorators import web_require_scopes, has_entitlements -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest from backend.finance.views.invoices.handler import invoices_core_handler diff --git a/backend/finance/views/invoices/recurring/edit.py b/backend/finance/views/invoices/recurring/edit.py index a3493c534..026fcb8e8 100644 --- a/backend/finance/views/invoices/recurring/edit.py +++ b/backend/finance/views/invoices/recurring/edit.py @@ -4,7 +4,7 @@ from backend.decorators import web_require_scopes, has_entitlements from backend.finance.models import InvoiceRecurringProfile -from backend.core.service.invoices.recurring.get import get_invoice_profile +from backend.finance.service.invoices.recurring.get import get_invoice_profile from backend.finance.views.invoices.handler import invoices_core_handler diff --git a/backend/finance/views/invoices/recurring/overview.py b/backend/finance/views/invoices/recurring/overview.py index 8b2bb9010..ec99f9ea0 100644 --- a/backend/finance/views/invoices/recurring/overview.py +++ b/backend/finance/views/invoices/recurring/overview.py @@ -1,6 +1,6 @@ from backend.decorators import * from backend.models import * -from backend.core.service.defaults.get import get_account_defaults +from backend.finance.service.defaults.get import get_account_defaults from backend.finance.views.invoices.handler import invoices_core_handler diff --git a/backend/finance/views/invoices/single/create.py b/backend/finance/views/invoices/single/create.py index b77db574d..631bf675d 100644 --- a/backend/finance/views/invoices/single/create.py +++ b/backend/finance/views/invoices/single/create.py @@ -2,9 +2,9 @@ from django.views.decorators.http import require_http_methods from backend.decorators import web_require_scopes, has_entitlements -from backend.core.service.invoices.single.create.create import create_invoice_items, save_invoice -from backend.core.service.invoices.single.create.get_page import get_invoice_context -from backend.core.types.requests import WebRequest +from backend.finance.service.invoices.single.create.create import create_invoice_items, save_invoice +from backend.finance.service.invoices.single.create.get_page import get_invoice_context +from core.types.requests import WebRequest from backend.finance.views.invoices.handler import invoices_core_handler diff --git a/backend/finance/views/invoices/single/dashboard.py b/backend/finance/views/invoices/single/dashboard.py index 96c9f51f6..27a08887d 100644 --- a/backend/finance/views/invoices/single/dashboard.py +++ b/backend/finance/views/invoices/single/dashboard.py @@ -4,7 +4,7 @@ from backend.decorators import web_require_scopes from backend.finance.models import Invoice -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest from backend.finance.views.invoices.handler import invoices_core_handler diff --git a/backend/finance/views/invoices/single/edit.py b/backend/finance/views/invoices/single/edit.py index e6d509857..f6d702d88 100644 --- a/backend/finance/views/invoices/single/edit.py +++ b/backend/finance/views/invoices/single/edit.py @@ -5,10 +5,10 @@ from django.shortcuts import render, redirect from django.views.decorators.http import require_http_methods -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest from backend.decorators import web_require_scopes from backend.finance.models import Invoice, Client, InvoiceItem -from backend.core.types.htmx import HtmxHttpRequest +from core.types.htmx import HtmxHttpRequest # RELATED PATH FILES : \frontend\templates\pages\invoices\dashboard\_fetch_body.html, \backend\urls.py diff --git a/backend/finance/views/invoices/single/manage_access.py b/backend/finance/views/invoices/single/manage_access.py index e5f3ea413..a6e40af77 100644 --- a/backend/finance/views/invoices/single/manage_access.py +++ b/backend/finance/views/invoices/single/manage_access.py @@ -4,9 +4,9 @@ from backend.decorators import web_require_scopes from backend.finance.models import Invoice, InvoiceURL -from backend.core.service.invoices.single.get_invoice import get_invoice_by_actor -from backend.core.types.htmx import HtmxHttpRequest -from backend.core.types.requests import WebRequest +from backend.finance.service.invoices.single.get_invoice import get_invoice_by_actor +from core.types.htmx import HtmxHttpRequest +from core.types.requests import WebRequest @web_require_scopes("invoices:write", False, False, "finance:invoices:single:dashboard") diff --git a/backend/finance/views/invoices/single/view.py b/backend/finance/views/invoices/single/view.py index f50daa1e3..db49d9a68 100644 --- a/backend/finance/views/invoices/single/view.py +++ b/backend/finance/views/invoices/single/view.py @@ -9,7 +9,7 @@ from backend.decorators import web_require_scopes from backend.finance.models import Invoice, InvoiceURL -from backend.core.types.htmx import HtmxHttpRequest +from core.types.htmx import HtmxHttpRequest @web_require_scopes("invoices:read", False, False, "dashboard") diff --git a/backend/finance/views/receipts/dashboard.py b/backend/finance/views/receipts/dashboard.py index b810764e3..23cf1510b 100644 --- a/backend/finance/views/receipts/dashboard.py +++ b/backend/finance/views/receipts/dashboard.py @@ -2,7 +2,7 @@ from django.shortcuts import render from backend.decorators import web_require_scopes -from backend.core.types.htmx import HtmxHttpRequest +from core.types.htmx import HtmxHttpRequest @login_required diff --git a/backend/finance/views/reports/dashboard.py b/backend/finance/views/reports/dashboard.py index 5ce098bd1..16728ea36 100644 --- a/backend/finance/views/reports/dashboard.py +++ b/backend/finance/views/reports/dashboard.py @@ -1,4 +1,4 @@ -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest from django.shortcuts import render diff --git a/backend/finance/views/reports/view.py b/backend/finance/views/reports/view.py index 1041ab29d..dd239bd05 100644 --- a/backend/finance/views/reports/view.py +++ b/backend/finance/views/reports/view.py @@ -1,7 +1,7 @@ from django.contrib import messages -from backend.core.service.reports.get import get_report -from backend.core.types.requests import WebRequest +from backend.finance.service.reports.get import get_report +from core.types.requests import WebRequest from django.shortcuts import render, redirect diff --git a/backend/middleware.py b/backend/middleware.py index 2e5ec6908..43bb6158c 100644 --- a/backend/middleware.py +++ b/backend/middleware.py @@ -5,8 +5,8 @@ from django.http import HttpResponse from backend.models import User -from backend.core.types.htmx import HtmxAnyHttpRequest -from backend.core.types.requests import WebRequest +from core.types.htmx import HtmxAnyHttpRequest +from core.types.requests import WebRequest class HealthCheckMiddleware: diff --git a/backend/migrations/0001_initial.py b/backend/migrations/0001_initial.py index f9cff14bb..b6572b794 100644 --- a/backend/migrations/0001_initial.py +++ b/backend/migrations/0001_initial.py @@ -114,7 +114,7 @@ class Migration(migrations.Migration): "abstract": False, }, managers=[ - ("objects", backend.core.models.CustomUserManager()), + ("objects", backend.models.CustomUserManager()), ], ), migrations.CreateModel( diff --git a/backend/migrations/0022_loginlog_service_alter_verificationcodes_expiry_and_more.py b/backend/migrations/0022_loginlog_service_alter_verificationcodes_expiry_and_more.py index a192c24d6..ae25f4c2c 100644 --- a/backend/migrations/0022_loginlog_service_alter_verificationcodes_expiry_and_more.py +++ b/backend/migrations/0022_loginlog_service_alter_verificationcodes_expiry_and_more.py @@ -20,11 +20,11 @@ class Migration(migrations.Migration): migrations.AlterField( model_name="verificationcodes", name="expiry", - field=models.DateTimeField(default=backend.core.models.add_3hrs_from_now), + field=models.DateTimeField(default=backend.models.add_3hrs_from_now), ), migrations.AlterField( model_name="verificationcodes", name="token", - field=models.TextField(default=backend.core.models.RandomCode, editable=False), + field=models.TextField(default=backend.models.RandomCode, editable=False), ), ] diff --git a/backend/migrations/0023_apikey_invoiceonetimeschedule.py b/backend/migrations/0023_apikey_invoiceonetimeschedule.py index 5af19f8c8..f472a2c9f 100644 --- a/backend/migrations/0023_apikey_invoiceonetimeschedule.py +++ b/backend/migrations/0023_apikey_invoiceonetimeschedule.py @@ -18,7 +18,7 @@ class Migration(migrations.Migration): fields=[ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ("service", models.CharField(choices=[("aws_api_destination", "Aws Api Destination")], max_length=20, null=True)), - ("key", models.CharField(default=backend.core.models.RandomAPICode, max_length=100)), + ("key", models.CharField(default=backend.models.RandomAPICode, max_length=100)), ("last_used", models.DateTimeField(auto_now_add=True)), ], options={ diff --git a/backend/migrations/0046_rename_status_invoicereminder_boto_schedule_status_and_more.py b/backend/migrations/0046_rename_status_invoicereminder_boto_schedule_status_and_more.py index 6b0548fbb..332f04292 100644 --- a/backend/migrations/0046_rename_status_invoicereminder_boto_schedule_status_and_more.py +++ b/backend/migrations/0046_rename_status_invoicereminder_boto_schedule_status_and_more.py @@ -46,12 +46,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name="invoice", name="logo", - field=models.ImageField(blank=True, null=True, storage=backend.core.models._private_storage, upload_to="invoice_logos"), + field=models.ImageField(blank=True, null=True, storage=backend.models._private_storage, upload_to="invoice_logos"), ), migrations.AlterField( model_name="receipt", name="image", - field=models.ImageField(storage=backend.core.models._private_storage, upload_to="receipts"), + field=models.ImageField(storage=backend.models._private_storage, upload_to="receipts"), ), migrations.AlterField( model_name="teammemberpermission", @@ -63,7 +63,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name="usersettings", name="profile_picture", - field=models.ImageField(blank=True, null=True, storage=backend.core.models._public_storage, upload_to="profile_pictures/"), + field=models.ImageField(blank=True, null=True, storage=backend.models._public_storage, upload_to="profile_pictures/"), ), migrations.CreateModel( name="InvoiceRecurringProfile", @@ -109,7 +109,7 @@ class Migration(migrations.Migration): ("reference", models.CharField(blank=True, max_length=100, null=True)), ("invoice_number", models.CharField(blank=True, max_length=100, null=True)), ("vat_number", models.CharField(blank=True, max_length=100, null=True)), - ("logo", models.ImageField(blank=True, null=True, storage=backend.core.models._private_storage, upload_to="invoice_logos")), + ("logo", models.ImageField(blank=True, null=True, storage=backend.models._private_storage, upload_to="invoice_logos")), ("notes", models.TextField(blank=True, null=True)), ( "currency", diff --git a/backend/migrations/0048_alter_defaultvalues_default_invoice_logo.py b/backend/migrations/0048_alter_defaultvalues_default_invoice_logo.py index e00e2effb..a93d15ff2 100644 --- a/backend/migrations/0048_alter_defaultvalues_default_invoice_logo.py +++ b/backend/migrations/0048_alter_defaultvalues_default_invoice_logo.py @@ -3,7 +3,7 @@ import backend.models from django.db import migrations, models -from backend.core.models import _private_storage +from backend.models import _private_storage class Migration(migrations.Migration): diff --git a/backend/migrations/0049_filestoragefile.py b/backend/migrations/0049_filestoragefile.py index 31578e422..19b90a444 100644 --- a/backend/migrations/0049_filestoragefile.py +++ b/backend/migrations/0049_filestoragefile.py @@ -19,9 +19,7 @@ class Migration(migrations.Migration): ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ( "file", - models.FileField( - storage=backend.core.models._private_storage, upload_to=backend.core.models.upload_to_user_separate_folder - ), + models.FileField(storage=backend.models._private_storage, upload_to=backend.models.upload_to_user_separate_folder), ), ("created_at", models.DateTimeField(auto_now_add=True)), ("updated_at", models.DateTimeField(auto_now=True)), diff --git a/backend/migrations/0063_defaultvalues_email_template_recurring_invoices_invoice_cancelled_and_more.py b/backend/migrations/0063_defaultvalues_email_template_recurring_invoices_invoice_cancelled_and_more.py index 4f30d311f..5aefa7ed3 100644 --- a/backend/migrations/0063_defaultvalues_email_template_recurring_invoices_invoice_cancelled_and_more.py +++ b/backend/migrations/0063_defaultvalues_email_template_recurring_invoices_invoice_cancelled_and_more.py @@ -1,6 +1,6 @@ # Generated by Django 5.1 on 2024-09-28 18:46 -import backend.core.data.default_email_templates +import backend.data.default_email_templates from django.db import migrations, models @@ -15,22 +15,18 @@ class Migration(migrations.Migration): model_name="defaultvalues", name="email_template_recurring_invoices_invoice_cancelled", field=models.TextField( - default=backend.core.data.default_email_templates.recurring_invoices_invoice_cancelled_default_email_template + default=backend.data.default_email_templates.recurring_invoices_invoice_cancelled_default_email_template ), ), migrations.AddField( model_name="defaultvalues", name="email_template_recurring_invoices_invoice_created", - field=models.TextField( - default=backend.core.data.default_email_templates.recurring_invoices_invoice_created_default_email_template - ), + field=models.TextField(default=backend.data.default_email_templates.recurring_invoices_invoice_created_default_email_template), ), migrations.AddField( model_name="defaultvalues", name="email_template_recurring_invoices_invoice_overdue", - field=models.TextField( - default=backend.core.data.default_email_templates.recurring_invoices_invoice_overdue_default_email_template - ), + field=models.TextField(default=backend.data.default_email_templates.recurring_invoices_invoice_overdue_default_email_template), ), migrations.AddField( model_name="defaultvalues", diff --git a/backend/models.py b/backend/models.py index 6b354acfe..fcc2196aa 100644 --- a/backend/models.py +++ b/backend/models.py @@ -1,24 +1,28 @@ -from backend.core.models import ( - PasswordSecret, +from core.models import ( AuditLog, LoginLog, Error, TracebackError, - UserSettings, - Notification, Organization, TeamInvitation, TeamMemberPermission, User, FeatureFlags, + UserSettings, + Notification, VerificationCodes, - QuotaLimit, - QuotaOverrides, - QuotaUsage, - QuotaIncreaseRequest, + PasswordSecret, EmailSendStatus, - FileStorageFile, - MultiFileUpload, + OwnerBase, + _private_storage, + USER_OR_ORGANIZATION_CONSTRAINT, + ExpiresBase, + CustomUserManager, + add_3hrs_from_now, + RandomCode, + _public_storage, + upload_to_user_separate_folder, + RandomAPICode, ) from backend.finance.models import ( @@ -35,3 +39,5 @@ ) from backend.clients.models import Client, DefaultValues + +from backend.storage.models import FileStorageFile, MultiFileUpload diff --git a/backend/storage/api/delete.py b/backend/storage/api/delete.py index a2ac79f14..822a562ec 100644 --- a/backend/storage/api/delete.py +++ b/backend/storage/api/delete.py @@ -6,7 +6,7 @@ from backend.decorators import htmx_only from backend.models import FileStorageFile -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest @require_http_methods(["DELETE"]) diff --git a/backend/storage/api/fetch.py b/backend/storage/api/fetch.py index e8b0e33da..efdf23125 100644 --- a/backend/storage/api/fetch.py +++ b/backend/storage/api/fetch.py @@ -5,8 +5,8 @@ from backend.models import FileStorageFile # from backend.core.service.billing.calculate.test import generate_monthly_billing_summary -from backend.core.service.file_storage.utils import format_file_size -from backend.core.types.requests import WebRequest +from backend.storage.service.utils import format_file_size +from core.types.requests import WebRequest from django.shortcuts import render diff --git a/backend/storage/file_storage.py b/backend/storage/file_storage.py index 4c01777c1..9282a735c 100644 --- a/backend/storage/file_storage.py +++ b/backend/storage/file_storage.py @@ -3,7 +3,7 @@ from django.db.models.signals import pre_delete, post_save from django.dispatch import receiver -from backend.core.models import _private_storage +from backend.models import _private_storage from backend.models import FileStorageFile logger = logging.getLogger(__name__) diff --git a/backend/storage/models.py b/backend/storage/models.py new file mode 100644 index 000000000..b6225ea07 --- /dev/null +++ b/backend/storage/models.py @@ -0,0 +1,31 @@ +from __future__ import annotations +from uuid import uuid4 +from django.db import models +from backend.models import OwnerBase, _private_storage, upload_to_user_separate_folder, User + + +class FileStorageFile(OwnerBase): + file = models.FileField(upload_to=upload_to_user_separate_folder, storage=_private_storage) + file_uri_path = models.CharField(max_length=500) # relative path not including user folder/media + last_edited_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, editable=False, related_name="files_edited") + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + __original_file = None + __original_file_uri_path = None + + def __init__(self, *args, **kwargs): + super(FileStorageFile, self).__init__(*args, **kwargs) + self.__original_file = self.file + self.__original_file_uri_path = self.file_uri_path + + +class MultiFileUpload(OwnerBase): + files = models.ManyToManyField(FileStorageFile, related_name="multi_file_uploads") + started_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + finished_at = models.DateTimeField(null=True, blank=True, editable=False) + uuid = models.UUIDField(default=uuid4, editable=False, unique=True) + + def is_finished(self): + return self.finished_at is not None diff --git a/backend/core/service/asyn_tasks/__init__.py b/backend/storage/service/__init__.py similarity index 100% rename from backend/core/service/asyn_tasks/__init__.py rename to backend/storage/service/__init__.py diff --git a/backend/core/service/file_storage/create.py b/backend/storage/service/create.py similarity index 95% rename from backend/core/service/file_storage/create.py rename to backend/storage/service/create.py index 84c16cafc..5da1c3c79 100644 --- a/backend/core/service/file_storage/create.py +++ b/backend/storage/service/create.py @@ -1,6 +1,6 @@ from django.core.files.uploadedfile import UploadedFile -from backend.core.utils.dataclasses import BaseServiceResponse +from core.utils.dataclasses import BaseServiceResponse from backend.models import FileStorageFile, User, Organization diff --git a/backend/core/service/file_storage/utils.py b/backend/storage/service/utils.py similarity index 100% rename from backend/core/service/file_storage/utils.py rename to backend/storage/service/utils.py diff --git a/backend/storage/views/dashboard.py b/backend/storage/views/dashboard.py index 47d9a79b4..7408eb28c 100644 --- a/backend/storage/views/dashboard.py +++ b/backend/storage/views/dashboard.py @@ -1,9 +1,9 @@ from django.shortcuts import render from django.utils.html import escape -from backend.core.models import FileStorageFile -from backend.core.service.file_storage.utils import format_file_size -from backend.core.types.requests import WebRequest +from backend.models import FileStorageFile +from backend.storage.service.utils import format_file_size +from core.types.requests import WebRequest def file_storage_dashboard_endpoint(request: WebRequest): diff --git a/backend/storage/views/upload.py b/backend/storage/views/upload.py index 408d80d04..b78068261 100644 --- a/backend/storage/views/upload.py +++ b/backend/storage/views/upload.py @@ -8,11 +8,11 @@ from django.utils import timezone from django.views.decorators.http import require_http_methods -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest from backend.models import FileStorageFile, MultiFileUpload -from backend.core.models import _private_storage, upload_to_user_separate_folder +from backend.models import _private_storage, upload_to_user_separate_folder -from backend.core.service.file_storage.create import parse_files_for_creation +from backend.storage.service.create import parse_files_for_creation from django.urls import reverse diff --git a/backend/templatetags/cal_filters.py b/backend/templatetags/cal_filters.py deleted file mode 100644 index e68d14793..000000000 --- a/backend/templatetags/cal_filters.py +++ /dev/null @@ -1,16 +0,0 @@ -from django import template - -register = template.Library() - - -@register.filter -def ordinal(value): - try: - value = int(value) - if 10 <= value % 100 <= 20: - suffix = "th" - else: - suffix = {1: "st", 2: "nd", 3: "rd"}.get(value % 10, "th") - return f"{value}{suffix}" - except (ValueError, TypeError): - return value diff --git a/backend/templatetags/dictfilters.py b/backend/templatetags/dictfilters.py deleted file mode 100644 index ed99a7ff0..000000000 --- a/backend/templatetags/dictfilters.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Any - -from django import template - -register = template.Library() - - -@register.simple_tag -def dict_get(dictionary: dict, key: Any): - return dictionary.get(key) - - -register.filter("dict_get", dict_get) diff --git a/backend/templatetags/feature_enabled.py b/backend/templatetags/feature_enabled.py deleted file mode 100644 index 3d00c8ed2..000000000 --- a/backend/templatetags/feature_enabled.py +++ /dev/null @@ -1,44 +0,0 @@ -from django import template -from django.urls import NoReverseMatch - -from backend.models import User, Organization -from backend.core.utils.feature_flags import get_feature_status - -from django.conf import settings - -register = template.Library() - - -@register.simple_tag -def has_module(module_str: str): - return module_str in settings.INSTALLED_APPS - - -@register.simple_tag -def safe_url(view_name, *args, **kwargs): - from django.urls import reverse - - try: - return reverse(view_name, args=args, kwargs=kwargs) - except NoReverseMatch: - return "" - - -@register.simple_tag -def feature_enabled(feature): - return get_feature_status(feature) - - -@register.simple_tag -def personal_feature_enabled(user: User, feature: str): - return user.user_profile.has_feature(feature) - - -@register.simple_tag -def has_entitlement(actor: User | Organization, entitlement: str) -> bool: - if not settings.BILLING_ENABLED: - return True - - from billing.service.entitlements import has_entitlement as _has_entitlement - - return _has_entitlement(actor, entitlement) diff --git a/backend/templatetags/listfilters.py b/backend/templatetags/listfilters.py deleted file mode 100644 index 8a1bde5f1..000000000 --- a/backend/templatetags/listfilters.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Any - -from django import template - -register = template.Library() - - -@register.simple_tag -def list_item_prefix_distinct(items: list, index: int = 0, separator=":"): - return set(i.split(separator)[index] for i in items) - - -def lookup_separator_perms(items: list, lookup_value: Any): - values = [i.split(":")[-1] for i in items if i.split(":")[0] == lookup_value] - return values - - -def at_index(items: list, index: int = 0): - return items[index] - - -@register.simple_tag -def common_items(*lists: list[list]) -> list: - result = set(lists[0]) - for lst in lists[1:]: - result.intersection_update(lst) - return list(result) - - -@register.simple_tag -def common_items_count(*lists: list[list]) -> int: - return len(common_items(*lists)) - - -def common_children_filter(list1, list2): - return common_items(list1, list2) - - -@register.filter -def get_first_n_items(value, n): - """ - Returns the first n items of a list. - """ - try: - return value[:n] - except TypeError: - return [] - - -register.filter("list_item_prefix_distinct", list_item_prefix_distinct) -register.filter("lookup_separator_perms", lookup_separator_perms) -register.filter("at_index", at_index) -register.filter("common_children_filter", common_children_filter) diff --git a/backend/templatetags/strfilters.py b/backend/templatetags/strfilters.py deleted file mode 100644 index c71ffe66f..000000000 --- a/backend/templatetags/strfilters.py +++ /dev/null @@ -1,59 +0,0 @@ -import time - -from django import template - -register = template.Library() - - -def split(string, char=" "): - return string.split(char) - - -def dashify(string, recurrence=2): - num_str = str(string) - - return "-".join(num_str[i : i + recurrence] for i in range(0, len(num_str), recurrence)) - - -def to_list(string, separator=",") -> list[str]: - return string.split(separator) - - -def contains(value, arg): - return arg in str(value) - - -def day_to_number_sunday(day: str) -> int: - """ - Converts a day of the week to a number with Sunday as the first day. - - Args: - day (str): The day of the week (e.g., "Sunday", "Monday"). - - Returns: - int: The corresponding number with Sunday as 0 and Saturday as 6. - """ - # Get the day number with Monday as 0 - day_number = time.strptime(day, "%A").tm_wday - - # Adjust the day number to make Sunday the first day - sunday_first_day_number = (day_number + 1) % 7 - - return sunday_first_day_number - - -def day_to_number_monday(day: str) -> int: - return time.strptime(day, "%A").tm_wday + 1 - - -def month_to_number(month: str) -> int: - return time.strptime(month, "%B").tm_mon - - -register.filter("split", split) -register.filter("dashify", dashify) -register.filter("contains", contains) -register.filter("to_list", to_list) -register.filter("day_to_number_monday", day_to_number_monday) -register.filter("day_to_number_sunday", day_to_number_sunday) -register.filter("month_to_number", month_to_number) diff --git a/backend/templatetags/utils.py b/backend/templatetags/utils.py deleted file mode 100644 index abc003c5c..000000000 --- a/backend/templatetags/utils.py +++ /dev/null @@ -1,42 +0,0 @@ -import re - -from django.template import Library, Node -from django.utils.encoding import force_str - -register = Library() - - -def strip_spaces_in_tags(value): - value = force_str(value) - value = re.sub(r"\s+", " ", value) - value = re.sub(r">\s+", ">", value) - value = re.sub(r"\s+<", "<", value) - return value - - -class NoSpacesNode(Node): - def __init__(self, nodelist): - self.nodelist = nodelist - - def render(self, context): - return strip_spaces_in_tags(self.nodelist.render(context).strip()) - - -@register.tag -def nospaces(parser, token): - """ - Removes any duplicite whitespace in tags and text. Can be used as supplementary tag for {% spaceless %}:: - - {% nospaces %} - - Hello - this is text - - {% nospaces %} - - Returns:: - Hello this is text - """ - nodelist = parser.parse(("endnospaces",)) - parser.delete_first_token() - return NoSpacesNode(nodelist) diff --git a/backend/urls.py b/backend/urls.py index 494ea5dc6..5cdb90037 100644 --- a/backend/urls.py +++ b/backend/urls.py @@ -9,35 +9,34 @@ from django.views.generic import RedirectView from django.views.static import serve -from backend.core.api.public.swagger_ui import get_swagger_ui, get_swagger_endpoints from backend.finance.views.invoices.single.view import view_invoice_with_uuid_endpoint from backend.finance.views.receipts.dashboard import receipts_dashboard -from backend.core.views.other.index import dashboard -from backend.core.views.other.index import index, pricing -from backend.core.views.quotas.view import quotas_list -from backend.core.views.quotas.view import view_quota_increase_requests -from settings.settings import BILLING_ENABLED +from core.views.other.index import dashboard +from core.views.other.index import index, pricing + +# from core.views.quotas.view import quotas_list +# from core.views.quotas.view import view_quota_increase_requests url( r"^frontend/static/(?P.*)$", serve, {"document_root": settings.STATICFILES_DIRS[0]}, ) + +api_patterns = [path("finance/", include("backend.finance.api"))] + urlpatterns = [ - path("tz_detect/", include("tz_detect.urls")), - path("webhooks/", include("backend.core.webhooks.urls")), + path("", include("core.urls")), + path("webhooks/", include("backend.webhooks.urls")), path("", index, name="index"), path("pricing", pricing, name="pricing"), path("dashboard/", dashboard, name="dashboard"), - path("dashboard/settings/", include("backend.core.views.settings.urls")), - path("dashboard/teams/", include("backend.core.views.teams.urls")), path("dashboard/", include("backend.finance.views.urls")), # path("dashboard/quotas/", quotas_page, name="quotas"), path("dashboard/quotas/", RedirectView.as_view(url="/dashboard"), name="quotas"), - path("dashboard/quotas//", quotas_list, name="quotas group"), - path("dashboard/emails/", include("backend.core.views.emails.urls")), + # path("dashboard/quotas//", quotas_list, name="quotas group"), path("dashboard/reports/", include("backend.finance.views.reports.urls")), - path("dashboard/admin/quota_requests/", view_quota_increase_requests, name="admin quota increase requests"), + # path("dashboard/admin/quota_requests/", view_quota_increase_requests, name="admin quota increase requests"), path("dashboard/file_storage/", include("backend.storage.views.urls")), path("dashboard/clients/", include("backend.clients.views.urls")), path("favicon.ico", RedirectView.as_view(url=settings.STATIC_URL + "favicon.ico")), @@ -51,25 +50,9 @@ view_invoice_with_uuid_endpoint, name="invoices view invoice", ), - path("login/external/", include("social_django.urls", namespace="social")), - path("auth/", include("backend.core.views.auth.urls")), - path("api/", include("backend.core.api.urls")), - path("admin/", admin.site.urls), + path("api/", include("backend.api.urls")), ] + static(settings.STATIC_URL, document_root=settings.STATICFILES_DIRS[0]) -if settings.DEBUG: - urlpatterns += [path("silk/", include("silk.urls", namespace="silk"))] - - urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) - # may not need to be in debug - urlpatterns += static(settings.STATIC_URL, document_root=settings.STATICFILES_DIRS[0]) - -if BILLING_ENABLED: - urlpatterns.append(path("", include("billing.urls"))) - -schema_view = get_swagger_ui() -urlpatterns += get_swagger_endpoints(settings.DEBUG) - -handler500 = "backend.core.views.other.errors.universal" -handler404 = "backend.core.views.other.errors.universal" -handler403 = "backend.core.views.other.errors.e_403" +handler500 = "core.views.other.errors.universal" +handler404 = "core.views.other.errors.universal" +handler403 = "core.views.other.errors.e_403" diff --git a/backend/core/service/base/__init__.py b/backend/webhooks/__init__.py similarity index 100% rename from backend/core/service/base/__init__.py rename to backend/webhooks/__init__.py diff --git a/backend/core/service/boto3/__init__.py b/backend/webhooks/invoices/__init__.py similarity index 100% rename from backend/core/service/boto3/__init__.py rename to backend/webhooks/invoices/__init__.py diff --git a/backend/core/webhooks/invoices/invoice_status.py b/backend/webhooks/invoices/invoice_status.py similarity index 87% rename from backend/core/webhooks/invoices/invoice_status.py rename to backend/webhooks/invoices/invoice_status.py index 3f9e85d9f..41d84afcd 100644 --- a/backend/core/webhooks/invoices/invoice_status.py +++ b/backend/webhooks/invoices/invoice_status.py @@ -6,12 +6,12 @@ from login_required import login_not_required from backend.finance.models import InvoiceRecurringProfile -from backend.core.service.invoices.recurring.generation.next_invoice import safe_generate_next_invoice_service -from backend.core.service.invoices.recurring.webhooks.webhook_apikey_auth import authenticate_api_key +from backend.finance.service.invoices.recurring.generation.next_invoice import safe_generate_next_invoice_service +from backend.finance.service.invoices.recurring.webhooks.webhook_apikey_auth import authenticate_api_key import logging -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest logger = logging.getLogger(__name__) diff --git a/backend/core/webhooks/invoices/recurring.py b/backend/webhooks/invoices/recurring.py similarity index 88% rename from backend/core/webhooks/invoices/recurring.py rename to backend/webhooks/invoices/recurring.py index 3f0a1b627..f2c154027 100644 --- a/backend/core/webhooks/invoices/recurring.py +++ b/backend/webhooks/invoices/recurring.py @@ -6,12 +6,12 @@ from login_required import login_not_required from backend.finance.models import InvoiceRecurringProfile -from backend.core.service.invoices.recurring.generation.next_invoice import safe_generate_next_invoice_service -from backend.core.service.invoices.recurring.webhooks.webhook_apikey_auth import authenticate_api_key +from backend.finance.service.invoices.recurring.generation.next_invoice import safe_generate_next_invoice_service +from backend.finance.service.invoices.recurring.webhooks.webhook_apikey_auth import authenticate_api_key import logging -from backend.core.types.requests import WebRequest +from core.types.requests import WebRequest logger = logging.getLogger(__name__) diff --git a/backend/core/webhooks/urls.py b/backend/webhooks/urls.py similarity index 67% rename from backend/core/webhooks/urls.py rename to backend/webhooks/urls.py index 2ceeab7d0..2a9035270 100644 --- a/backend/core/webhooks/urls.py +++ b/backend/webhooks/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from backend.core.webhooks.invoices.recurring import handle_recurring_invoice_webhook_endpoint +from backend.webhooks.invoices.recurring import handle_recurring_invoice_webhook_endpoint urlpatterns = [ path("schedules/receive/recurring_invoices/", handle_recurring_invoice_webhook_endpoint, name="receive_recurring_invoices"), diff --git a/billing/__init__.py b/billing/__init__.py deleted file mode 100644 index 2088f7c1e..000000000 --- a/billing/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import apps, billing_settings diff --git a/billing/admin.py b/billing/admin.py deleted file mode 100644 index 8c38f3f3d..000000000 --- a/billing/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/billing/apps.py b/billing/apps.py deleted file mode 100644 index a663d988f..000000000 --- a/billing/apps.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.apps import AppConfig - - -class BillingConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "billing" - - def ready(self): - from . import signals - - pass diff --git a/billing/billing_settings.py b/billing/billing_settings.py deleted file mode 100644 index 124cb9c77..000000000 --- a/billing/billing_settings.py +++ /dev/null @@ -1,43 +0,0 @@ -import stripe - -from settings.helpers import get_var - -STRIPE_TEST_SECRET_KEY = get_var("STRIPE_TEST_SECRET_KEY") -STRIPE_LIVE_SECRET_KEY = get_var("STRIPE_LIVE_SECRET_KEY") -STRIPE_WEBHOOK_ENDPOINT_SECRET = get_var("STRIPE_WEBHOOK_ENDPOINT_SECRET") - -STRIPE_MAIN_API_KEY = STRIPE_LIVE_SECRET_KEY if STRIPE_LIVE_SECRET_KEY else STRIPE_TEST_SECRET_KEY - -STRIPE_LIVE_MODE = True if STRIPE_LIVE_SECRET_KEY else False - -stripe.api_key = STRIPE_MAIN_API_KEY - -NO_SUBSCRIPTION_PLAN_DENY_VIEW_NAMES: set[str] = { - "clients:create", - "file_storage:upload:start_batch", - "file_storage:upload:end_batch", - "file_storage:upload:add_to_batch", - "file_storage:upload:dashboard", - # "finance:invoices:single:manage_access", - "finance:invoices:single:manage_access create", - # "finance:invoices:single:manage_access delete", - "finance:invoices:single:edit", - "finance:invoices:single:create", - "finance:invoices:recurring:create", - "finance:invoices:recurring:edit", - # APIS - "teams:invite", - "teams:create", - "receipts:edit", - "receipts:new", - "finance:invoices:single:edit", - "finance:invoices:single:edit discount", - "finance:invoices:recurring:generate next invoice", - "finance:invoices:recurring:edit", - "finance:invoices:create:set_destination from", - "finance:invoices:create:set_destination to", - "finance:invoices:create:services add", - "products:create", - "public:clients:create", - "public:invoices:create", -} diff --git a/billing/data/__init__.py b/billing/data/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/billing/data/default_usage_plans.py b/billing/data/default_usage_plans.py deleted file mode 100644 index 0f50ec7a5..000000000 --- a/billing/data/default_usage_plans.py +++ /dev/null @@ -1,135 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from decimal import Decimal -from typing import Literal - - -@dataclass -class DefaultFeature: - """ - Single Product, e.g. "invoices-created" - This is a stripe "PRICE" (part of a PRODUCT) - """ - - slug: str # Consistent slug across plans - description: str - max_limit_per_month: int - subscription_plan: DefaultSubscriptionPlan - - -@dataclass -class DefaultFeatureGroup: - """ - Group of products, e.g. "Invoices" - """ - - name: str - items: list[DefaultFeature] - - -@dataclass -class DefaultSubscriptionPlan: - """ - This is a Stripe PRODUCT - """ - - name: str - price_per_month: int - description: str - - -# Default subscription plans -free_plan = DefaultSubscriptionPlan( - name="Free Trial", - price_per_month=0, - description="Try out MyFinances", -) - -starter_plan = DefaultSubscriptionPlan( - name="Starter", - price_per_month=5, - description="For small businesses that need limited features", -) - -growth_plan = DefaultSubscriptionPlan( - name="Growth", - price_per_month=10, - description="For growing businesses that need a little extra", -) - -# enterprise_plan = DefaultSubscriptionPlan( -# name="Enterprise", -# price_per_month=-1, -# description="Additional customization for your ideal business", -# ) - -default_subscription_plans: list[DefaultSubscriptionPlan] = [free_plan, starter_plan, growth_plan] - -# Default usage plans -default_usage_plans: list[DefaultFeatureGroup] = [ - DefaultFeatureGroup( - "invoices", - [ - # region "invoices-created" - DefaultFeature( - slug="invoices-created", - description="Amount of invoices created per month", - max_limit_per_month=10, - subscription_plan=free_plan, - ), - DefaultFeature( - slug="invoices-created", - description="Amount of invoices created per month (starter plan)", - max_limit_per_month=500, - subscription_plan=starter_plan, - ), - DefaultFeature( - slug="invoices-created", - description="Amount of invoices created per month", - max_limit_per_month=-1, - subscription_plan=growth_plan, - ), - # endregion "invoices-created" - # region "invoices-sent-via-schedule" - DefaultFeature( - slug="invoices-sent-via-schedule", - description="Amount of invoices sent from a schedule per month", - max_limit_per_month=1, - subscription_plan=free_plan, - ), - DefaultFeature( - slug="invoices-sent-via-schedule", - description="Amount of invoices sent from a schedule per month", - max_limit_per_month=50, - subscription_plan=starter_plan, - ), - DefaultFeature( - slug="invoices-sent-via-schedule", - description="Amount of invoices sent from a schedule per month", - max_limit_per_month=-1, - subscription_plan=growth_plan, - ), - # endregion "invoices-sent-via-schedule" - ], - ), - DefaultFeatureGroup( - "teams", - [ - # region "organization-access" - DefaultFeature( - slug="organization-access", - description="Amount of invoices created per month (starter plan)", - max_limit_per_month=1, - subscription_plan=starter_plan, - ), - DefaultFeature( - slug="organization-access", - description="Amount of invoices created per month", - max_limit_per_month=1, - subscription_plan=growth_plan, - ), - # endregion "organization-access" - ], - ), -] diff --git a/billing/decorators.py b/billing/decorators.py deleted file mode 100644 index ed755de17..000000000 --- a/billing/decorators.py +++ /dev/null @@ -1,32 +0,0 @@ -from django.contrib import messages -from django.http import HttpResponseRedirect -from django.shortcuts import redirect, render -from django.urls import reverse - -from billing.service.entitlements import has_entitlement, get_entitlements - - -def has_entitlements_called_from_backend_handler(entitlements: list[str] | str, htmx_api: bool = False): - def decorator(view_func): - def wrapper_func(request, *args, **kwargs): - user_does_have_entitlements: bool - if isinstance(entitlements, (list, set)): - users_entitlements = get_entitlements(request.actor) - user_does_have_entitlements = all(entitlement in users_entitlements for entitlement in entitlements) - else: - user_does_have_entitlements = has_entitlement(request.actor, entitlements) - - if user_does_have_entitlements: - return view_func(request, *args, **kwargs) - else: - messages.warning(request, f"Your plan unfortunately doesn't include this feature.") - - if htmx_api: - return render(request, "base/toast.html", {"autohide": False}) - elif request.htmx: - return HttpResponseRedirect(reverse("billing:dashboard")) - return redirect("billing:dashboard") - - return wrapper_func - - return decorator diff --git a/billing/management/__init__.py b/billing/management/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/billing/management/commands/__init__.py b/billing/management/commands/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/billing/management/commands/stripe.py b/billing/management/commands/stripe.py deleted file mode 100644 index 136e531f5..000000000 --- a/billing/management/commands/stripe.py +++ /dev/null @@ -1,43 +0,0 @@ -from django.core.management.base import BaseCommand -from django.core.cache import cache -import stripe - - -class Command(BaseCommand): - def add_arguments(self, parser): - parser.add_argument( - "action", - type=str, - help="help, create_entitlements", - ) - - def handle(self, *args, **kwargs): - action = kwargs.get("action") - - match action: - case "create_entitlements": - for entitlement in [ - "Receipts", - "File Storage", - "Organizations", - "Invoice Reminders", - "API Access", - "Emails", - "Advanced Onboarding", - "Basic Onboarding", - "Invoice Schedules", - "Invoices", - "Customers", - ]: - try: - stripe.entitlements.Feature.create(name=entitlement, lookup_key=entitlement.lower().replace(" ", "-")) - print(f"Created entitlement: {entitlement}") - except stripe.error.InvalidRequestError: - print(f"Entitlement already exists: {entitlement}") - case (_, "help"): - print( - """ - Available actions: - - create_entitlements: This will create all entitlements that you don't have - """ - ) diff --git a/billing/middleware.py b/billing/middleware.py deleted file mode 100644 index 6873b5cd6..000000000 --- a/billing/middleware.py +++ /dev/null @@ -1,50 +0,0 @@ -from django.contrib import messages -from django.shortcuts import redirect, render -from django.urls import resolve - -from backend.core.types.requests import WebRequest -from billing.billing_settings import NO_SUBSCRIPTION_PLAN_DENY_VIEW_NAMES -from billing.models import UserSubscription - - -# middleware to check if user is subscribed to a plan yet - - -class CheckUserSubScriptionMiddleware: - def __init__(self, get_response): - self.get_response = get_response - - def __call__(self, request: WebRequest): - if not request.user.is_authenticated: - return self.get_response(request) - - if request.team: - # todo: handle organization billing - return self.get_response(request) - - subscription: UserSubscription | None = ( - UserSubscription.filter_by_owner(request.actor).filter(end_date__isnull=True).prefetch_related("subscription_plan").first() - ) - request.users_subscription = subscription - - resolver_match = resolve(request.path_info) - - view_name = resolver_match.view_name - - if view_name not in NO_SUBSCRIPTION_PLAN_DENY_VIEW_NAMES: - return self.get_response(request) - - if not subscription: - print("[BILLING] [MIDDLEWARE] User doesn't have an active subscription.") - messages.warning( - request, - """ - You currently are not subscribed to a plan. If you think this is a mistake scroll down and - press "Refetch" or contact support at - support@strelix.org.""", - ) - - if request.htmx: - return render(request, "base/toast.html", {"autohide": False}) - return redirect("billing:dashboard") - return self.get_response(request) diff --git a/billing/migrations/0001_initial.py b/billing/migrations/0001_initial.py deleted file mode 100644 index b246e1340..000000000 --- a/billing/migrations/0001_initial.py +++ /dev/null @@ -1,89 +0,0 @@ -# Generated by Django 5.1 on 2024-08-29 13:26 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ("backend", "0055_remove_planfeature_group_and_more"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="PlanFeatureGroup", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("name", models.CharField(max_length=50)), - ], - ), - migrations.CreateModel( - name="SubscriptionPlan", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("name", models.CharField(max_length=50, unique=True)), - ("price_per_month", models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), - ("description", models.TextField(blank=True, max_length=500, null=True)), - ("maximum_duration_months", models.IntegerField(blank=True, null=True)), - ("stripe_product_id", models.CharField(blank=True, max_length=100, null=True)), - ], - ), - migrations.CreateModel( - name="PlanFeature", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("slug", models.CharField(max_length=100)), - ("stripe_price_id", models.CharField(blank=True, max_length=100, null=True)), - ("description", models.TextField(blank=True, max_length=500, null=True)), - ("max_limit_per_month", models.IntegerField(blank=True, null=True)), - ( - "group", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="features", to="billing.planfeaturegroup"), - ), - ( - "subscription_plan", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="features", to="billing.subscriptionplan"), - ), - ], - ), - migrations.CreateModel( - name="UserSubscription", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("stripe_subscription_id", models.CharField(blank=True, max_length=100, null=True)), - ("custom_subscription_price_per_month", models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), - ("start_date", models.DateTimeField(auto_now_add=True)), - ("end_date", models.DateTimeField(blank=True, null=True)), - ( - "organization", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - ( - "subscription_plan", - models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to="billing.subscriptionplan"), - ), - ( - "user", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ], - options={ - "abstract": False, - "constraints": [ - models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="billing_usersubscription_check_user_or_organization", - ) - ], - }, - ), - ] diff --git a/billing/migrations/0002_subscriptionplan_stripe_price_id.py b/billing/migrations/0002_subscriptionplan_stripe_price_id.py deleted file mode 100644 index ee5c42b5a..000000000 --- a/billing/migrations/0002_subscriptionplan_stripe_price_id.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1 on 2024-08-29 19:20 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("billing", "0001_initial"), - ] - - operations = [ - migrations.AddField( - model_name="subscriptionplan", - name="stripe_price_id", - field=models.CharField(blank=True, max_length=100, null=True), - ), - ] diff --git a/billing/migrations/0003_stripewebhookevent_usersubscription_uuid_and_more.py b/billing/migrations/0003_stripewebhookevent_usersubscription_uuid_and_more.py deleted file mode 100644 index 4d4e5881d..000000000 --- a/billing/migrations/0003_stripewebhookevent_usersubscription_uuid_and_more.py +++ /dev/null @@ -1,69 +0,0 @@ -# Generated by Django 5.1 on 2024-08-30 15:55 - -import django.db.models.deletion -import uuid -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0056_user_stripe_customer_id"), - ("billing", "0002_subscriptionplan_stripe_price_id"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="StripeWebhookEvent", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("event_id", models.CharField(max_length=100, unique=True)), - ("event_type", models.CharField(max_length=100)), - ("data", models.JSONField()), - ("raw_event", models.JSONField()), - ], - ), - migrations.AddField( - model_name="usersubscription", - name="uuid", - field=models.UUIDField(default=uuid.uuid4, null=True), - ), - migrations.CreateModel( - name="StripeCheckoutSession", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("uuid", models.UUIDField(default=uuid.uuid4, unique=True)), - ("stripe_session_id", models.CharField(blank=True, max_length=100, null=True, unique=True)), - ("features", models.ManyToManyField(related_name="checkout_sessions", to="billing.planfeature")), - ( - "organization", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - ( - "plan", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, related_name="checkout_sessions", to="billing.subscriptionplan" - ), - ), - ( - "user", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ], - options={ - "abstract": False, - "constraints": [ - models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="billing_stripecheckoutsession_check_user_or_organization", - ) - ], - }, - ), - ] diff --git a/billing/migrations/0004_auto_20240830_1655.py b/billing/migrations/0004_auto_20240830_1655.py deleted file mode 100644 index 94531271e..000000000 --- a/billing/migrations/0004_auto_20240830_1655.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.1 on 2024-08-30 15:55 -import uuid - -from django.db import migrations - - -def gen_uuid(apps, schema_editor): - UserSubscription = apps.get_model("billing", "UserSubscription") - for row in UserSubscription.objects.all(): - row.uuid = uuid.uuid4() - row.save(update_fields=["uuid"]) - - -class Migration(migrations.Migration): - - dependencies = [ - ("billing", "0003_stripewebhookevent_usersubscription_uuid_and_more"), - ] - - operations = [ - migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop), - ] diff --git a/billing/migrations/0005_auto_20240830_1655.py b/billing/migrations/0005_auto_20240830_1655.py deleted file mode 100644 index 9ce66ecfd..000000000 --- a/billing/migrations/0005_auto_20240830_1655.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1 on 2024-08-30 15:55 -import uuid - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("billing", "0004_auto_20240830_1655"), - ] - - operations = [ - migrations.AlterField( - model_name="usersubscription", - name="uuid", - field=models.UUIDField(default=uuid.uuid4, unique=True), - ), - ] diff --git a/billing/migrations/0006_billingusage.py b/billing/migrations/0006_billingusage.py deleted file mode 100644 index 9fb0b537a..000000000 --- a/billing/migrations/0006_billingusage.py +++ /dev/null @@ -1,51 +0,0 @@ -# Generated by Django 5.1 on 2024-08-31 10:47 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0056_user_stripe_customer_id"), - ("billing", "0005_auto_20240830_1655"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="BillingUsage", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("event_name", models.CharField(max_length=100)), - ("event_type", models.CharField(choices=[("usage", "Metered Usage")], default="usage", max_length=20)), - ("quantity", models.PositiveSmallIntegerField(default=1)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("processed_at", models.DateTimeField(blank=True, null=True)), - ("processed", models.BooleanField(default=False)), - ("stripe_unique_usage_identifier", models.CharField(blank=True, max_length=100, null=True)), - ( - "organization", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - ( - "user", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ], - options={ - "abstract": False, - "constraints": [ - models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="billing_billingusage_check_user_or_organization", - ) - ], - }, - ), - ] diff --git a/billing/migrations/__init__.py b/billing/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/billing/models.py b/billing/models.py deleted file mode 100644 index 65a184cd1..000000000 --- a/billing/models.py +++ /dev/null @@ -1,117 +0,0 @@ -from uuid import uuid4 - -from django.db import models - -from backend.core.models import OwnerBase - -from django.utils import timezone - -from django.utils.timezone import now as timezone_now - - -class SubscriptionPlan(models.Model): - """ - Subscription plans available for users. - """ - - name = models.CharField(max_length=50, unique=True) - price_per_month = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) - description = models.TextField(max_length=500, null=True, blank=True) - stripe_product_id = models.CharField(max_length=100, null=True, blank=True) - stripe_price_id = models.CharField(max_length=100, null=True, blank=True) - - -def __str__(self): - return f"{self.name} - {self.price_per_month or 'free' if self.price_per_month != -1 else 'custom'}" - - -class UserSubscription(OwnerBase): - """ - Track which subscription plan a user is currently subscribed to. - """ - - uuid = models.UUIDField(unique=True, default=uuid4) - subscription_plan = models.ForeignKey(SubscriptionPlan, on_delete=models.SET_NULL, null=True) - stripe_subscription_id = models.CharField(max_length=100, null=True, blank=True) - # Custom price only used for enterprise or negotiated plans - custom_subscription_price_per_month = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) - - start_date = models.DateTimeField(auto_now_add=True) - end_date = models.DateTimeField(null=True, blank=True) - - def __str__(self): - return f"{self.owner} - {self.subscription_plan.name} ({self.start_date} to {self.end_date or 'ongoing'})" - - @property - def has_ended(self): - return bool(self.end_date) - - @property - def get_price(self): - return self.custom_subscription_price_per_month or self.subscription_plan.price_per_month or "0.00" - - def end_now(self): - self.end_date = timezone.now() - self.save() - return self - - -class PlanFeatureGroup(models.Model): - name = models.CharField(max_length=50) # E.g. 'invoices' - - -class PlanFeature(models.Model): - """ - Details related to certain features. E.g. "emails sent", we can allow site admins to change prices, units, and customise their - billing - """ - - slug = models.CharField(max_length=100) - stripe_price_id = models.CharField(max_length=100, null=True, blank=True) - description = models.TextField(max_length=500, null=True, blank=True) - - max_limit_per_month = models.IntegerField(null=True, blank=True) - - subscription_plan = models.ForeignKey(SubscriptionPlan, on_delete=models.CASCADE, related_name="features") - group = models.ForeignKey(PlanFeatureGroup, on_delete=models.CASCADE, related_name="features") - - def __str__(self): - return f"{self.slug} - subscription id: {self.subscription_plan_id}" - - -class StripeWebhookEvent(models.Model): - event_id = models.CharField(max_length=100, unique=True) - event_type = models.CharField(max_length=100) # e.g. 'customer.subscription.created' - data = models.JSONField() - raw_event = models.JSONField() - - -class StripeCheckoutSession(OwnerBase): - uuid = models.UUIDField(unique=True, default=uuid4) - - stripe_session_id = models.CharField(max_length=100, unique=True, blank=True, null=True) - plan = models.ForeignKey(SubscriptionPlan, on_delete=models.CASCADE, related_name="checkout_sessions") - features = models.ManyToManyField(PlanFeature, related_name="checkout_sessions") - - -class BillingUsage(OwnerBase): - EVENT_TYPES = ( - ("usage", "Metered Usage"), - # ("storage", "Storage"), - ) - - event_name = models.CharField(max_length=100) # e.g. 'invoices-created' - event_type = models.CharField(max_length=20, choices=EVENT_TYPES, default="usage") - quantity = models.PositiveSmallIntegerField(default=1) # e.g. 1 - - created_at = models.DateTimeField(auto_now_add=True) - - processed_at = models.DateTimeField(null=True, blank=True) - processed = models.BooleanField(default=False) - stripe_unique_usage_identifier = models.CharField(max_length=100, null=True, blank=True) - - def set_processed(self, processed_time): - self.processed = True - self.processed_at = processed_time or timezone_now() - self.save() - return self diff --git a/billing/service/__init__.py b/billing/service/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/billing/service/checkout_completed.py b/billing/service/checkout_completed.py deleted file mode 100644 index 62d90d97f..000000000 --- a/billing/service/checkout_completed.py +++ /dev/null @@ -1,48 +0,0 @@ -import stripe - -from backend.core.utils.calendar import timezone_now -from billing.models import StripeCheckoutSession, StripeWebhookEvent, UserSubscription - - -def checkout_completed(webhook_event: StripeWebhookEvent): - event_data: stripe.checkout.Session = webhook_event.data["object"] - - stripe_session_obj = StripeCheckoutSession.objects.filter( - uuid=event_data.metadata.get("dj_checkout_uuid", "doesn't_exist") if event_data.metadata else "doesn't_exist" - ).first() # type: ignore[misc] - - if not stripe_session_obj: - print("No matching session object found.") - return - - completed_with_session_object(stripe_session_obj, event_data) - - -def completed_with_session_object(stripe_session_obj: StripeCheckoutSession, event_data: stripe.checkout.Session) -> None: - # Fetch current active subscriptions based on the owner (user or organization) - user_current_plans = UserSubscription.filter_by_owner(owner=stripe_session_obj.owner).filter(end_date__isnull=True) - - # Get plan ID from metadata - stripe_plan_id = event_data.metadata.get("dj_subscription_plan_id", None) if event_data.metadata else None - if not stripe_plan_id: - print("No subscription plan ID found in metadata.") - return - - # Cancel existing subscriptions except the one in the metadata - for current_plan in user_current_plans: - if current_plan.subscription_plan.id != stripe_plan_id: # Fix: Using `subscription_plan.id` - stripe.Subscription.modify(current_plan.stripe_subscription_id, cancel_at_period_end=True) # type: ignore[arg-type] - current_plan.end_date = timezone_now() - current_plan.save() - - # Create new subscription if the user doesn't have it - if not user_current_plans.filter(subscription_plan__id=stripe_plan_id).exists(): # Fix: Using `subscription_plan__id` - UserSubscription.objects.create( - owner=stripe_session_obj.owner, - subscription_plan_id=stripe_plan_id, - stripe_subscription_id=event_data.subscription, - ) - - # Expire the checkout session - stripe.checkout.Session.expire(stripe_session_obj.stripe_session_id) # type: ignore[arg-type] - stripe_session_obj.delete() diff --git a/billing/service/entitlements.py b/billing/service/entitlements.py deleted file mode 100644 index cc751f8c6..000000000 --- a/billing/service/entitlements.py +++ /dev/null @@ -1,57 +0,0 @@ -import stripe.entitlements -from django.contrib import messages -from django.core.cache import cache -from django.core.cache.backends.redis import RedisCacheClient -from django.shortcuts import redirect - -from backend.models import User, Organization -from billing.models import StripeWebhookEvent -from billing.service.get_user import get_actor_from_stripe_customer - -cache: RedisCacheClient = cache - - -def entitlements_updated_via_stripe_webhook(webhook_event: StripeWebhookEvent) -> None: - data: stripe.entitlements.ActiveEntitlementSummary = webhook_event.data["object"] - actor = get_actor_from_stripe_customer(data["customer"]) - - if not actor: - print("No actor found for customer.") - return - - # Re-fetch and update the entitlements for the actor (User or Organization) - update_user_entitlements(actor) - - -def update_user_entitlements(actor: User | Organization) -> list[str]: - if not actor.stripe_customer_id: - return [] - - entitlements = stripe.entitlements.ActiveEntitlement.list(customer=actor.stripe_customer_id, limit=25).data - - entitlement_names = [entitlement.lookup_key for entitlement in entitlements] - - actor.entitlements = entitlement_names - actor.save(update_fields=["entitlements"]) - - cache_actor_type = "user" if isinstance(actor, User) else "org" - - cache.set(f"myfinances:entitlements:{cache_actor_type}:{actor.id}", entitlement_names, timeout=3600) - - return entitlement_names - - -def get_entitlements(actor: User | Organization, avoid_cache=False) -> list[str]: - cache_key = "user" if isinstance(actor, User) else "org" - - if not avoid_cache and (cached_entitlements := cache.get(f"myfinances:entitlements:{cache_key}:{actor.id}", default=[])): - return cached_entitlements - return update_user_entitlements(actor) - - -def has_entitlement(actor: User | Organization, entitlement: str) -> bool: - return entitlement in get_entitlements(actor) - - -def has_entitlements(actor: User | Organization, entitlements: list[str]) -> bool: - return all(entitlement in entitlements for entitlement in get_entitlements(actor)) diff --git a/billing/service/get_user.py b/billing/service/get_user.py deleted file mode 100644 index 65c93fec0..000000000 --- a/billing/service/get_user.py +++ /dev/null @@ -1,8 +0,0 @@ -from backend.models import User, Organization - - -def get_actor_from_stripe_customer(stripe_customer_id: str) -> User | Organization | None: - return ( - User.objects.filter(stripe_customer_id=stripe_customer_id).first() - or Organization.objects.filter(stripe_customer_id=stripe_customer_id).first() - ) diff --git a/billing/service/plan_change.py b/billing/service/plan_change.py deleted file mode 100644 index 7154705ef..000000000 --- a/billing/service/plan_change.py +++ /dev/null @@ -1,31 +0,0 @@ -from datetime import datetime -import stripe -from django.db.models import QuerySet -from backend.models import User, Organization -from billing.models import UserSubscription, SubscriptionPlan -from billing.service.entitlements import update_user_entitlements -from billing.service.stripe_customer import get_or_create_customer_id - - -def handle_plan_change(user_subscription: UserSubscription, new_plan: SubscriptionPlan) -> UserSubscription: - """ - Handles plan upgrades or downgrades. - """ - # Cancel the current Stripe subscription if necessary - stripe.Subscription.modify( - user_subscription.stripe_subscription_id, - cancel_at_period_end=False, # Cancels immediately - ) - - # Create a new Stripe subscription for the new plan - new_subscription = stripe.Subscription.create( - customer=user_subscription.owner.stripe_customer_id, - items=[{"price": new_plan.stripe_price_id}], - ) - - # Update the UserSubscription object with new plan and subscription id - user_subscription.subscription_plan = new_plan - user_subscription.stripe_subscription_id = new_subscription.id - user_subscription.save() - - return user_subscription diff --git a/billing/service/price.py b/billing/service/price.py deleted file mode 100644 index 009249a27..000000000 --- a/billing/service/price.py +++ /dev/null @@ -1,8 +0,0 @@ -import stripe - - -def get_price_id_from_lookup_key(lookup_key: str) -> str: - prices = stripe.Price.list(lookup_keys=[lookup_key]) - if prices.data: - return prices.data[0].id # Assuming the lookup key returns one price - raise ValueError(f"Price with lookup key {lookup_key} not found.") diff --git a/billing/service/stripe_customer.py b/billing/service/stripe_customer.py deleted file mode 100644 index a66108958..000000000 --- a/billing/service/stripe_customer.py +++ /dev/null @@ -1,32 +0,0 @@ -from backend.models import User, Organization -import stripe - - -def get_or_create_customer_id(actor: User | Organization) -> str: - if actor.stripe_customer_id: - return actor.stripe_customer_id - - return create_stripe_customer_id(actor) - - -def create_stripe_customer_id(actor: User | Organization) -> str: - if isinstance(actor, User): - customer = stripe.Customer.create( - email=actor.email, - name=actor.get_full_name(), - ) - actor.stripe_customer_id = customer.id - actor.save() - - return customer.id - - else: - customer = stripe.Customer.create( - email=actor.leader.email, - name=actor.name, - ) - - actor.stripe_customer_id = customer.id - actor.save() - - return customer.id diff --git a/billing/service/subscription_ended.py b/billing/service/subscription_ended.py deleted file mode 100644 index 253e013f9..000000000 --- a/billing/service/subscription_ended.py +++ /dev/null @@ -1,39 +0,0 @@ -import stripe - -from backend.core.models import User, Organization -from billing.models import StripeWebhookEvent, UserSubscription - - -def subscription_ended(webhook_event: StripeWebhookEvent) -> None: - event_data: stripe.Subscription = webhook_event.data.object - stripe_customer = event_data.customer - - # Find the user or organization based on the stripe customer - actor = ( - User.objects.filter(stripe_customer_id=stripe_customer).first() - or Organization.objects.filter(stripe_customer_id=stripe_customer).first() - ) - - actor_subscription_plan = None - - if not actor: - # If no user found, try to fetch the subscription plan using the stripe subscription ID - plan = UserSubscription.objects.filter( - stripe_subscription_id=event_data.id, stripe_subscription_id__isnull=False - ).first() # type: ignore[misc] - - if plan: - actor_subscription_plan = plan - actor = plan.owner - else: - print("Error: Could not find user or subscription plan.") - return - - if not actor_subscription_plan: - actor_subscriptions = UserSubscription.filter_by_owner(owner=actor).all() - if not actor_subscriptions: - return - - # Find a subscription plan with the same Stripe subscription ID - if plan_with_same_id := actor_subscriptions.filter(stripe_subscription_id=event_data.id).first(): - plan_with_same_id.end_now() diff --git a/billing/service/subscription_handler.py b/billing/service/subscription_handler.py deleted file mode 100644 index 78381a77a..000000000 --- a/billing/service/subscription_handler.py +++ /dev/null @@ -1,86 +0,0 @@ -import stripe -from typing import Union -from django.utils import timezone - -from backend.models import Organization -from billing.models import UserSubscription, SubscriptionPlan -from django.contrib.auth.models import User - -from billing.service.entitlements import update_user_entitlements - - -def create_subscription(owner: Union[User, Organization], subscription_plan: SubscriptionPlan) -> UserSubscription: - """ - Creates a new Stripe subscription for a user or organization. - - Args: - owner: The user or organization subscribing. - subscription_plan: The plan the owner is subscribing to. - - Returns: - A UserSubscription object representing the subscription. - """ - # Create a new Stripe subscription for the given owner (user or organization) - stripe_subscription = stripe.Subscription.create( - customer=owner.stripe_customer_id, - items=[{"price": subscription_plan.stripe_price_id}], - ) - - # Create the corresponding UserSubscription record in your database - user_subscription = UserSubscription.objects.create( - owner=owner, subscription_plan=subscription_plan, stripe_subscription_id=stripe_subscription.id, start_date=timezone.now() - ) - - # Update user entitlements via Stripe entitlements - update_user_entitlements(owner) - - return user_subscription - - -def cancel_subscription(user_subscription: UserSubscription) -> UserSubscription: - """ - Cancels an active Stripe subscription and updates the local subscription record. - - Args: - user_subscription: The subscription to cancel. - - Returns: - The updated UserSubscription object with the end_date set. - """ - stripe.Subscription.delete(subscription=user_subscription.stripe_subscription_id) - - # Mark the subscription as canceled in your local database - user_subscription.end_date = timezone.now() - user_subscription.save() - - # Update entitlements after cancellation - update_user_entitlements(user_subscription.owner) - - return user_subscription - - -def handle_plan_change(user_subscription: UserSubscription, new_plan: SubscriptionPlan) -> UserSubscription: - """ - Updates the user's Stripe subscription to a new plan, with proration handled automatically. - - Args: - user_subscription: The current UserSubscription to update. - new_plan: The new SubscriptionPlan to switch to. - - Returns: - The updated UserSubscription object with the new plan. - """ - stripe.Subscription.modify( - user_subscription.stripe_subscription_id, - cancel_at_period_end=False, # Cancels the current plan immediately - items=[{"price": new_plan.stripe_price_id}], - ) - - # Update the local subscription model - user_subscription.subscription_plan = new_plan - user_subscription.save() - - # Update entitlements after the plan change - update_user_entitlements(user_subscription.owner) - - return user_subscription diff --git a/billing/service/test.py b/billing/service/test.py deleted file mode 100644 index 927b6893c..000000000 --- a/billing/service/test.py +++ /dev/null @@ -1,38 +0,0 @@ -# import os -# -# import stripe -# from django.urls import reverse -# -# from backend.models import User -# -# user: User = User.objects.first() -# -# # stripe.billing.MeterEvent.create( -# # event_name="invoices_created", -# # payload={"invoices": "250", "stripe_customer_id": user.stripe_customer_id}, -# # # identifier="id" -# # ) -# # -# # stripe.billing.Meter.list_event_summaries( -# # "" -# # ) -# -# # a = stripe.Customer.create( -# # name=user.get_full_name(), -# # email=user.email -# # ) -# # -# # user.stripe_customer_id = a.id -# # user.save() -# -# # print(a) -# -# # stripe.checkout.Session.create( -# # success_url=os.environ.get("SITE_URL", default="http://127.0.0.1:8000") + reverse("api:public:webhooks:receive_global"), -# # line_items=[ -# # { -# # "price": "price_", -# # "quantity": 1 -# # } -# # ] -# # ) diff --git a/billing/signals/__init__.py b/billing/signals/__init__.py deleted file mode 100644 index ccb677602..000000000 --- a/billing/signals/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import migrations, usage, stripe diff --git a/billing/signals/migrations.py b/billing/signals/migrations.py deleted file mode 100644 index 011548902..000000000 --- a/billing/signals/migrations.py +++ /dev/null @@ -1,69 +0,0 @@ -from __future__ import annotations - -import logging - -from django.db.models.signals import post_migrate -from django.dispatch import receiver - -from billing.data.default_usage_plans import default_usage_plans, default_subscription_plans, DefaultFeature, DefaultSubscriptionPlan -from billing.models import PlanFeature, PlanFeatureGroup, SubscriptionPlan - - -# -@receiver(post_migrate) -def update_usage_plans(**kwargs): - subscription_plans: dict = {} - - subscription_plan: DefaultSubscriptionPlan - - for subscription_plan in default_subscription_plans: - if plan := SubscriptionPlan.objects.filter(name=subscription_plan.name).first(): - subscription_plans[plan.name] = plan - else: - subscription_plans[subscription_plan.name] = SubscriptionPlan.objects.create( - name=subscription_plan.name, - description=subscription_plan.description, - price_per_month=subscription_plan.price_per_month, - ) - logging.info(f"Added SubscriptionPlan {subscription_plan.name}") - - for group in default_usage_plans: - group_obj, created = PlanFeatureGroup.objects.get_or_create(name=group.name) - - if created: - logging.info(f"Created group {group.name}") - - item: DefaultFeature - for item in group.items: - existing: PlanFeature = PlanFeature.objects.filter( - slug=item.slug, subscription_plan=SubscriptionPlan.objects.get(name=item.subscription_plan.name) - ).first() - - if existing: - description, old_subscription_plan, max_limit_per_month = ( - existing.description, - existing.subscription_plan, - existing.max_limit_per_month, - ) - - existing.description = item.description - existing.max_limit_per_month = item.max_limit_per_month if item.max_limit_per_month != -1 else None - - if ( - existing.description != description - or (existing.max_limit_per_month == None and max_limit_per_month != -1) - or (existing.max_limit_per_month != None and max_limit_per_month == -1) - or (existing.max_limit_per_month != None and existing.max_limit_per_month != max_limit_per_month) - ): - existing.save() - - logging.info(f"Updated PlanFeature description/limits for {item.slug}") - else: - existing = PlanFeature.objects.create( - group=group_obj, - description=item.description, - slug=item.slug, - max_limit_per_month=item.max_limit_per_month, - subscription_plan=SubscriptionPlan.objects.get(name=item.subscription_plan.name), - ) - logging.info(f"Added PlanFeature {item.slug}") diff --git a/billing/signals/quotas.py b/billing/signals/quotas.py deleted file mode 100644 index d79ba6bd1..000000000 --- a/billing/signals/quotas.py +++ /dev/null @@ -1,15 +0,0 @@ -# from django.db.models.signals import post_save -# from django.dispatch import receiver -# -# from backend.finance.models import Invoice, Usage -# -# -# @receiver(post_save, sender=Invoice) -# def created_invoice(sender, instance: Invoice, **kwargs): -# Usage.objects.create( -# owner=instance.owner, -# feature="invoices-created", -# quantity=1, -# unit="invocations", -# instance_id=instance.id, -# ) diff --git a/billing/signals/stripe/__init__.py b/billing/signals/stripe/__init__.py deleted file mode 100644 index ae73ac8a2..000000000 --- a/billing/signals/stripe/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import webhook_handler diff --git a/billing/signals/stripe/webhook_handler.py b/billing/signals/stripe/webhook_handler.py deleted file mode 100644 index 2b003a783..000000000 --- a/billing/signals/stripe/webhook_handler.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.db.models.signals import post_save -from django.dispatch import receiver -from billing.models import StripeWebhookEvent -from billing.service.checkout_completed import checkout_completed -from billing.service.subscription_ended import subscription_ended -from billing.service.entitlements import entitlements_updated_via_stripe_webhook - - -@receiver(post_save, sender=StripeWebhookEvent) -def stripe_webhook_event_created(sender, instance: StripeWebhookEvent, **kwargs): - match instance.event_type: - case "checkout.session.completed": - checkout_completed(instance) - case "customer.subscription.deleted": - subscription_ended(instance) - case "entitlements.active_entitlement_summary.updated": - entitlements_updated_via_stripe_webhook(instance) - case _: - print(f"Unhandled event type: {instance.event_type}") diff --git a/billing/signals/usage.py b/billing/signals/usage.py deleted file mode 100644 index f67c51a40..000000000 --- a/billing/signals/usage.py +++ /dev/null @@ -1,59 +0,0 @@ -import logging -from datetime import datetime - -import stripe -from django.db.models.signals import post_save -from django.dispatch import receiver - -from backend.finance.models import Invoice -from backend.core.models import User -from billing.models import BillingUsage - -logger = logging.getLogger(__name__) - - -@receiver(post_save, sender=BillingUsage) -def usage_occurred(sender, instance: BillingUsage, created, **kwargs): - if not created or instance.processed: - return - - if instance.event_type != "usage": - return # may add storage at a later point - - if not instance.owner: - print("CANNOT HANDLE ORGS AT THE MOMENT!") - return # todo: cannot handle organisations at the moment - - stripe_customer_id = instance.owner.stripe_customer_id - - if not stripe_customer_id: - print(f"No stripe customer id for actor #{'usr_' if isinstance(instance.owner, User) else 'org_'}{instance.owner.id}") - return # todo - - meter_event = stripe.billing.MeterEvent.create( - event_name=instance.event_name, payload={"value": str(instance.quantity), f"stripe_customer_id": stripe_customer_id} - ) - - if meter_event.created: - instance.stripe_unique_usage_identifier = meter_event.identifier - instance.set_processed(datetime.fromtimestamp(meter_event.created)) - - return - - -@receiver(post_save, sender=Invoice) -def created_invoice(sender, instance: Invoice, created, **kwargs): - if not created: - return - - BillingUsage.objects.create( - owner=instance.owner, - event_name="invoices_created", - ) - - if instance.invoice_recurring_profile: - BillingUsage.objects.create( - owner=instance.owner, - event_name="invoice_schedule_invocations", - ) - return diff --git a/billing/templates/pages/billing/dashboard/all_subscriptions.html b/billing/templates/pages/billing/dashboard/all_subscriptions.html deleted file mode 100644 index 4353101c5..000000000 --- a/billing/templates/pages/billing/dashboard/all_subscriptions.html +++ /dev/null @@ -1,71 +0,0 @@ -
-
-
- All Subscriptions - -
- - - - - - - - - - - - - {% for subscription in all_user_subscriptions|dictsortreversed:"start_date" %} - - - - - - {% if subscription.has_ended %} - - {% else %} - - - {% endif %} - - {% endfor %} - -
StatusPlan NamePriceStartedEndedActions
- {% if subscription.has_ended %} - Ended - {% else %} - Ongoing - {% endif %} - {{ subscription.subscription_plan.name }} - {% if subscription.subscription_plan.price_per_month == -1 %} - {{ subscription.custom_subscription_price_per_month }} - {% else %} - {{ subscription.subscription_plan.price_per_month }} - {% endif %} - {{ subscription.start_date }}{{ subscription.end_date | default_if_none:"" }} - - - -
-
-
\ No newline at end of file diff --git a/billing/templates/pages/billing/dashboard/choose_plan_section.html b/billing/templates/pages/billing/dashboard/choose_plan_section.html deleted file mode 100644 index b6994e4d2..000000000 --- a/billing/templates/pages/billing/dashboard/choose_plan_section.html +++ /dev/null @@ -1,199 +0,0 @@ -
-
-
- Current Subscription Plan: {{ active_subscription.subscription_plan.name | default_if_none:"Not subscribed" }} -
- -
-
- -
-
-

Starter

-

- For individual freelancers or small businesses with less recurring customers -

-
- £5 - /month -
-
    -
  • - - Unlimited Customers -
  • -
  • - - - Unlimited Invoices (first 10 on us!)* - -
  • -
  • - - Basic Customer Onboarding -
  • -
  • - - Emailing (automated, normal & bulk) -
  • -
  • - - File Storage (10 GB included!) -
  • -
  • - - - No Limits * - -
  • -
- {% if active_subscription and active_subscription.subscription_plan.name|lower == "starter" %} - - Your active plan - - {% else %} - - {% endif %} -
-
-

Growth

-

- For small-medium sized businesses that require more features or have a wider customer base -

-
- £15 - /month -
-
    -
  • - Everything in Starter, plus: -
  • -
  • - - Automated Invoice Reminders -
  • -
  • - - Advanced Customer Onboarding -
  • -
  • - - API Access -
  • -
  • - - File Storage (50 GB included!) -
  • -
  • - - - No Limits * - -
  • -
- {% if active_subscription and active_subscription.subscription_plan.name|lower == "growth" %} - - Your active plan - - {% else %} - - {% endif %} -
-
-

Enterprise

-

- For large businesses or if your business needs a bit more - customisation -

-
- - Custom - -
-
    -
  • - - Choose a price that fits your business -
  • -
  • - - - Organization Mode - - - - -
  • -
  • - - Dedicated Customer Success -
  • -
  • - - SLA can be discussed -
  • -
  • - - - Analytics and live updates - - - - -
  • -
  • - - Pre-release notifications -
  • -
- - Contact Us - -
-
- -
- - {% include "pages/billing/dashboard/starter_usages.html" %} - - - {% include "pages/billing/dashboard/growth_usages.html" %} -
-
-
\ No newline at end of file diff --git a/billing/templates/pages/billing/dashboard/dashboard.html b/billing/templates/pages/billing/dashboard/dashboard.html deleted file mode 100644 index d8f6310de..000000000 --- a/billing/templates/pages/billing/dashboard/dashboard.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends base|default:"base/base.html" %} -{% load listfilters %} -{% block content %} - {% load get_first_n_items from listfilters %} -
-
-
-

Billing

- -
-
- {% include "pages/billing/dashboard/choose_plan_section.html" %} - {% include "pages/billing/dashboard/all_subscriptions.html" %} -{% endblock %} diff --git a/billing/templates/pages/billing/dashboard/growth_usages.html b/billing/templates/pages/billing/dashboard/growth_usages.html deleted file mode 100644 index c0e71d280..000000000 --- a/billing/templates/pages/billing/dashboard/growth_usages.html +++ /dev/null @@ -1,137 +0,0 @@ -
-
-
- Growth Plan - Usage-Based Pricing -
- -
- - -
- -
- Invoices -
-
- - - - - - - - - - - - - - - - - - - - - -
QuantityUnit Price
First 80 invoicesIncluded for free
Next 170 invoices£0.04/invoice
Greater than 250 invoices£0.03/invoice
-
-
- - -
- -
- Recurring Invoices -
-
- - - - - - - - - - - - - - - - - - - - - -
QuantityUnit Price
First 100 invocationsIncluded for free
Next 400 invocations£0.07/invocation
Greater than 500 invocations£0.04/invocation
-
-
- - -
- -
- Invoice Reminders -
-
- - - - - - - - - - - - - - - - - - - - - -
QuantityUnit Price
First 400 invocationsIncluded for free
Next 600 invocations£0.02/invocation
Greater than 1000 invocations£0.01/invocation
-
-
- - -
- -
- Advanced Onboarding -
-
- - - - - - - - - - - - - - - - - - - - - -
QuantityUnit Price
First 5 customersIncluded for free
Next 20 customers£0.10/customer
Greater than 25 customers£0.08/customer
-
-
-
-
diff --git a/billing/templates/pages/billing/dashboard/starter_usages.html b/billing/templates/pages/billing/dashboard/starter_usages.html deleted file mode 100644 index 0ae8d737a..000000000 --- a/billing/templates/pages/billing/dashboard/starter_usages.html +++ /dev/null @@ -1,73 +0,0 @@ -
-
-
- Starter Plan - Usage-Based Pricing -
- -
- - -
- -
- Invoices -
-
- - - - - - - - - - - - - - - - - - - - - -
QuantityUnit Price
First 10 invoicesIncluded for free
Next 40 invoices£0.05/invoice
Greater than 50 invoices£0.035/invoice
-
-
- - -
- -
- Recurring Invoices -
-
- - - - - - - - - - - - - - - - - - - - - -
QuantityUnit Price
First 10 invocationsIncluded for free
Next 20 invocations£0.07/invocation
Greater than 30 invocations£0.05/invocation
-
-
-
-
diff --git a/billing/urls.py b/billing/urls.py deleted file mode 100644 index 7fd307871..000000000 --- a/billing/urls.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.urls import path - -from billing.views.dashboard import billing_dashboard_endpoint, all_subscriptions_htmx_endpoint -from billing.views.stripe_misc import customer_client_portal_endpoint -from billing.views.stripe_webhooks import stripe_listener_webhook_endpoint - -from .views.return_urls.success import stripe_success_return_endpoint -from .views.change_plan import change_plan_endpoint - -urlpatterns = [ - path("dashboard/billing/", billing_dashboard_endpoint, name="dashboard"), - path("api/billing/all_subscriptions/", all_subscriptions_htmx_endpoint, name="all_subscriptions"), - # path("dashboard/billing/", RedirectView.as_view(url="/dashboard"), name="dashboard"), - path("api/public/webhooks/receive/payments/stripe/", stripe_listener_webhook_endpoint, name="receive_stripe_webhook"), - path("api/billing/stripe/change_plan/", change_plan_endpoint, name="change_plan"), - path("dashboard/billing/stripe/portal/", customer_client_portal_endpoint, name="stripe_customer_portal"), - path("dashboard/billing/stripe/checkout/response/success/", stripe_success_return_endpoint, name="stripe_checkout_success_response"), - path("dashboard/billing/stripe/checkout/response/failed/", stripe_success_return_endpoint, name="stripe_checkout_failed_response"), -] - -app_name = "billing" diff --git a/billing/views.py b/billing/views.py deleted file mode 100644 index 91ea44a21..000000000 --- a/billing/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/billing/views/__init__.py b/billing/views/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/billing/views/change_plan.py b/billing/views/change_plan.py deleted file mode 100644 index c35735d0a..000000000 --- a/billing/views/change_plan.py +++ /dev/null @@ -1,111 +0,0 @@ -import logging - -import stripe -from django.contrib import messages -from django.db.models import QuerySet -from django.http import HttpResponse -from django.shortcuts import render -from django.urls import reverse - -from backend.decorators import htmx_only, web_require_scopes -from backend.core.models import User -from backend.core.types.requests import WebRequest -from billing.models import SubscriptionPlan, UserSubscription, StripeCheckoutSession -from billing.service.stripe_customer import get_or_create_customer_id - -logger = logging.getLogger(__name__) - - -@web_require_scopes("billing:manage", api=True, htmx=True) -@htmx_only("billing:dashboard") -def change_plan_endpoint(request: WebRequest): - - plan: SubscriptionPlan | None = None - - if plan_filter := request.POST.get("plan_name"): - plan = SubscriptionPlan.objects.filter(name=plan_filter).first() - elif plan_filter := request.POST.get("plan_id"): - plan = SubscriptionPlan.objects.filter(id=plan_filter).first() - - if not plan: - messages.error(request, "Invalid plan") - return render(request, "base/toast.html") - elif plan.price_per_month == -1 or plan.name.lower() == "enterprise": - print("THIS PLAN IS ENTERPRISE, currently not implemented") - messages.error(request, "Invalid plan (not yet implemented)") - return render(request, "base/toast.html") - - users_plans: QuerySet[UserSubscription] = UserSubscription.filter_by_owner(request.actor) - - if plan.price_per_month == 0 and users_plans.exists(): - messages.error( - request, - """ - Unfortunately you have already used up your free trial. Please upgrade to a paid plan to continue. - If you have another query, feel free to book a call with the project lead + founder! - - Book here - - """, - ) - return render(request, "base/toast.html", {"autohide": False}) - - users_active_plans: QuerySet[UserSubscription] = users_plans.filter(end_date__isnull=True) - # if users_active_plans.exists(): - # for active_plan in users_active_plans: - # active_plan.end_date = timezone_now() - # active_plan.save() - - line_items = [{"adjustable_quantity": {"enabled": False}, "quantity": 1, "price": plan.stripe_price_id}] # type: ignore - - checkout_session_django_object = ( - StripeCheckoutSession.objects.create(user=request.actor, plan=plan) - if isinstance(request.actor, User) - else StripeCheckoutSession.objects.create(organization=request.actor, plan=plan) - ) - - for feature in plan.features.all(): - if not feature.stripe_price_id: - continue - - checkout_session_django_object.features.add(feature) - - line_items.append( - { - # "adjustable_quantity": { - # "enabled": False - # }, - "price": feature.stripe_price_id, - # "quantity": 1, - } - ) - - customer_id = get_or_create_customer_id(request.actor) - - if isinstance(request.actor, User): - customer_email = request.actor.email - else: - customer_email = request.actor.leader.email - - checkout_session = stripe.checkout.Session.create( - customer=customer_id, - customer_email=customer_email if not customer_email else None, # type: ignore[arg-type] - line_items=line_items, # type: ignore[arg-type] - mode="subscription", - # return_url="http://127.0.0.1:8000" + reverse("billing:stripe_checkout_failed_response"), - cancel_url=request.build_absolute_uri(reverse("billing:dashboard")), - success_url=request.build_absolute_uri(reverse("billing:stripe_checkout_success_response")), - metadata={"dj_checkout_uuid": checkout_session_django_object.uuid, "dj_subscription_plan_id": str(plan.id)}, - saved_payment_method_options={"payment_method_save": "enabled"}, - ) - - checkout_session_django_object.stripe_session_id = checkout_session.id - - checkout_session_django_object.save() - - # UserSubscription.objects.create(owner=request.actor, subscription_plan=plan) - messages.success(request, "Great! Redirecting you to stripe now!") - r = HttpResponse(status=200) - r["HX-Redirect"] = str(checkout_session.url) - return r diff --git a/billing/views/dashboard.py b/billing/views/dashboard.py deleted file mode 100644 index f7db39ed8..000000000 --- a/billing/views/dashboard.py +++ /dev/null @@ -1,50 +0,0 @@ -from django.shortcuts import render - -from backend.decorators import web_require_scopes -from billing.models import UserSubscription, SubscriptionPlan -from backend.core.types.requests import WebRequest - - -@web_require_scopes("billing:manage", api=True, htmx=True) -def billing_dashboard_endpoint(request: WebRequest): - context: dict = {} - - subscriptions = UserSubscription.filter_by_owner(request.actor).select_related("subscription_plan").all() - all_subscription_plans = SubscriptionPlan.objects.all() - - if subscriptions.exists(): - context["free_plan_available"] = True - - context.update( - { - "active_subscription": subscriptions.filter(end_date__isnull=True).first(), - "all_user_subscriptions": subscriptions, - "all_subscription_plans": all_subscription_plans, - } - ) - - return render( - request, - "pages/billing/dashboard/dashboard.html", - context, - ) - - -@web_require_scopes("billing:manage", api=True, htmx=True) -def all_subscriptions_htmx_endpoint(request: WebRequest): - context: dict = {} - - subscriptions = UserSubscription.filter_by_owner(request.actor).select_related("subscription_plan").all() - - context.update( - { - "active_subscription": subscriptions.filter(end_date__isnull=True).first(), - "all_user_subscriptions": subscriptions, - } - ) - - return render( - request, - "pages/billing/dashboard/all_subscriptions.html", - context, - ) diff --git a/billing/views/return_urls/failed.py b/billing/views/return_urls/failed.py deleted file mode 100644 index c03d4a888..000000000 --- a/billing/views/return_urls/failed.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.contrib import messages -from django.shortcuts import redirect - -from backend.core.types.requests import WebRequest - - -def stripe_failed_return_endpoint(request: WebRequest): - messages.warning(request, "FAILED RESPONSE") - return redirect("billing:dashboard") diff --git a/billing/views/return_urls/success.py b/billing/views/return_urls/success.py deleted file mode 100644 index dd0d328a8..000000000 --- a/billing/views/return_urls/success.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.shortcuts import redirect - -from backend.core.types.requests import WebRequest - - -def stripe_success_return_endpoint(request: WebRequest): - return redirect("billing:dashboard") diff --git a/billing/views/stripe_misc.py b/billing/views/stripe_misc.py deleted file mode 100644 index c7d0905f8..000000000 --- a/billing/views/stripe_misc.py +++ /dev/null @@ -1,30 +0,0 @@ -import stripe -from django.http import HttpResponseRedirect, HttpResponse -from django.urls import reverse, resolve, NoReverseMatch - -from backend.decorators import web_require_scopes -from backend.core.types.requests import WebRequest - -from billing.service.stripe_customer import get_or_create_customer_id - - -@web_require_scopes("billing:manage", api=True, htmx=True) -def customer_client_portal_endpoint(request: WebRequest): - if NEXT := request.GET.get("back"): - try: - resolve(NEXT) - except NoReverseMatch: - NEXT = None - - customer_id = get_or_create_customer_id(request.actor) - - stripe_resp = stripe.billing_portal.Session.create( - customer=customer_id, return_url=request.build_absolute_uri(NEXT or reverse("dashboard")) - ) - - if request.htmx: - response = HttpResponse(status=200) - response["HX-Redirect"] = stripe_resp.url - return response - - return HttpResponseRedirect(stripe_resp.url) diff --git a/billing/views/stripe_webhooks.py b/billing/views/stripe_webhooks.py deleted file mode 100644 index cb058232d..000000000 --- a/billing/views/stripe_webhooks.py +++ /dev/null @@ -1,33 +0,0 @@ -import stripe -from rest_framework.permissions import AllowAny -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.decorators import api_view, authentication_classes, permission_classes -from billing.billing_settings import STRIPE_WEBHOOK_ENDPOINT_SECRET -from billing.models import StripeWebhookEvent -from django.views.decorators.csrf import csrf_exempt - - -@api_view(["POST"]) -@authentication_classes([]) # No auth required for webhooks -@permission_classes([AllowAny]) -@csrf_exempt -def stripe_listener_webhook_endpoint(request: Request): - payload = request.body - sig_header = request.META.get("HTTP_STRIPE_SIGNATURE", "") - - try: - event = stripe.Webhook.construct_event(payload, sig_header, STRIPE_WEBHOOK_ENDPOINT_SECRET) - print(f"Webhook received: {event['type']}") - except ValueError as error: - print(f"Invalid payload: {error}") - return Response(status=400) - except stripe.error.SignatureVerificationError as error: - print(f"Invalid signature: {error}") - return Response(status=400) - - # Store event in database - StripeWebhookEvent.objects.create(event_id=event.id, event_type=event["type"], data=event["data"], raw_event=event) - - # Call specific event handler (signal) - return Response(status=200) diff --git a/frontend/templates/pages/dashboard.html b/frontend/templates/pages/dashboard.html index 45d3b33b1..31ccd60b5 100644 --- a/frontend/templates/pages/dashboard.html +++ b/frontend/templates/pages/dashboard.html @@ -1,4 +1,4 @@ -{% extends base|default:"core/base.html" %} +{% extends base|default:"core/base/base.html" %} {% block content %}
diff --git a/package-lock.json b/package-lock.json index 2e9a267ce..7e3f97cb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,15 +10,19 @@ "daisyui": "^4.7.3", "htmx.org": "^1.9.12", "hyperscript.org": "^0.9.12", - "jquery": "^3.7.1", - "tailwindcss": "^3.4.1" + "jquery": "^3.7.1" }, "devDependencies": { "@fortawesome/fontawesome-free": "^6.5.1", + "autoprefixer": "^10.4.20", + "css-loader": "^7.1.2", "daisyui": "^4.9.0", + "mini-css-extract-plugin": "^2.9.2", "npm-check-updates": "16.14.17", - "tailwindcss": "^3.4.1", - "webpack": "^5.94.0", + "postcss": "^8.4.49", + "postcss-loader": "^8.1.1", + "tailwindcss": "^3.4.17", + "webpack": "^5.97.1", "webpack-bundle-tracker": "^3.1.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.3", @@ -37,6 +41,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -221,39 +248,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@npmcli/fs/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/fs/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@npmcli/git": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", @@ -282,33 +276,6 @@ "node": ">=12" } }, - "node_modules/@npmcli/git/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/git/node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@npmcli/git/node_modules/which": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", @@ -324,12 +291,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@npmcli/git/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@npmcli/installed-package-contents": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", @@ -642,10 +603,30 @@ "@types/node": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, "node_modules/@types/express": { @@ -803,148 +784,148 @@ "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==" }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -1024,9 +1005,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "bin": { "acorn": "bin/acorn" }, @@ -1034,15 +1015,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1302,6 +1274,43 @@ "node": ">=8" } }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1401,9 +1410,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "dev": true, "funding": [ { @@ -1420,10 +1429,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -1446,39 +1455,6 @@ "semver": "^7.0.0" } }, - "node_modules/builtins/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/builtins/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/builtins/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -1563,6 +1539,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1573,9 +1558,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001636", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", - "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "dev": true, "funding": [ { @@ -1824,6 +1809,32 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1838,6 +1849,41 @@ "node": ">= 8" } }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, "node_modules/css-selector-tokenizer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz", @@ -2055,9 +2101,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.661", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.661.tgz", - "integrity": "sha512-AFg4wDHSOk5F+zA8aR+SVIOabu7m0e7BiJnigCvPXzIGy731XENw/lmNxTySpVFtkFEy+eyt4oHhh5FF3NjQNw==", + "version": "1.5.75", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz", + "integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==", "dev": true }, "node_modules/emoji-regex": { @@ -2125,6 +2171,15 @@ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "dev": true }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -2153,9 +2208,9 @@ "dev": true }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "engines": { "node": ">=6" @@ -2378,6 +2433,12 @@ "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "dev": true + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -2538,6 +2599,19 @@ "node": ">=10" } }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -3096,6 +3170,18 @@ "node": ">=0.10.0" } }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/ignore": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", @@ -3117,6 +3203,31 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -3213,6 +3324,12 @@ "node": ">= 10" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -3465,9 +3582,9 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -3484,6 +3601,12 @@ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -3581,6 +3704,24 @@ "shell-quote": "^1.8.1" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -3818,36 +3959,109 @@ "node": ">=6" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "node_modules/mini-css-extract-plugin": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">= 12.13.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" } }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/mini-css-extract-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0" - } - }, + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -4359,33 +4573,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-gyp/node_modules/ssri": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", @@ -4429,9 +4616,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, "node_modules/nopt": { @@ -4485,39 +4672,6 @@ "node": ">=12" } }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4527,6 +4681,15 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-bundled": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", @@ -5044,18 +5207,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-check-updates/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/npm-check-updates/node_modules/mimic-response": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", @@ -5194,21 +5345,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-check-updates/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/npm-check-updates/node_modules/semver-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", @@ -5333,12 +5469,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-check-updates/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/npm-install-checks": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", @@ -5351,39 +5481,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm-install-checks/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-install-checks/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-install-checks/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/npm-normalize-package-bin": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", @@ -5429,39 +5526,6 @@ "node": ">=12" } }, - "node_modules/npm-package-arg/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-package-arg/node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-package-arg/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/npm-packlist": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", @@ -5489,39 +5553,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm-pick-manifest/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-pick-manifest/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm-pick-manifest/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/npm-registry-fetch": { "version": "14.0.5", "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", @@ -5775,6 +5806,18 @@ "node": ">=8" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-github-url": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz", @@ -5787,6 +5830,30 @@ "node": ">=0.10.0" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -5870,9 +5937,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "node_modules/pirates": { @@ -5897,9 +5964,9 @@ } }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "dev": true, "funding": [ { @@ -5917,8 +5984,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -5960,29 +6027,138 @@ "postcss": "^8.4.21" } }, - "node_modules/postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.11" + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" }, "engines": { - "node": ">=12.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" }, "peerDependencies": { "postcss": "^8.2.14" } }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -6559,6 +6735,18 @@ "node": ">=10" } }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/semver-utils": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/semver-utils/-/semver-utils-1.1.4.tgz", @@ -6907,9 +7095,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -7189,14 +7377,14 @@ } }, "node_modules/sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "7.1.6", + "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", @@ -7207,7 +7395,7 @@ "sucrase-node": "bin/sucrase-node" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/sucrase/node_modules/commander": { @@ -7219,50 +7407,6 @@ "node": ">= 6" } }, - "node_modules/sucrase/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sucrase/node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/sucrase/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/sucrase/node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -7276,33 +7420,33 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", - "chokidar": "^3.5.3", + "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.3.0", + "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.19.1", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", @@ -7312,15 +7456,6 @@ "node": ">=14.0.0" } }, - "node_modules/tailwindcss/node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/tailwindcss/node_modules/postcss-load-config": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", @@ -7356,13 +7491,17 @@ } } }, - "node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, "engines": { - "node": ">=14" + "node": ">=4" } }, "node_modules/tapable": { @@ -7529,6 +7668,12 @@ "node": ">=0.6" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -7643,9 +7788,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -7662,8 +7807,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -7759,18 +7904,18 @@ } }, "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", diff --git a/package.json b/package.json index 6bee665b5..e8600574f 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,15 @@ }, "devDependencies": { "@fortawesome/fontawesome-free": "^6.5.1", + "autoprefixer": "^10.4.20", + "css-loader": "^7.1.2", "daisyui": "^4.9.0", + "mini-css-extract-plugin": "^2.9.2", "npm-check-updates": "16.14.17", - "tailwindcss": "^3.4.1", - "webpack": "^5.94.0", + "postcss": "^8.4.49", + "postcss-loader": "^8.1.1", + "tailwindcss": "^3.4.17", + "webpack": "^5.97.1", "webpack-bundle-tracker": "^3.1.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.3", @@ -25,7 +30,6 @@ "daisyui": "^4.7.3", "htmx.org": "^1.9.12", "hyperscript.org": "^0.9.12", - "jquery": "^3.7.1", - "tailwindcss": "^3.4.1" + "jquery": "^3.7.1" } } diff --git a/poetry.lock b/poetry.lock index e920c78e3..f08cbade9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -81,19 +81,19 @@ files = [ [[package]] name = "attrs" -version = "24.2.0" +version = "24.3.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, + {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, + {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, ] [package.extras] 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", "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 (<24.7)"] 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"] @@ -213,13 +213,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "boto3-stubs" -version = "1.35.76" -description = "Type annotations for boto3 1.35.76 generated with mypy-boto3-builder 8.5.0" +version = "1.35.85" +description = "Type annotations for boto3 1.35.85 generated with mypy-boto3-builder 8.6.4" optional = false python-versions = ">=3.8" files = [ - {file = "boto3_stubs-1.35.76-py3-none-any.whl", hash = "sha256:882f69f02cca48176fa3adf7f354fe64a65268423c9696871d0f4d098af35431"}, - {file = "boto3_stubs-1.35.76.tar.gz", hash = "sha256:32109b6a0c9720bf7c2e389655479c6dab4ee33c622e2cf2746c9e5ec527bae3"}, + {file = "boto3_stubs-1.35.85-py3-none-any.whl", hash = "sha256:c3c1709603cb9d0fba4667b8408847f05b8f0b92bb74e88e0e97571cb6dd7745"}, + {file = "boto3_stubs-1.35.85.tar.gz", hash = "sha256:c949abdba605dec649cfceab95f573c8fbce575ed23d8522e965b9eb6da4eeba"}, ] [package.dependencies] @@ -238,7 +238,7 @@ accessanalyzer = ["mypy-boto3-accessanalyzer (>=1.35.0,<1.36.0)"] account = ["mypy-boto3-account (>=1.35.0,<1.36.0)"] acm = ["mypy-boto3-acm (>=1.35.0,<1.36.0)"] acm-pca = ["mypy-boto3-acm-pca (>=1.35.0,<1.36.0)"] -all = ["mypy-boto3-accessanalyzer (>=1.35.0,<1.36.0)", "mypy-boto3-account (>=1.35.0,<1.36.0)", "mypy-boto3-acm (>=1.35.0,<1.36.0)", "mypy-boto3-acm-pca (>=1.35.0,<1.36.0)", "mypy-boto3-amp (>=1.35.0,<1.36.0)", "mypy-boto3-amplify (>=1.35.0,<1.36.0)", "mypy-boto3-amplifybackend (>=1.35.0,<1.36.0)", "mypy-boto3-amplifyuibuilder (>=1.35.0,<1.36.0)", "mypy-boto3-apigateway (>=1.35.0,<1.36.0)", "mypy-boto3-apigatewaymanagementapi (>=1.35.0,<1.36.0)", "mypy-boto3-apigatewayv2 (>=1.35.0,<1.36.0)", "mypy-boto3-appconfig (>=1.35.0,<1.36.0)", "mypy-boto3-appconfigdata (>=1.35.0,<1.36.0)", "mypy-boto3-appfabric (>=1.35.0,<1.36.0)", "mypy-boto3-appflow (>=1.35.0,<1.36.0)", "mypy-boto3-appintegrations (>=1.35.0,<1.36.0)", "mypy-boto3-application-autoscaling (>=1.35.0,<1.36.0)", "mypy-boto3-application-insights (>=1.35.0,<1.36.0)", "mypy-boto3-application-signals (>=1.35.0,<1.36.0)", "mypy-boto3-applicationcostprofiler (>=1.35.0,<1.36.0)", "mypy-boto3-appmesh (>=1.35.0,<1.36.0)", "mypy-boto3-apprunner (>=1.35.0,<1.36.0)", "mypy-boto3-appstream (>=1.35.0,<1.36.0)", "mypy-boto3-appsync (>=1.35.0,<1.36.0)", "mypy-boto3-apptest (>=1.35.0,<1.36.0)", "mypy-boto3-arc-zonal-shift (>=1.35.0,<1.36.0)", "mypy-boto3-artifact (>=1.35.0,<1.36.0)", "mypy-boto3-athena (>=1.35.0,<1.36.0)", "mypy-boto3-auditmanager (>=1.35.0,<1.36.0)", "mypy-boto3-autoscaling (>=1.35.0,<1.36.0)", "mypy-boto3-autoscaling-plans (>=1.35.0,<1.36.0)", "mypy-boto3-b2bi (>=1.35.0,<1.36.0)", "mypy-boto3-backup (>=1.35.0,<1.36.0)", "mypy-boto3-backup-gateway (>=1.35.0,<1.36.0)", "mypy-boto3-batch (>=1.35.0,<1.36.0)", "mypy-boto3-bcm-data-exports (>=1.35.0,<1.36.0)", "mypy-boto3-bcm-pricing-calculator (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-agent (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-agent-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-data-automation (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-data-automation-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-billing (>=1.35.0,<1.36.0)", "mypy-boto3-billingconductor (>=1.35.0,<1.36.0)", "mypy-boto3-braket (>=1.35.0,<1.36.0)", "mypy-boto3-budgets (>=1.35.0,<1.36.0)", "mypy-boto3-ce (>=1.35.0,<1.36.0)", "mypy-boto3-chatbot (>=1.35.0,<1.36.0)", "mypy-boto3-chime (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-identity (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-meetings (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-messaging (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-voice (>=1.35.0,<1.36.0)", "mypy-boto3-cleanrooms (>=1.35.0,<1.36.0)", "mypy-boto3-cleanroomsml (>=1.35.0,<1.36.0)", "mypy-boto3-cloud9 (>=1.35.0,<1.36.0)", "mypy-boto3-cloudcontrol (>=1.35.0,<1.36.0)", "mypy-boto3-clouddirectory (>=1.35.0,<1.36.0)", "mypy-boto3-cloudformation (>=1.35.0,<1.36.0)", "mypy-boto3-cloudfront (>=1.35.0,<1.36.0)", "mypy-boto3-cloudfront-keyvaluestore (>=1.35.0,<1.36.0)", "mypy-boto3-cloudhsm (>=1.35.0,<1.36.0)", "mypy-boto3-cloudhsmv2 (>=1.35.0,<1.36.0)", "mypy-boto3-cloudsearch (>=1.35.0,<1.36.0)", "mypy-boto3-cloudsearchdomain (>=1.35.0,<1.36.0)", "mypy-boto3-cloudtrail (>=1.35.0,<1.36.0)", "mypy-boto3-cloudtrail-data (>=1.35.0,<1.36.0)", "mypy-boto3-cloudwatch (>=1.35.0,<1.36.0)", "mypy-boto3-codeartifact (>=1.35.0,<1.36.0)", "mypy-boto3-codebuild (>=1.35.0,<1.36.0)", "mypy-boto3-codecatalyst (>=1.35.0,<1.36.0)", "mypy-boto3-codecommit (>=1.35.0,<1.36.0)", "mypy-boto3-codeconnections (>=1.35.0,<1.36.0)", "mypy-boto3-codedeploy (>=1.35.0,<1.36.0)", "mypy-boto3-codeguru-reviewer (>=1.35.0,<1.36.0)", "mypy-boto3-codeguru-security (>=1.35.0,<1.36.0)", "mypy-boto3-codeguruprofiler (>=1.35.0,<1.36.0)", "mypy-boto3-codepipeline (>=1.35.0,<1.36.0)", "mypy-boto3-codestar-connections (>=1.35.0,<1.36.0)", "mypy-boto3-codestar-notifications (>=1.35.0,<1.36.0)", "mypy-boto3-cognito-identity (>=1.35.0,<1.36.0)", "mypy-boto3-cognito-idp (>=1.35.0,<1.36.0)", "mypy-boto3-cognito-sync (>=1.35.0,<1.36.0)", "mypy-boto3-comprehend (>=1.35.0,<1.36.0)", "mypy-boto3-comprehendmedical (>=1.35.0,<1.36.0)", "mypy-boto3-compute-optimizer (>=1.35.0,<1.36.0)", "mypy-boto3-config (>=1.35.0,<1.36.0)", "mypy-boto3-connect (>=1.35.0,<1.36.0)", "mypy-boto3-connect-contact-lens (>=1.35.0,<1.36.0)", "mypy-boto3-connectcampaigns (>=1.35.0,<1.36.0)", "mypy-boto3-connectcampaignsv2 (>=1.35.0,<1.36.0)", "mypy-boto3-connectcases (>=1.35.0,<1.36.0)", "mypy-boto3-connectparticipant (>=1.35.0,<1.36.0)", "mypy-boto3-controlcatalog (>=1.35.0,<1.36.0)", "mypy-boto3-controltower (>=1.35.0,<1.36.0)", "mypy-boto3-cost-optimization-hub (>=1.35.0,<1.36.0)", "mypy-boto3-cur (>=1.35.0,<1.36.0)", "mypy-boto3-customer-profiles (>=1.35.0,<1.36.0)", "mypy-boto3-databrew (>=1.35.0,<1.36.0)", "mypy-boto3-dataexchange (>=1.35.0,<1.36.0)", "mypy-boto3-datapipeline (>=1.35.0,<1.36.0)", "mypy-boto3-datasync (>=1.35.0,<1.36.0)", "mypy-boto3-datazone (>=1.35.0,<1.36.0)", "mypy-boto3-dax (>=1.35.0,<1.36.0)", "mypy-boto3-deadline (>=1.35.0,<1.36.0)", "mypy-boto3-detective (>=1.35.0,<1.36.0)", "mypy-boto3-devicefarm (>=1.35.0,<1.36.0)", "mypy-boto3-devops-guru (>=1.35.0,<1.36.0)", "mypy-boto3-directconnect (>=1.35.0,<1.36.0)", "mypy-boto3-discovery (>=1.35.0,<1.36.0)", "mypy-boto3-dlm (>=1.35.0,<1.36.0)", "mypy-boto3-dms (>=1.35.0,<1.36.0)", "mypy-boto3-docdb (>=1.35.0,<1.36.0)", "mypy-boto3-docdb-elastic (>=1.35.0,<1.36.0)", "mypy-boto3-drs (>=1.35.0,<1.36.0)", "mypy-boto3-ds (>=1.35.0,<1.36.0)", "mypy-boto3-ds-data (>=1.35.0,<1.36.0)", "mypy-boto3-dsql (>=1.35.0,<1.36.0)", "mypy-boto3-dynamodb (>=1.35.0,<1.36.0)", "mypy-boto3-dynamodbstreams (>=1.35.0,<1.36.0)", "mypy-boto3-ebs (>=1.35.0,<1.36.0)", "mypy-boto3-ec2 (>=1.35.0,<1.36.0)", "mypy-boto3-ec2-instance-connect (>=1.35.0,<1.36.0)", "mypy-boto3-ecr (>=1.35.0,<1.36.0)", "mypy-boto3-ecr-public (>=1.35.0,<1.36.0)", "mypy-boto3-ecs (>=1.35.0,<1.36.0)", "mypy-boto3-efs (>=1.35.0,<1.36.0)", "mypy-boto3-eks (>=1.35.0,<1.36.0)", "mypy-boto3-eks-auth (>=1.35.0,<1.36.0)", "mypy-boto3-elastic-inference (>=1.35.0,<1.36.0)", "mypy-boto3-elasticache (>=1.35.0,<1.36.0)", "mypy-boto3-elasticbeanstalk (>=1.35.0,<1.36.0)", "mypy-boto3-elastictranscoder (>=1.35.0,<1.36.0)", "mypy-boto3-elb (>=1.35.0,<1.36.0)", "mypy-boto3-elbv2 (>=1.35.0,<1.36.0)", "mypy-boto3-emr (>=1.35.0,<1.36.0)", "mypy-boto3-emr-containers (>=1.35.0,<1.36.0)", "mypy-boto3-emr-serverless (>=1.35.0,<1.36.0)", "mypy-boto3-entityresolution (>=1.35.0,<1.36.0)", "mypy-boto3-es (>=1.35.0,<1.36.0)", "mypy-boto3-events (>=1.35.0,<1.36.0)", "mypy-boto3-evidently (>=1.35.0,<1.36.0)", "mypy-boto3-finspace (>=1.35.0,<1.36.0)", "mypy-boto3-finspace-data (>=1.35.0,<1.36.0)", "mypy-boto3-firehose (>=1.35.0,<1.36.0)", "mypy-boto3-fis (>=1.35.0,<1.36.0)", "mypy-boto3-fms (>=1.35.0,<1.36.0)", "mypy-boto3-forecast (>=1.35.0,<1.36.0)", "mypy-boto3-forecastquery (>=1.35.0,<1.36.0)", "mypy-boto3-frauddetector (>=1.35.0,<1.36.0)", "mypy-boto3-freetier (>=1.35.0,<1.36.0)", "mypy-boto3-fsx (>=1.35.0,<1.36.0)", "mypy-boto3-gamelift (>=1.35.0,<1.36.0)", "mypy-boto3-geo-maps (>=1.35.0,<1.36.0)", "mypy-boto3-geo-places (>=1.35.0,<1.36.0)", "mypy-boto3-geo-routes (>=1.35.0,<1.36.0)", "mypy-boto3-glacier (>=1.35.0,<1.36.0)", "mypy-boto3-globalaccelerator (>=1.35.0,<1.36.0)", "mypy-boto3-glue (>=1.35.0,<1.36.0)", "mypy-boto3-grafana (>=1.35.0,<1.36.0)", "mypy-boto3-greengrass (>=1.35.0,<1.36.0)", "mypy-boto3-greengrassv2 (>=1.35.0,<1.36.0)", "mypy-boto3-groundstation (>=1.35.0,<1.36.0)", "mypy-boto3-guardduty (>=1.35.0,<1.36.0)", "mypy-boto3-health (>=1.35.0,<1.36.0)", "mypy-boto3-healthlake (>=1.35.0,<1.36.0)", "mypy-boto3-iam (>=1.35.0,<1.36.0)", "mypy-boto3-identitystore (>=1.35.0,<1.36.0)", "mypy-boto3-imagebuilder (>=1.35.0,<1.36.0)", "mypy-boto3-importexport (>=1.35.0,<1.36.0)", "mypy-boto3-inspector (>=1.35.0,<1.36.0)", "mypy-boto3-inspector-scan (>=1.35.0,<1.36.0)", "mypy-boto3-inspector2 (>=1.35.0,<1.36.0)", "mypy-boto3-internetmonitor (>=1.35.0,<1.36.0)", "mypy-boto3-invoicing (>=1.35.0,<1.36.0)", "mypy-boto3-iot (>=1.35.0,<1.36.0)", "mypy-boto3-iot-data (>=1.35.0,<1.36.0)", "mypy-boto3-iot-jobs-data (>=1.35.0,<1.36.0)", "mypy-boto3-iot1click-devices (>=1.35.0,<1.36.0)", "mypy-boto3-iot1click-projects (>=1.35.0,<1.36.0)", "mypy-boto3-iotanalytics (>=1.35.0,<1.36.0)", "mypy-boto3-iotdeviceadvisor (>=1.35.0,<1.36.0)", "mypy-boto3-iotevents (>=1.35.0,<1.36.0)", "mypy-boto3-iotevents-data (>=1.35.0,<1.36.0)", "mypy-boto3-iotfleethub (>=1.35.0,<1.36.0)", "mypy-boto3-iotfleetwise (>=1.35.0,<1.36.0)", "mypy-boto3-iotsecuretunneling (>=1.35.0,<1.36.0)", "mypy-boto3-iotsitewise (>=1.35.0,<1.36.0)", "mypy-boto3-iotthingsgraph (>=1.35.0,<1.36.0)", "mypy-boto3-iottwinmaker (>=1.35.0,<1.36.0)", "mypy-boto3-iotwireless (>=1.35.0,<1.36.0)", "mypy-boto3-ivs (>=1.35.0,<1.36.0)", "mypy-boto3-ivs-realtime (>=1.35.0,<1.36.0)", "mypy-boto3-ivschat (>=1.35.0,<1.36.0)", "mypy-boto3-kafka (>=1.35.0,<1.36.0)", "mypy-boto3-kafkaconnect (>=1.35.0,<1.36.0)", "mypy-boto3-kendra (>=1.35.0,<1.36.0)", "mypy-boto3-kendra-ranking (>=1.35.0,<1.36.0)", "mypy-boto3-keyspaces (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-archived-media (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-media (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-signaling (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.35.0,<1.36.0)", "mypy-boto3-kinesisanalytics (>=1.35.0,<1.36.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.35.0,<1.36.0)", "mypy-boto3-kinesisvideo (>=1.35.0,<1.36.0)", "mypy-boto3-kms (>=1.35.0,<1.36.0)", "mypy-boto3-lakeformation (>=1.35.0,<1.36.0)", "mypy-boto3-lambda (>=1.35.0,<1.36.0)", "mypy-boto3-launch-wizard (>=1.35.0,<1.36.0)", "mypy-boto3-lex-models (>=1.35.0,<1.36.0)", "mypy-boto3-lex-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-lexv2-models (>=1.35.0,<1.36.0)", "mypy-boto3-lexv2-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-license-manager (>=1.35.0,<1.36.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.35.0,<1.36.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.35.0,<1.36.0)", "mypy-boto3-lightsail (>=1.35.0,<1.36.0)", "mypy-boto3-location (>=1.35.0,<1.36.0)", "mypy-boto3-logs (>=1.35.0,<1.36.0)", "mypy-boto3-lookoutequipment (>=1.35.0,<1.36.0)", "mypy-boto3-lookoutmetrics (>=1.35.0,<1.36.0)", "mypy-boto3-lookoutvision (>=1.35.0,<1.36.0)", "mypy-boto3-m2 (>=1.35.0,<1.36.0)", "mypy-boto3-machinelearning (>=1.35.0,<1.36.0)", "mypy-boto3-macie2 (>=1.35.0,<1.36.0)", "mypy-boto3-mailmanager (>=1.35.0,<1.36.0)", "mypy-boto3-managedblockchain (>=1.35.0,<1.36.0)", "mypy-boto3-managedblockchain-query (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-agreement (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-catalog (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-deployment (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-entitlement (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-reporting (>=1.35.0,<1.36.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.35.0,<1.36.0)", "mypy-boto3-mediaconnect (>=1.35.0,<1.36.0)", "mypy-boto3-mediaconvert (>=1.35.0,<1.36.0)", "mypy-boto3-medialive (>=1.35.0,<1.36.0)", "mypy-boto3-mediapackage (>=1.35.0,<1.36.0)", "mypy-boto3-mediapackage-vod (>=1.35.0,<1.36.0)", "mypy-boto3-mediapackagev2 (>=1.35.0,<1.36.0)", "mypy-boto3-mediastore (>=1.35.0,<1.36.0)", "mypy-boto3-mediastore-data (>=1.35.0,<1.36.0)", "mypy-boto3-mediatailor (>=1.35.0,<1.36.0)", "mypy-boto3-medical-imaging (>=1.35.0,<1.36.0)", "mypy-boto3-memorydb (>=1.35.0,<1.36.0)", "mypy-boto3-meteringmarketplace (>=1.35.0,<1.36.0)", "mypy-boto3-mgh (>=1.35.0,<1.36.0)", "mypy-boto3-mgn (>=1.35.0,<1.36.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.35.0,<1.36.0)", "mypy-boto3-migrationhub-config (>=1.35.0,<1.36.0)", "mypy-boto3-migrationhuborchestrator (>=1.35.0,<1.36.0)", "mypy-boto3-migrationhubstrategy (>=1.35.0,<1.36.0)", "mypy-boto3-mq (>=1.35.0,<1.36.0)", "mypy-boto3-mturk (>=1.35.0,<1.36.0)", "mypy-boto3-mwaa (>=1.35.0,<1.36.0)", "mypy-boto3-neptune (>=1.35.0,<1.36.0)", "mypy-boto3-neptune-graph (>=1.35.0,<1.36.0)", "mypy-boto3-neptunedata (>=1.35.0,<1.36.0)", "mypy-boto3-network-firewall (>=1.35.0,<1.36.0)", "mypy-boto3-networkflowmonitor (>=1.35.0,<1.36.0)", "mypy-boto3-networkmanager (>=1.35.0,<1.36.0)", "mypy-boto3-networkmonitor (>=1.35.0,<1.36.0)", "mypy-boto3-notifications (>=1.35.0,<1.36.0)", "mypy-boto3-notificationscontacts (>=1.35.0,<1.36.0)", "mypy-boto3-oam (>=1.35.0,<1.36.0)", "mypy-boto3-observabilityadmin (>=1.35.0,<1.36.0)", "mypy-boto3-omics (>=1.35.0,<1.36.0)", "mypy-boto3-opensearch (>=1.35.0,<1.36.0)", "mypy-boto3-opensearchserverless (>=1.35.0,<1.36.0)", "mypy-boto3-opsworks (>=1.35.0,<1.36.0)", "mypy-boto3-opsworkscm (>=1.35.0,<1.36.0)", "mypy-boto3-organizations (>=1.35.0,<1.36.0)", "mypy-boto3-osis (>=1.35.0,<1.36.0)", "mypy-boto3-outposts (>=1.35.0,<1.36.0)", "mypy-boto3-panorama (>=1.35.0,<1.36.0)", "mypy-boto3-partnercentral-selling (>=1.35.0,<1.36.0)", "mypy-boto3-payment-cryptography (>=1.35.0,<1.36.0)", "mypy-boto3-payment-cryptography-data (>=1.35.0,<1.36.0)", "mypy-boto3-pca-connector-ad (>=1.35.0,<1.36.0)", "mypy-boto3-pca-connector-scep (>=1.35.0,<1.36.0)", "mypy-boto3-pcs (>=1.35.0,<1.36.0)", "mypy-boto3-personalize (>=1.35.0,<1.36.0)", "mypy-boto3-personalize-events (>=1.35.0,<1.36.0)", "mypy-boto3-personalize-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-pi (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint-email (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint-sms-voice (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.35.0,<1.36.0)", "mypy-boto3-pipes (>=1.35.0,<1.36.0)", "mypy-boto3-polly (>=1.35.0,<1.36.0)", "mypy-boto3-pricing (>=1.35.0,<1.36.0)", "mypy-boto3-privatenetworks (>=1.35.0,<1.36.0)", "mypy-boto3-proton (>=1.35.0,<1.36.0)", "mypy-boto3-qapps (>=1.35.0,<1.36.0)", "mypy-boto3-qbusiness (>=1.35.0,<1.36.0)", "mypy-boto3-qconnect (>=1.35.0,<1.36.0)", "mypy-boto3-qldb (>=1.35.0,<1.36.0)", "mypy-boto3-qldb-session (>=1.35.0,<1.36.0)", "mypy-boto3-quicksight (>=1.35.0,<1.36.0)", "mypy-boto3-ram (>=1.35.0,<1.36.0)", "mypy-boto3-rbin (>=1.35.0,<1.36.0)", "mypy-boto3-rds (>=1.35.0,<1.36.0)", "mypy-boto3-rds-data (>=1.35.0,<1.36.0)", "mypy-boto3-redshift (>=1.35.0,<1.36.0)", "mypy-boto3-redshift-data (>=1.35.0,<1.36.0)", "mypy-boto3-redshift-serverless (>=1.35.0,<1.36.0)", "mypy-boto3-rekognition (>=1.35.0,<1.36.0)", "mypy-boto3-repostspace (>=1.35.0,<1.36.0)", "mypy-boto3-resiliencehub (>=1.35.0,<1.36.0)", "mypy-boto3-resource-explorer-2 (>=1.35.0,<1.36.0)", "mypy-boto3-resource-groups (>=1.35.0,<1.36.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.35.0,<1.36.0)", "mypy-boto3-robomaker (>=1.35.0,<1.36.0)", "mypy-boto3-rolesanywhere (>=1.35.0,<1.36.0)", "mypy-boto3-route53 (>=1.35.0,<1.36.0)", "mypy-boto3-route53-recovery-cluster (>=1.35.0,<1.36.0)", "mypy-boto3-route53-recovery-control-config (>=1.35.0,<1.36.0)", "mypy-boto3-route53-recovery-readiness (>=1.35.0,<1.36.0)", "mypy-boto3-route53domains (>=1.35.0,<1.36.0)", "mypy-boto3-route53profiles (>=1.35.0,<1.36.0)", "mypy-boto3-route53resolver (>=1.35.0,<1.36.0)", "mypy-boto3-rum (>=1.35.0,<1.36.0)", "mypy-boto3-s3 (>=1.35.0,<1.36.0)", "mypy-boto3-s3control (>=1.35.0,<1.36.0)", "mypy-boto3-s3outposts (>=1.35.0,<1.36.0)", "mypy-boto3-s3tables (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-edge (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-geospatial (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-metrics (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-savingsplans (>=1.35.0,<1.36.0)", "mypy-boto3-scheduler (>=1.35.0,<1.36.0)", "mypy-boto3-schemas (>=1.35.0,<1.36.0)", "mypy-boto3-sdb (>=1.35.0,<1.36.0)", "mypy-boto3-secretsmanager (>=1.35.0,<1.36.0)", "mypy-boto3-security-ir (>=1.35.0,<1.36.0)", "mypy-boto3-securityhub (>=1.35.0,<1.36.0)", "mypy-boto3-securitylake (>=1.35.0,<1.36.0)", "mypy-boto3-serverlessrepo (>=1.35.0,<1.36.0)", "mypy-boto3-service-quotas (>=1.35.0,<1.36.0)", "mypy-boto3-servicecatalog (>=1.35.0,<1.36.0)", "mypy-boto3-servicecatalog-appregistry (>=1.35.0,<1.36.0)", "mypy-boto3-servicediscovery (>=1.35.0,<1.36.0)", "mypy-boto3-ses (>=1.35.0,<1.36.0)", "mypy-boto3-sesv2 (>=1.35.0,<1.36.0)", "mypy-boto3-shield (>=1.35.0,<1.36.0)", "mypy-boto3-signer (>=1.35.0,<1.36.0)", "mypy-boto3-simspaceweaver (>=1.35.0,<1.36.0)", "mypy-boto3-sms (>=1.35.0,<1.36.0)", "mypy-boto3-sms-voice (>=1.35.0,<1.36.0)", "mypy-boto3-snow-device-management (>=1.35.0,<1.36.0)", "mypy-boto3-snowball (>=1.35.0,<1.36.0)", "mypy-boto3-sns (>=1.35.0,<1.36.0)", "mypy-boto3-socialmessaging (>=1.35.0,<1.36.0)", "mypy-boto3-sqs (>=1.35.0,<1.36.0)", "mypy-boto3-ssm (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-contacts (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-incidents (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-quicksetup (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-sap (>=1.35.0,<1.36.0)", "mypy-boto3-sso (>=1.35.0,<1.36.0)", "mypy-boto3-sso-admin (>=1.35.0,<1.36.0)", "mypy-boto3-sso-oidc (>=1.35.0,<1.36.0)", "mypy-boto3-stepfunctions (>=1.35.0,<1.36.0)", "mypy-boto3-storagegateway (>=1.35.0,<1.36.0)", "mypy-boto3-sts (>=1.35.0,<1.36.0)", "mypy-boto3-supplychain (>=1.35.0,<1.36.0)", "mypy-boto3-support (>=1.35.0,<1.36.0)", "mypy-boto3-support-app (>=1.35.0,<1.36.0)", "mypy-boto3-swf (>=1.35.0,<1.36.0)", "mypy-boto3-synthetics (>=1.35.0,<1.36.0)", "mypy-boto3-taxsettings (>=1.35.0,<1.36.0)", "mypy-boto3-textract (>=1.35.0,<1.36.0)", "mypy-boto3-timestream-influxdb (>=1.35.0,<1.36.0)", "mypy-boto3-timestream-query (>=1.35.0,<1.36.0)", "mypy-boto3-timestream-write (>=1.35.0,<1.36.0)", "mypy-boto3-tnb (>=1.35.0,<1.36.0)", "mypy-boto3-transcribe (>=1.35.0,<1.36.0)", "mypy-boto3-transfer (>=1.35.0,<1.36.0)", "mypy-boto3-translate (>=1.35.0,<1.36.0)", "mypy-boto3-trustedadvisor (>=1.35.0,<1.36.0)", "mypy-boto3-verifiedpermissions (>=1.35.0,<1.36.0)", "mypy-boto3-voice-id (>=1.35.0,<1.36.0)", "mypy-boto3-vpc-lattice (>=1.35.0,<1.36.0)", "mypy-boto3-waf (>=1.35.0,<1.36.0)", "mypy-boto3-waf-regional (>=1.35.0,<1.36.0)", "mypy-boto3-wafv2 (>=1.35.0,<1.36.0)", "mypy-boto3-wellarchitected (>=1.35.0,<1.36.0)", "mypy-boto3-wisdom (>=1.35.0,<1.36.0)", "mypy-boto3-workdocs (>=1.35.0,<1.36.0)", "mypy-boto3-workmail (>=1.35.0,<1.36.0)", "mypy-boto3-workmailmessageflow (>=1.35.0,<1.36.0)", "mypy-boto3-workspaces (>=1.35.0,<1.36.0)", "mypy-boto3-workspaces-thin-client (>=1.35.0,<1.36.0)", "mypy-boto3-workspaces-web (>=1.35.0,<1.36.0)", "mypy-boto3-xray (>=1.35.0,<1.36.0)"] +all = ["mypy-boto3-accessanalyzer (>=1.35.0,<1.36.0)", "mypy-boto3-account (>=1.35.0,<1.36.0)", "mypy-boto3-acm (>=1.35.0,<1.36.0)", "mypy-boto3-acm-pca (>=1.35.0,<1.36.0)", "mypy-boto3-amp (>=1.35.0,<1.36.0)", "mypy-boto3-amplify (>=1.35.0,<1.36.0)", "mypy-boto3-amplifybackend (>=1.35.0,<1.36.0)", "mypy-boto3-amplifyuibuilder (>=1.35.0,<1.36.0)", "mypy-boto3-apigateway (>=1.35.0,<1.36.0)", "mypy-boto3-apigatewaymanagementapi (>=1.35.0,<1.36.0)", "mypy-boto3-apigatewayv2 (>=1.35.0,<1.36.0)", "mypy-boto3-appconfig (>=1.35.0,<1.36.0)", "mypy-boto3-appconfigdata (>=1.35.0,<1.36.0)", "mypy-boto3-appfabric (>=1.35.0,<1.36.0)", "mypy-boto3-appflow (>=1.35.0,<1.36.0)", "mypy-boto3-appintegrations (>=1.35.0,<1.36.0)", "mypy-boto3-application-autoscaling (>=1.35.0,<1.36.0)", "mypy-boto3-application-insights (>=1.35.0,<1.36.0)", "mypy-boto3-application-signals (>=1.35.0,<1.36.0)", "mypy-boto3-applicationcostprofiler (>=1.35.0,<1.36.0)", "mypy-boto3-appmesh (>=1.35.0,<1.36.0)", "mypy-boto3-apprunner (>=1.35.0,<1.36.0)", "mypy-boto3-appstream (>=1.35.0,<1.36.0)", "mypy-boto3-appsync (>=1.35.0,<1.36.0)", "mypy-boto3-apptest (>=1.35.0,<1.36.0)", "mypy-boto3-arc-zonal-shift (>=1.35.0,<1.36.0)", "mypy-boto3-artifact (>=1.35.0,<1.36.0)", "mypy-boto3-athena (>=1.35.0,<1.36.0)", "mypy-boto3-auditmanager (>=1.35.0,<1.36.0)", "mypy-boto3-autoscaling (>=1.35.0,<1.36.0)", "mypy-boto3-autoscaling-plans (>=1.35.0,<1.36.0)", "mypy-boto3-b2bi (>=1.35.0,<1.36.0)", "mypy-boto3-backup (>=1.35.0,<1.36.0)", "mypy-boto3-backup-gateway (>=1.35.0,<1.36.0)", "mypy-boto3-backupsearch (>=1.35.0,<1.36.0)", "mypy-boto3-batch (>=1.35.0,<1.36.0)", "mypy-boto3-bcm-data-exports (>=1.35.0,<1.36.0)", "mypy-boto3-bcm-pricing-calculator (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-agent (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-agent-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-data-automation (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-data-automation-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-bedrock-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-billing (>=1.35.0,<1.36.0)", "mypy-boto3-billingconductor (>=1.35.0,<1.36.0)", "mypy-boto3-braket (>=1.35.0,<1.36.0)", "mypy-boto3-budgets (>=1.35.0,<1.36.0)", "mypy-boto3-ce (>=1.35.0,<1.36.0)", "mypy-boto3-chatbot (>=1.35.0,<1.36.0)", "mypy-boto3-chime (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-identity (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-meetings (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-messaging (>=1.35.0,<1.36.0)", "mypy-boto3-chime-sdk-voice (>=1.35.0,<1.36.0)", "mypy-boto3-cleanrooms (>=1.35.0,<1.36.0)", "mypy-boto3-cleanroomsml (>=1.35.0,<1.36.0)", "mypy-boto3-cloud9 (>=1.35.0,<1.36.0)", "mypy-boto3-cloudcontrol (>=1.35.0,<1.36.0)", "mypy-boto3-clouddirectory (>=1.35.0,<1.36.0)", "mypy-boto3-cloudformation (>=1.35.0,<1.36.0)", "mypy-boto3-cloudfront (>=1.35.0,<1.36.0)", "mypy-boto3-cloudfront-keyvaluestore (>=1.35.0,<1.36.0)", "mypy-boto3-cloudhsm (>=1.35.0,<1.36.0)", "mypy-boto3-cloudhsmv2 (>=1.35.0,<1.36.0)", "mypy-boto3-cloudsearch (>=1.35.0,<1.36.0)", "mypy-boto3-cloudsearchdomain (>=1.35.0,<1.36.0)", "mypy-boto3-cloudtrail (>=1.35.0,<1.36.0)", "mypy-boto3-cloudtrail-data (>=1.35.0,<1.36.0)", "mypy-boto3-cloudwatch (>=1.35.0,<1.36.0)", "mypy-boto3-codeartifact (>=1.35.0,<1.36.0)", "mypy-boto3-codebuild (>=1.35.0,<1.36.0)", "mypy-boto3-codecatalyst (>=1.35.0,<1.36.0)", "mypy-boto3-codecommit (>=1.35.0,<1.36.0)", "mypy-boto3-codeconnections (>=1.35.0,<1.36.0)", "mypy-boto3-codedeploy (>=1.35.0,<1.36.0)", "mypy-boto3-codeguru-reviewer (>=1.35.0,<1.36.0)", "mypy-boto3-codeguru-security (>=1.35.0,<1.36.0)", "mypy-boto3-codeguruprofiler (>=1.35.0,<1.36.0)", "mypy-boto3-codepipeline (>=1.35.0,<1.36.0)", "mypy-boto3-codestar-connections (>=1.35.0,<1.36.0)", "mypy-boto3-codestar-notifications (>=1.35.0,<1.36.0)", "mypy-boto3-cognito-identity (>=1.35.0,<1.36.0)", "mypy-boto3-cognito-idp (>=1.35.0,<1.36.0)", "mypy-boto3-cognito-sync (>=1.35.0,<1.36.0)", "mypy-boto3-comprehend (>=1.35.0,<1.36.0)", "mypy-boto3-comprehendmedical (>=1.35.0,<1.36.0)", "mypy-boto3-compute-optimizer (>=1.35.0,<1.36.0)", "mypy-boto3-config (>=1.35.0,<1.36.0)", "mypy-boto3-connect (>=1.35.0,<1.36.0)", "mypy-boto3-connect-contact-lens (>=1.35.0,<1.36.0)", "mypy-boto3-connectcampaigns (>=1.35.0,<1.36.0)", "mypy-boto3-connectcampaignsv2 (>=1.35.0,<1.36.0)", "mypy-boto3-connectcases (>=1.35.0,<1.36.0)", "mypy-boto3-connectparticipant (>=1.35.0,<1.36.0)", "mypy-boto3-controlcatalog (>=1.35.0,<1.36.0)", "mypy-boto3-controltower (>=1.35.0,<1.36.0)", "mypy-boto3-cost-optimization-hub (>=1.35.0,<1.36.0)", "mypy-boto3-cur (>=1.35.0,<1.36.0)", "mypy-boto3-customer-profiles (>=1.35.0,<1.36.0)", "mypy-boto3-databrew (>=1.35.0,<1.36.0)", "mypy-boto3-dataexchange (>=1.35.0,<1.36.0)", "mypy-boto3-datapipeline (>=1.35.0,<1.36.0)", "mypy-boto3-datasync (>=1.35.0,<1.36.0)", "mypy-boto3-datazone (>=1.35.0,<1.36.0)", "mypy-boto3-dax (>=1.35.0,<1.36.0)", "mypy-boto3-deadline (>=1.35.0,<1.36.0)", "mypy-boto3-detective (>=1.35.0,<1.36.0)", "mypy-boto3-devicefarm (>=1.35.0,<1.36.0)", "mypy-boto3-devops-guru (>=1.35.0,<1.36.0)", "mypy-boto3-directconnect (>=1.35.0,<1.36.0)", "mypy-boto3-discovery (>=1.35.0,<1.36.0)", "mypy-boto3-dlm (>=1.35.0,<1.36.0)", "mypy-boto3-dms (>=1.35.0,<1.36.0)", "mypy-boto3-docdb (>=1.35.0,<1.36.0)", "mypy-boto3-docdb-elastic (>=1.35.0,<1.36.0)", "mypy-boto3-drs (>=1.35.0,<1.36.0)", "mypy-boto3-ds (>=1.35.0,<1.36.0)", "mypy-boto3-ds-data (>=1.35.0,<1.36.0)", "mypy-boto3-dsql (>=1.35.0,<1.36.0)", "mypy-boto3-dynamodb (>=1.35.0,<1.36.0)", "mypy-boto3-dynamodbstreams (>=1.35.0,<1.36.0)", "mypy-boto3-ebs (>=1.35.0,<1.36.0)", "mypy-boto3-ec2 (>=1.35.0,<1.36.0)", "mypy-boto3-ec2-instance-connect (>=1.35.0,<1.36.0)", "mypy-boto3-ecr (>=1.35.0,<1.36.0)", "mypy-boto3-ecr-public (>=1.35.0,<1.36.0)", "mypy-boto3-ecs (>=1.35.0,<1.36.0)", "mypy-boto3-efs (>=1.35.0,<1.36.0)", "mypy-boto3-eks (>=1.35.0,<1.36.0)", "mypy-boto3-eks-auth (>=1.35.0,<1.36.0)", "mypy-boto3-elastic-inference (>=1.35.0,<1.36.0)", "mypy-boto3-elasticache (>=1.35.0,<1.36.0)", "mypy-boto3-elasticbeanstalk (>=1.35.0,<1.36.0)", "mypy-boto3-elastictranscoder (>=1.35.0,<1.36.0)", "mypy-boto3-elb (>=1.35.0,<1.36.0)", "mypy-boto3-elbv2 (>=1.35.0,<1.36.0)", "mypy-boto3-emr (>=1.35.0,<1.36.0)", "mypy-boto3-emr-containers (>=1.35.0,<1.36.0)", "mypy-boto3-emr-serverless (>=1.35.0,<1.36.0)", "mypy-boto3-entityresolution (>=1.35.0,<1.36.0)", "mypy-boto3-es (>=1.35.0,<1.36.0)", "mypy-boto3-events (>=1.35.0,<1.36.0)", "mypy-boto3-evidently (>=1.35.0,<1.36.0)", "mypy-boto3-finspace (>=1.35.0,<1.36.0)", "mypy-boto3-finspace-data (>=1.35.0,<1.36.0)", "mypy-boto3-firehose (>=1.35.0,<1.36.0)", "mypy-boto3-fis (>=1.35.0,<1.36.0)", "mypy-boto3-fms (>=1.35.0,<1.36.0)", "mypy-boto3-forecast (>=1.35.0,<1.36.0)", "mypy-boto3-forecastquery (>=1.35.0,<1.36.0)", "mypy-boto3-frauddetector (>=1.35.0,<1.36.0)", "mypy-boto3-freetier (>=1.35.0,<1.36.0)", "mypy-boto3-fsx (>=1.35.0,<1.36.0)", "mypy-boto3-gamelift (>=1.35.0,<1.36.0)", "mypy-boto3-geo-maps (>=1.35.0,<1.36.0)", "mypy-boto3-geo-places (>=1.35.0,<1.36.0)", "mypy-boto3-geo-routes (>=1.35.0,<1.36.0)", "mypy-boto3-glacier (>=1.35.0,<1.36.0)", "mypy-boto3-globalaccelerator (>=1.35.0,<1.36.0)", "mypy-boto3-glue (>=1.35.0,<1.36.0)", "mypy-boto3-grafana (>=1.35.0,<1.36.0)", "mypy-boto3-greengrass (>=1.35.0,<1.36.0)", "mypy-boto3-greengrassv2 (>=1.35.0,<1.36.0)", "mypy-boto3-groundstation (>=1.35.0,<1.36.0)", "mypy-boto3-guardduty (>=1.35.0,<1.36.0)", "mypy-boto3-health (>=1.35.0,<1.36.0)", "mypy-boto3-healthlake (>=1.35.0,<1.36.0)", "mypy-boto3-iam (>=1.35.0,<1.36.0)", "mypy-boto3-identitystore (>=1.35.0,<1.36.0)", "mypy-boto3-imagebuilder (>=1.35.0,<1.36.0)", "mypy-boto3-importexport (>=1.35.0,<1.36.0)", "mypy-boto3-inspector (>=1.35.0,<1.36.0)", "mypy-boto3-inspector-scan (>=1.35.0,<1.36.0)", "mypy-boto3-inspector2 (>=1.35.0,<1.36.0)", "mypy-boto3-internetmonitor (>=1.35.0,<1.36.0)", "mypy-boto3-invoicing (>=1.35.0,<1.36.0)", "mypy-boto3-iot (>=1.35.0,<1.36.0)", "mypy-boto3-iot-data (>=1.35.0,<1.36.0)", "mypy-boto3-iot-jobs-data (>=1.35.0,<1.36.0)", "mypy-boto3-iot1click-devices (>=1.35.0,<1.36.0)", "mypy-boto3-iot1click-projects (>=1.35.0,<1.36.0)", "mypy-boto3-iotanalytics (>=1.35.0,<1.36.0)", "mypy-boto3-iotdeviceadvisor (>=1.35.0,<1.36.0)", "mypy-boto3-iotevents (>=1.35.0,<1.36.0)", "mypy-boto3-iotevents-data (>=1.35.0,<1.36.0)", "mypy-boto3-iotfleethub (>=1.35.0,<1.36.0)", "mypy-boto3-iotfleetwise (>=1.35.0,<1.36.0)", "mypy-boto3-iotsecuretunneling (>=1.35.0,<1.36.0)", "mypy-boto3-iotsitewise (>=1.35.0,<1.36.0)", "mypy-boto3-iotthingsgraph (>=1.35.0,<1.36.0)", "mypy-boto3-iottwinmaker (>=1.35.0,<1.36.0)", "mypy-boto3-iotwireless (>=1.35.0,<1.36.0)", "mypy-boto3-ivs (>=1.35.0,<1.36.0)", "mypy-boto3-ivs-realtime (>=1.35.0,<1.36.0)", "mypy-boto3-ivschat (>=1.35.0,<1.36.0)", "mypy-boto3-kafka (>=1.35.0,<1.36.0)", "mypy-boto3-kafkaconnect (>=1.35.0,<1.36.0)", "mypy-boto3-kendra (>=1.35.0,<1.36.0)", "mypy-boto3-kendra-ranking (>=1.35.0,<1.36.0)", "mypy-boto3-keyspaces (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-archived-media (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-media (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-signaling (>=1.35.0,<1.36.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.35.0,<1.36.0)", "mypy-boto3-kinesisanalytics (>=1.35.0,<1.36.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.35.0,<1.36.0)", "mypy-boto3-kinesisvideo (>=1.35.0,<1.36.0)", "mypy-boto3-kms (>=1.35.0,<1.36.0)", "mypy-boto3-lakeformation (>=1.35.0,<1.36.0)", "mypy-boto3-lambda (>=1.35.0,<1.36.0)", "mypy-boto3-launch-wizard (>=1.35.0,<1.36.0)", "mypy-boto3-lex-models (>=1.35.0,<1.36.0)", "mypy-boto3-lex-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-lexv2-models (>=1.35.0,<1.36.0)", "mypy-boto3-lexv2-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-license-manager (>=1.35.0,<1.36.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.35.0,<1.36.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.35.0,<1.36.0)", "mypy-boto3-lightsail (>=1.35.0,<1.36.0)", "mypy-boto3-location (>=1.35.0,<1.36.0)", "mypy-boto3-logs (>=1.35.0,<1.36.0)", "mypy-boto3-lookoutequipment (>=1.35.0,<1.36.0)", "mypy-boto3-lookoutmetrics (>=1.35.0,<1.36.0)", "mypy-boto3-lookoutvision (>=1.35.0,<1.36.0)", "mypy-boto3-m2 (>=1.35.0,<1.36.0)", "mypy-boto3-machinelearning (>=1.35.0,<1.36.0)", "mypy-boto3-macie2 (>=1.35.0,<1.36.0)", "mypy-boto3-mailmanager (>=1.35.0,<1.36.0)", "mypy-boto3-managedblockchain (>=1.35.0,<1.36.0)", "mypy-boto3-managedblockchain-query (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-agreement (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-catalog (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-deployment (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-entitlement (>=1.35.0,<1.36.0)", "mypy-boto3-marketplace-reporting (>=1.35.0,<1.36.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.35.0,<1.36.0)", "mypy-boto3-mediaconnect (>=1.35.0,<1.36.0)", "mypy-boto3-mediaconvert (>=1.35.0,<1.36.0)", "mypy-boto3-medialive (>=1.35.0,<1.36.0)", "mypy-boto3-mediapackage (>=1.35.0,<1.36.0)", "mypy-boto3-mediapackage-vod (>=1.35.0,<1.36.0)", "mypy-boto3-mediapackagev2 (>=1.35.0,<1.36.0)", "mypy-boto3-mediastore (>=1.35.0,<1.36.0)", "mypy-boto3-mediastore-data (>=1.35.0,<1.36.0)", "mypy-boto3-mediatailor (>=1.35.0,<1.36.0)", "mypy-boto3-medical-imaging (>=1.35.0,<1.36.0)", "mypy-boto3-memorydb (>=1.35.0,<1.36.0)", "mypy-boto3-meteringmarketplace (>=1.35.0,<1.36.0)", "mypy-boto3-mgh (>=1.35.0,<1.36.0)", "mypy-boto3-mgn (>=1.35.0,<1.36.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.35.0,<1.36.0)", "mypy-boto3-migrationhub-config (>=1.35.0,<1.36.0)", "mypy-boto3-migrationhuborchestrator (>=1.35.0,<1.36.0)", "mypy-boto3-migrationhubstrategy (>=1.35.0,<1.36.0)", "mypy-boto3-mq (>=1.35.0,<1.36.0)", "mypy-boto3-mturk (>=1.35.0,<1.36.0)", "mypy-boto3-mwaa (>=1.35.0,<1.36.0)", "mypy-boto3-neptune (>=1.35.0,<1.36.0)", "mypy-boto3-neptune-graph (>=1.35.0,<1.36.0)", "mypy-boto3-neptunedata (>=1.35.0,<1.36.0)", "mypy-boto3-network-firewall (>=1.35.0,<1.36.0)", "mypy-boto3-networkflowmonitor (>=1.35.0,<1.36.0)", "mypy-boto3-networkmanager (>=1.35.0,<1.36.0)", "mypy-boto3-networkmonitor (>=1.35.0,<1.36.0)", "mypy-boto3-notifications (>=1.35.0,<1.36.0)", "mypy-boto3-notificationscontacts (>=1.35.0,<1.36.0)", "mypy-boto3-oam (>=1.35.0,<1.36.0)", "mypy-boto3-observabilityadmin (>=1.35.0,<1.36.0)", "mypy-boto3-omics (>=1.35.0,<1.36.0)", "mypy-boto3-opensearch (>=1.35.0,<1.36.0)", "mypy-boto3-opensearchserverless (>=1.35.0,<1.36.0)", "mypy-boto3-opsworks (>=1.35.0,<1.36.0)", "mypy-boto3-opsworkscm (>=1.35.0,<1.36.0)", "mypy-boto3-organizations (>=1.35.0,<1.36.0)", "mypy-boto3-osis (>=1.35.0,<1.36.0)", "mypy-boto3-outposts (>=1.35.0,<1.36.0)", "mypy-boto3-panorama (>=1.35.0,<1.36.0)", "mypy-boto3-partnercentral-selling (>=1.35.0,<1.36.0)", "mypy-boto3-payment-cryptography (>=1.35.0,<1.36.0)", "mypy-boto3-payment-cryptography-data (>=1.35.0,<1.36.0)", "mypy-boto3-pca-connector-ad (>=1.35.0,<1.36.0)", "mypy-boto3-pca-connector-scep (>=1.35.0,<1.36.0)", "mypy-boto3-pcs (>=1.35.0,<1.36.0)", "mypy-boto3-personalize (>=1.35.0,<1.36.0)", "mypy-boto3-personalize-events (>=1.35.0,<1.36.0)", "mypy-boto3-personalize-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-pi (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint-email (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint-sms-voice (>=1.35.0,<1.36.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.35.0,<1.36.0)", "mypy-boto3-pipes (>=1.35.0,<1.36.0)", "mypy-boto3-polly (>=1.35.0,<1.36.0)", "mypy-boto3-pricing (>=1.35.0,<1.36.0)", "mypy-boto3-privatenetworks (>=1.35.0,<1.36.0)", "mypy-boto3-proton (>=1.35.0,<1.36.0)", "mypy-boto3-qapps (>=1.35.0,<1.36.0)", "mypy-boto3-qbusiness (>=1.35.0,<1.36.0)", "mypy-boto3-qconnect (>=1.35.0,<1.36.0)", "mypy-boto3-qldb (>=1.35.0,<1.36.0)", "mypy-boto3-qldb-session (>=1.35.0,<1.36.0)", "mypy-boto3-quicksight (>=1.35.0,<1.36.0)", "mypy-boto3-ram (>=1.35.0,<1.36.0)", "mypy-boto3-rbin (>=1.35.0,<1.36.0)", "mypy-boto3-rds (>=1.35.0,<1.36.0)", "mypy-boto3-rds-data (>=1.35.0,<1.36.0)", "mypy-boto3-redshift (>=1.35.0,<1.36.0)", "mypy-boto3-redshift-data (>=1.35.0,<1.36.0)", "mypy-boto3-redshift-serverless (>=1.35.0,<1.36.0)", "mypy-boto3-rekognition (>=1.35.0,<1.36.0)", "mypy-boto3-repostspace (>=1.35.0,<1.36.0)", "mypy-boto3-resiliencehub (>=1.35.0,<1.36.0)", "mypy-boto3-resource-explorer-2 (>=1.35.0,<1.36.0)", "mypy-boto3-resource-groups (>=1.35.0,<1.36.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.35.0,<1.36.0)", "mypy-boto3-robomaker (>=1.35.0,<1.36.0)", "mypy-boto3-rolesanywhere (>=1.35.0,<1.36.0)", "mypy-boto3-route53 (>=1.35.0,<1.36.0)", "mypy-boto3-route53-recovery-cluster (>=1.35.0,<1.36.0)", "mypy-boto3-route53-recovery-control-config (>=1.35.0,<1.36.0)", "mypy-boto3-route53-recovery-readiness (>=1.35.0,<1.36.0)", "mypy-boto3-route53domains (>=1.35.0,<1.36.0)", "mypy-boto3-route53profiles (>=1.35.0,<1.36.0)", "mypy-boto3-route53resolver (>=1.35.0,<1.36.0)", "mypy-boto3-rum (>=1.35.0,<1.36.0)", "mypy-boto3-s3 (>=1.35.0,<1.36.0)", "mypy-boto3-s3control (>=1.35.0,<1.36.0)", "mypy-boto3-s3outposts (>=1.35.0,<1.36.0)", "mypy-boto3-s3tables (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-edge (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-geospatial (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-metrics (>=1.35.0,<1.36.0)", "mypy-boto3-sagemaker-runtime (>=1.35.0,<1.36.0)", "mypy-boto3-savingsplans (>=1.35.0,<1.36.0)", "mypy-boto3-scheduler (>=1.35.0,<1.36.0)", "mypy-boto3-schemas (>=1.35.0,<1.36.0)", "mypy-boto3-sdb (>=1.35.0,<1.36.0)", "mypy-boto3-secretsmanager (>=1.35.0,<1.36.0)", "mypy-boto3-security-ir (>=1.35.0,<1.36.0)", "mypy-boto3-securityhub (>=1.35.0,<1.36.0)", "mypy-boto3-securitylake (>=1.35.0,<1.36.0)", "mypy-boto3-serverlessrepo (>=1.35.0,<1.36.0)", "mypy-boto3-service-quotas (>=1.35.0,<1.36.0)", "mypy-boto3-servicecatalog (>=1.35.0,<1.36.0)", "mypy-boto3-servicecatalog-appregistry (>=1.35.0,<1.36.0)", "mypy-boto3-servicediscovery (>=1.35.0,<1.36.0)", "mypy-boto3-ses (>=1.35.0,<1.36.0)", "mypy-boto3-sesv2 (>=1.35.0,<1.36.0)", "mypy-boto3-shield (>=1.35.0,<1.36.0)", "mypy-boto3-signer (>=1.35.0,<1.36.0)", "mypy-boto3-simspaceweaver (>=1.35.0,<1.36.0)", "mypy-boto3-sms (>=1.35.0,<1.36.0)", "mypy-boto3-sms-voice (>=1.35.0,<1.36.0)", "mypy-boto3-snow-device-management (>=1.35.0,<1.36.0)", "mypy-boto3-snowball (>=1.35.0,<1.36.0)", "mypy-boto3-sns (>=1.35.0,<1.36.0)", "mypy-boto3-socialmessaging (>=1.35.0,<1.36.0)", "mypy-boto3-sqs (>=1.35.0,<1.36.0)", "mypy-boto3-ssm (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-contacts (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-incidents (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-quicksetup (>=1.35.0,<1.36.0)", "mypy-boto3-ssm-sap (>=1.35.0,<1.36.0)", "mypy-boto3-sso (>=1.35.0,<1.36.0)", "mypy-boto3-sso-admin (>=1.35.0,<1.36.0)", "mypy-boto3-sso-oidc (>=1.35.0,<1.36.0)", "mypy-boto3-stepfunctions (>=1.35.0,<1.36.0)", "mypy-boto3-storagegateway (>=1.35.0,<1.36.0)", "mypy-boto3-sts (>=1.35.0,<1.36.0)", "mypy-boto3-supplychain (>=1.35.0,<1.36.0)", "mypy-boto3-support (>=1.35.0,<1.36.0)", "mypy-boto3-support-app (>=1.35.0,<1.36.0)", "mypy-boto3-swf (>=1.35.0,<1.36.0)", "mypy-boto3-synthetics (>=1.35.0,<1.36.0)", "mypy-boto3-taxsettings (>=1.35.0,<1.36.0)", "mypy-boto3-textract (>=1.35.0,<1.36.0)", "mypy-boto3-timestream-influxdb (>=1.35.0,<1.36.0)", "mypy-boto3-timestream-query (>=1.35.0,<1.36.0)", "mypy-boto3-timestream-write (>=1.35.0,<1.36.0)", "mypy-boto3-tnb (>=1.35.0,<1.36.0)", "mypy-boto3-transcribe (>=1.35.0,<1.36.0)", "mypy-boto3-transfer (>=1.35.0,<1.36.0)", "mypy-boto3-translate (>=1.35.0,<1.36.0)", "mypy-boto3-trustedadvisor (>=1.35.0,<1.36.0)", "mypy-boto3-verifiedpermissions (>=1.35.0,<1.36.0)", "mypy-boto3-voice-id (>=1.35.0,<1.36.0)", "mypy-boto3-vpc-lattice (>=1.35.0,<1.36.0)", "mypy-boto3-waf (>=1.35.0,<1.36.0)", "mypy-boto3-waf-regional (>=1.35.0,<1.36.0)", "mypy-boto3-wafv2 (>=1.35.0,<1.36.0)", "mypy-boto3-wellarchitected (>=1.35.0,<1.36.0)", "mypy-boto3-wisdom (>=1.35.0,<1.36.0)", "mypy-boto3-workdocs (>=1.35.0,<1.36.0)", "mypy-boto3-workmail (>=1.35.0,<1.36.0)", "mypy-boto3-workmailmessageflow (>=1.35.0,<1.36.0)", "mypy-boto3-workspaces (>=1.35.0,<1.36.0)", "mypy-boto3-workspaces-thin-client (>=1.35.0,<1.36.0)", "mypy-boto3-workspaces-web (>=1.35.0,<1.36.0)", "mypy-boto3-xray (>=1.35.0,<1.36.0)"] amp = ["mypy-boto3-amp (>=1.35.0,<1.36.0)"] amplify = ["mypy-boto3-amplify (>=1.35.0,<1.36.0)"] amplifybackend = ["mypy-boto3-amplifybackend (>=1.35.0,<1.36.0)"] @@ -269,6 +269,7 @@ autoscaling-plans = ["mypy-boto3-autoscaling-plans (>=1.35.0,<1.36.0)"] b2bi = ["mypy-boto3-b2bi (>=1.35.0,<1.36.0)"] backup = ["mypy-boto3-backup (>=1.35.0,<1.36.0)"] backup-gateway = ["mypy-boto3-backup-gateway (>=1.35.0,<1.36.0)"] +backupsearch = ["mypy-boto3-backupsearch (>=1.35.0,<1.36.0)"] batch = ["mypy-boto3-batch (>=1.35.0,<1.36.0)"] bcm-data-exports = ["mypy-boto3-bcm-data-exports (>=1.35.0,<1.36.0)"] bcm-pricing-calculator = ["mypy-boto3-bcm-pricing-calculator (>=1.35.0,<1.36.0)"] @@ -280,7 +281,7 @@ bedrock-data-automation-runtime = ["mypy-boto3-bedrock-data-automation-runtime ( bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.35.0,<1.36.0)"] billing = ["mypy-boto3-billing (>=1.35.0,<1.36.0)"] billingconductor = ["mypy-boto3-billingconductor (>=1.35.0,<1.36.0)"] -boto3 = ["boto3 (==1.35.76)", "botocore (==1.35.76)"] +boto3 = ["boto3 (==1.35.85)"] braket = ["mypy-boto3-braket (>=1.35.0,<1.36.0)"] budgets = ["mypy-boto3-budgets (>=1.35.0,<1.36.0)"] ce = ["mypy-boto3-ce (>=1.35.0,<1.36.0)"] @@ -391,7 +392,7 @@ forecastquery = ["mypy-boto3-forecastquery (>=1.35.0,<1.36.0)"] frauddetector = ["mypy-boto3-frauddetector (>=1.35.0,<1.36.0)"] freetier = ["mypy-boto3-freetier (>=1.35.0,<1.36.0)"] fsx = ["mypy-boto3-fsx (>=1.35.0,<1.36.0)"] -full = ["boto3-stubs-full"] +full = ["boto3-stubs-full (>=1.35.0,<1.36.0)"] gamelift = ["mypy-boto3-gamelift (>=1.35.0,<1.36.0)"] geo-maps = ["mypy-boto3-geo-maps (>=1.35.0,<1.36.0)"] geo-places = ["mypy-boto3-geo-places (>=1.35.0,<1.36.0)"] @@ -664,13 +665,13 @@ crt = ["awscrt (==0.21.2)"] [[package]] name = "botocore-stubs" -version = "1.35.76" +version = "1.35.84.post1" description = "Type annotations and code completion for botocore" optional = false python-versions = ">=3.8" files = [ - {file = "botocore_stubs-1.35.76-py3-none-any.whl", hash = "sha256:617508d023e0bc98901e0189b794c4b3f289c1747c7cc410173ad698c819a716"}, - {file = "botocore_stubs-1.35.76.tar.gz", hash = "sha256:c977a049481d50a14bf2db0ef15020b76734ff628d4b8e0e77b8d1c65318369e"}, + {file = "botocore_stubs-1.35.84.post1-py3-none-any.whl", hash = "sha256:bb0d6fb74161944ab61a66ae2d891b780c163e89336f24462d0e8aed5b9ff4b9"}, + {file = "botocore_stubs-1.35.84.post1.tar.gz", hash = "sha256:ead1b807a3baa77962bc89ab701616425374ba5d31219fb64c2ca090f33090b3"}, ] [package.dependencies] @@ -706,13 +707,13 @@ test = ["coverage", "pre-commit", "pytest", "pytest-cov", "pytest-mock"] [[package]] name = "certifi" -version = "2024.8.30" +version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] @@ -957,73 +958,73 @@ files = [ [[package]] name = "coverage" -version = "7.6.8" +version = "7.6.9" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"}, - {file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"}, - {file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"}, - {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"}, - {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"}, - {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"}, - {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"}, - {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"}, - {file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"}, - {file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"}, - {file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"}, - {file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"}, - {file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"}, - {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"}, - {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"}, - {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"}, - {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"}, - {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"}, - {file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"}, - {file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"}, - {file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"}, - {file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"}, - {file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"}, - {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"}, - {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"}, - {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"}, - {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"}, - {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"}, - {file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"}, - {file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"}, - {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"}, - {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"}, - {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"}, - {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"}, - {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"}, - {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"}, - {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"}, - {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"}, - {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"}, - {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"}, - {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"}, - {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"}, - {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"}, - {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"}, - {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"}, - {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"}, - {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"}, - {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"}, - {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"}, - {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"}, - {file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"}, - {file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"}, - {file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"}, - {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"}, - {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"}, - {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"}, - {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"}, - {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"}, - {file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"}, - {file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"}, - {file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"}, - {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"}, + {file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"}, + {file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"}, + {file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"}, + {file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"}, + {file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"}, + {file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"}, + {file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"}, + {file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"}, + {file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"}, + {file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"}, + {file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"}, + {file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"}, + {file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"}, + {file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"}, + {file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"}, + {file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"}, + {file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"}, + {file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"}, + {file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"}, + {file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"}, + {file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"}, + {file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"}, + {file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"}, + {file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"}, + {file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"}, + {file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"}, ] [package.extras] @@ -1116,37 +1117,37 @@ test = ["flake8", "isort", "pytest"] [[package]] name = "debugpy" -version = "1.8.9" +version = "1.8.11" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" files = [ - {file = "debugpy-1.8.9-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:cfe1e6c6ad7178265f74981edf1154ffce97b69005212fbc90ca22ddfe3d017e"}, - {file = "debugpy-1.8.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada7fb65102a4d2c9ab62e8908e9e9f12aed9d76ef44880367bc9308ebe49a0f"}, - {file = "debugpy-1.8.9-cp310-cp310-win32.whl", hash = "sha256:c36856343cbaa448171cba62a721531e10e7ffb0abff838004701454149bc037"}, - {file = "debugpy-1.8.9-cp310-cp310-win_amd64.whl", hash = "sha256:17c5e0297678442511cf00a745c9709e928ea4ca263d764e90d233208889a19e"}, - {file = "debugpy-1.8.9-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:b74a49753e21e33e7cf030883a92fa607bddc4ede1aa4145172debc637780040"}, - {file = "debugpy-1.8.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62d22dacdb0e296966d7d74a7141aaab4bec123fa43d1a35ddcb39bf9fd29d70"}, - {file = "debugpy-1.8.9-cp311-cp311-win32.whl", hash = "sha256:8138efff315cd09b8dcd14226a21afda4ca582284bf4215126d87342bba1cc66"}, - {file = "debugpy-1.8.9-cp311-cp311-win_amd64.whl", hash = "sha256:ff54ef77ad9f5c425398efb150239f6fe8e20c53ae2f68367eba7ece1e96226d"}, - {file = "debugpy-1.8.9-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:957363d9a7a6612a37458d9a15e72d03a635047f946e5fceee74b50d52a9c8e2"}, - {file = "debugpy-1.8.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e565fc54b680292b418bb809f1386f17081d1346dca9a871bf69a8ac4071afe"}, - {file = "debugpy-1.8.9-cp312-cp312-win32.whl", hash = "sha256:3e59842d6c4569c65ceb3751075ff8d7e6a6ada209ceca6308c9bde932bcef11"}, - {file = "debugpy-1.8.9-cp312-cp312-win_amd64.whl", hash = "sha256:66eeae42f3137eb428ea3a86d4a55f28da9bd5a4a3d369ba95ecc3a92c1bba53"}, - {file = "debugpy-1.8.9-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:957ecffff80d47cafa9b6545de9e016ae8c9547c98a538ee96ab5947115fb3dd"}, - {file = "debugpy-1.8.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1efbb3ff61487e2c16b3e033bc8595aea578222c08aaf3c4bf0f93fadbd662ee"}, - {file = "debugpy-1.8.9-cp313-cp313-win32.whl", hash = "sha256:7c4d65d03bee875bcb211c76c1d8f10f600c305dbd734beaed4077e902606fee"}, - {file = "debugpy-1.8.9-cp313-cp313-win_amd64.whl", hash = "sha256:e46b420dc1bea64e5bbedd678148be512442bc589b0111bd799367cde051e71a"}, - {file = "debugpy-1.8.9-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:472a3994999fe6c0756945ffa359e9e7e2d690fb55d251639d07208dbc37caea"}, - {file = "debugpy-1.8.9-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365e556a4772d7d0d151d7eb0e77ec4db03bcd95f26b67b15742b88cacff88e9"}, - {file = "debugpy-1.8.9-cp38-cp38-win32.whl", hash = "sha256:54a7e6d3014c408eb37b0b06021366ee985f1539e12fe49ca2ee0d392d9ceca5"}, - {file = "debugpy-1.8.9-cp38-cp38-win_amd64.whl", hash = "sha256:8e99c0b1cc7bf86d83fb95d5ccdc4ad0586d4432d489d1f54e4055bcc795f693"}, - {file = "debugpy-1.8.9-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:7e8b079323a56f719977fde9d8115590cb5e7a1cba2fcee0986ef8817116e7c1"}, - {file = "debugpy-1.8.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6953b335b804a41f16a192fa2e7851bdcfd92173cbb2f9f777bb934f49baab65"}, - {file = "debugpy-1.8.9-cp39-cp39-win32.whl", hash = "sha256:7e646e62d4602bb8956db88b1e72fe63172148c1e25c041e03b103a25f36673c"}, - {file = "debugpy-1.8.9-cp39-cp39-win_amd64.whl", hash = "sha256:3d9755e77a2d680ce3d2c5394a444cf42be4a592caaf246dbfbdd100ffcf7ae5"}, - {file = "debugpy-1.8.9-py2.py3-none-any.whl", hash = "sha256:cc37a6c9987ad743d9c3a14fa1b1a14b7e4e6041f9dd0c8abf8895fe7a97b899"}, - {file = "debugpy-1.8.9.zip", hash = "sha256:1339e14c7d980407248f09824d1b25ff5c5616651689f1e0f0e51bdead3ea13e"}, + {file = "debugpy-1.8.11-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:2b26fefc4e31ff85593d68b9022e35e8925714a10ab4858fb1b577a8a48cb8cd"}, + {file = "debugpy-1.8.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61bc8b3b265e6949855300e84dc93d02d7a3a637f2aec6d382afd4ceb9120c9f"}, + {file = "debugpy-1.8.11-cp310-cp310-win32.whl", hash = "sha256:c928bbf47f65288574b78518449edaa46c82572d340e2750889bbf8cd92f3737"}, + {file = "debugpy-1.8.11-cp310-cp310-win_amd64.whl", hash = "sha256:8da1db4ca4f22583e834dcabdc7832e56fe16275253ee53ba66627b86e304da1"}, + {file = "debugpy-1.8.11-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:85de8474ad53ad546ff1c7c7c89230db215b9b8a02754d41cb5a76f70d0be296"}, + {file = "debugpy-1.8.11-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ffc382e4afa4aee367bf413f55ed17bd91b191dcaf979890af239dda435f2a1"}, + {file = "debugpy-1.8.11-cp311-cp311-win32.whl", hash = "sha256:40499a9979c55f72f4eb2fc38695419546b62594f8af194b879d2a18439c97a9"}, + {file = "debugpy-1.8.11-cp311-cp311-win_amd64.whl", hash = "sha256:987bce16e86efa86f747d5151c54e91b3c1e36acc03ce1ddb50f9d09d16ded0e"}, + {file = "debugpy-1.8.11-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:84e511a7545d11683d32cdb8f809ef63fc17ea2a00455cc62d0a4dbb4ed1c308"}, + {file = "debugpy-1.8.11-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce291a5aca4985d82875d6779f61375e959208cdf09fcec40001e65fb0a54768"}, + {file = "debugpy-1.8.11-cp312-cp312-win32.whl", hash = "sha256:28e45b3f827d3bf2592f3cf7ae63282e859f3259db44ed2b129093ca0ac7940b"}, + {file = "debugpy-1.8.11-cp312-cp312-win_amd64.whl", hash = "sha256:44b1b8e6253bceada11f714acf4309ffb98bfa9ac55e4fce14f9e5d4484287a1"}, + {file = "debugpy-1.8.11-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:8988f7163e4381b0da7696f37eec7aca19deb02e500245df68a7159739bbd0d3"}, + {file = "debugpy-1.8.11-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c1f6a173d1140e557347419767d2b14ac1c9cd847e0b4c5444c7f3144697e4e"}, + {file = "debugpy-1.8.11-cp313-cp313-win32.whl", hash = "sha256:bb3b15e25891f38da3ca0740271e63ab9db61f41d4d8541745cfc1824252cb28"}, + {file = "debugpy-1.8.11-cp313-cp313-win_amd64.whl", hash = "sha256:d8768edcbeb34da9e11bcb8b5c2e0958d25218df7a6e56adf415ef262cd7b6d1"}, + {file = "debugpy-1.8.11-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:ad7efe588c8f5cf940f40c3de0cd683cc5b76819446abaa50dc0829a30c094db"}, + {file = "debugpy-1.8.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:189058d03a40103a57144752652b3ab08ff02b7595d0ce1f651b9acc3a3a35a0"}, + {file = "debugpy-1.8.11-cp38-cp38-win32.whl", hash = "sha256:32db46ba45849daed7ccf3f2e26f7a386867b077f39b2a974bb5c4c2c3b0a280"}, + {file = "debugpy-1.8.11-cp38-cp38-win_amd64.whl", hash = "sha256:116bf8342062246ca749013df4f6ea106f23bc159305843491f64672a55af2e5"}, + {file = "debugpy-1.8.11-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:654130ca6ad5de73d978057eaf9e582244ff72d4574b3e106fb8d3d2a0d32458"}, + {file = "debugpy-1.8.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23dc34c5e03b0212fa3c49a874df2b8b1b8fda95160bd79c01eb3ab51ea8d851"}, + {file = "debugpy-1.8.11-cp39-cp39-win32.whl", hash = "sha256:52d8a3166c9f2815bfae05f386114b0b2d274456980d41f320299a8d9a5615a7"}, + {file = "debugpy-1.8.11-cp39-cp39-win_amd64.whl", hash = "sha256:52c3cf9ecda273a19cc092961ee34eb9ba8687d67ba34cc7b79a521c1c64c4c0"}, + {file = "debugpy-1.8.11-py2.py3-none-any.whl", hash = "sha256:0e22f846f4211383e6a416d04b4c13ed174d24cc5d43f5fd52e7821d0ebc8920"}, + {file = "debugpy-1.8.11.tar.gz", hash = "sha256:6ad2688b69235c43b020e04fecccdf6a96c8943ca9c2fb340b8adc103c655e57"}, ] [[package]] @@ -1315,13 +1316,13 @@ files = [ [[package]] name = "django-silk" -version = "5.3.1" +version = "5.3.2" description = "Silky smooth profiling for the Django Framework" optional = false python-versions = ">=3.9" files = [ - {file = "django_silk-5.3.1-py3-none-any.whl", hash = "sha256:7834580fabea5d9e8a32eabb0d9cb061cc37f0ec057d2933b4da761a53ae1bed"}, - {file = "django_silk-5.3.1.tar.gz", hash = "sha256:aa4ae73a90fcbd5159a810f81e15a0ec010619beab37c82957eb4012fd0016f0"}, + {file = "django_silk-5.3.2-py3-none-any.whl", hash = "sha256:49f1caebfda28b1707f0cfef524e0476beb82b8c5e40f5ccff7f73a6b4f6d3ac"}, + {file = "django_silk-5.3.2.tar.gz", hash = "sha256:b0db54eebedb8d16f572321bd6daccac0bd3f547ae2618bb45d96fe8fc02229d"}, ] [package.dependencies] @@ -1527,12 +1528,13 @@ validation = ["swagger-spec-validator (>=2.1.0)"] [[package]] name = "editorconfig" -version = "0.12.4" +version = "0.17.0" description = "EditorConfig File Locator and Interpreter for Python" optional = false python-versions = "*" files = [ - {file = "EditorConfig-0.12.4.tar.gz", hash = "sha256:24857fa1793917dd9ccf0c7810a07e05404ce9b823521c7dce22a4fb5d125f80"}, + {file = "EditorConfig-0.17.0-py3-none-any.whl", hash = "sha256:fe491719c5f65959ec00b167d07740e7ffec9a3f362038c72b289330b9991dfc"}, + {file = "editorconfig-0.17.0.tar.gz", hash = "sha256:8739052279699840065d3a9f5c125d7d5a98daeefe53b0e5274261d77cb49aa2"}, ] [[package]] @@ -2305,13 +2307,13 @@ requests = "*" [[package]] name = "mkdocs-material" -version = "9.5.47" +version = "9.5.49" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.47-py3-none-any.whl", hash = "sha256:53fb9c9624e7865da6ec807d116cd7be24b3cb36ab31b1d1d1a9af58c56009a2"}, - {file = "mkdocs_material-9.5.47.tar.gz", hash = "sha256:fc3b7a8e00ad896660bd3a5cc12ca0cb28bdc2bcbe2a946b5714c23ac91b0ede"}, + {file = "mkdocs_material-9.5.49-py3-none-any.whl", hash = "sha256:c3c2d8176b18198435d3a3e119011922f3e11424074645c24019c2dcf08a360e"}, + {file = "mkdocs_material-9.5.49.tar.gz", hash = "sha256:3671bb282b4f53a1c72e08adbe04d2481a98f85fed392530051f80ff94a9621d"}, ] [package.dependencies] @@ -2466,13 +2468,13 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} [[package]] name = "mypy-boto3-sesv2" -version = "1.35.53" -description = "Type annotations for boto3.SESV2 1.35.53 service generated with mypy-boto3-builder 8.1.4" +version = "1.35.79" +description = "Type annotations for boto3 SESV2 1.35.79 service generated with mypy-boto3-builder 8.6.3" optional = false python-versions = ">=3.8" files = [ - {file = "mypy_boto3_sesv2-1.35.53-py3-none-any.whl", hash = "sha256:7fdb2cf32dad7e5283d83c75e5a330c1eca3e76a2bab5ded0bea3e5c0d334178"}, - {file = "mypy_boto3_sesv2-1.35.53.tar.gz", hash = "sha256:8c8c4515c5d8260c311fbf209cc1f121a416785f3c0d3eed1f5a6e9141290dce"}, + {file = "mypy_boto3_sesv2-1.35.79-py3-none-any.whl", hash = "sha256:b155fe18884e67d96957717f9772821a8b0bf95e59c0d55268a4327dc20d4036"}, + {file = "mypy_boto3_sesv2-1.35.79.tar.gz", hash = "sha256:27bb00bc0c09f6abd7a9752ab004eda82a0fc2c6268fdbc920448ad1083f46c1"}, ] [package.dependencies] @@ -2796,12 +2798,12 @@ files = [ [[package]] name = "pulumi" -version = "3.142.0" +version = "3.143.0" description = "Pulumi's Python SDK" optional = false python-versions = ">=3.8" files = [ - {file = "pulumi-3.142.0-py3-none-any.whl", hash = "sha256:659a58a26aa36f585046750f01c3f20113c71fa065ee5a4487c775a9ef98b9b2"}, + {file = "pulumi-3.143.0-py3-none-any.whl", hash = "sha256:04d20d019b8ff2e3f9492c7bf1ca11262e6dd68c4395d2c0ed669cfa7860f1d2"}, ] [package.dependencies] @@ -2815,13 +2817,13 @@ six = ">=1.12,<2.0" [[package]] name = "pulumi-aws" -version = "6.63.0" +version = "6.65.0" description = "A Pulumi package for creating and managing Amazon Web Services (AWS) cloud resources." optional = false python-versions = ">=3.8" files = [ - {file = "pulumi_aws-6.63.0-py3-none-any.whl", hash = "sha256:5d4f3b6034cc3fc2ca2b0137d4cb668a52116b37215ab79d5872a66ffae0e664"}, - {file = "pulumi_aws-6.63.0.tar.gz", hash = "sha256:51b36a36c2bf9876f088e4ed7283b01f56e2d5a38d5a809e61992a2617030ff2"}, + {file = "pulumi_aws-6.65.0-py3-none-any.whl", hash = "sha256:1472b33a92504fa9758a02db35cfd3cfb5d34e4913fd0edc4dac9f8cf5c44938"}, + {file = "pulumi_aws-6.65.0.tar.gz", hash = "sha256:2cdd5750a2827b1e887aad921391f7914b3e493c2fe5d12fbd1f835254ac17ed"}, ] [package.dependencies] @@ -2854,18 +2856,18 @@ files = [ [[package]] name = "pydantic" -version = "2.10.3" +version = "2.10.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, - {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, + {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, + {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.27.1" +pydantic-core = "2.27.2" typing-extensions = ">=4.12.2" [package.extras] @@ -2874,111 +2876,111 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.27.1" +version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, - {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, - {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, - {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, - {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, - {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, - {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, - {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, - {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, - {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, - {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, - {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, - {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, - {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, - {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, - {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, - {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, - {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, - {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, - {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, - {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, - {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, - {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, - {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, - {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, - {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, - {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, - {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"}, - {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"}, - {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"}, - {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"}, - {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"}, - {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"}, - {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, - {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, - {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, - {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, - {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, - {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, - {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, - {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, - {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"}, + {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"}, + {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"}, + {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"}, + {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, ] [package.dependencies] @@ -2986,13 +2988,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-settings" -version = "2.6.1" +version = "2.7.0" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87"}, - {file = "pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0"}, + {file = "pydantic_settings-2.7.0-py3-none-any.whl", hash = "sha256:e00c05d5fa6cbbb227c84bd7487c5c1065084119b750df7c8c1a554aed236eb5"}, + {file = "pydantic_settings-2.7.0.tar.gz", hash = "sha256:ac4bfd4a36831a48dbf8b2d9325425b549a0a6f18cea118436d728eb4f1c4d66"}, ] [package.dependencies] @@ -3445,13 +3447,13 @@ prompt_toolkit = ">=2.0,<=3.0.36" [[package]] name = "redis" -version = "5.2.0" +version = "5.2.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" files = [ - {file = "redis-5.2.0-py3-none-any.whl", hash = "sha256:ae174f2bb3b1bf2b09d54bf3e51fbc1469cf6c10aa03e21141f51969801a7897"}, - {file = "redis-5.2.0.tar.gz", hash = "sha256:0b1087665a771b1ff2e003aa5bdd354f15a70c9e25d5a7dbf9c722c16528a7b0"}, + {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, + {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, ] [package.dependencies] @@ -3782,13 +3784,13 @@ saml = ["python3-saml (>=1.5.0)"] [[package]] name = "sqlparse" -version = "0.5.2" +version = "0.5.3" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" files = [ - {file = "sqlparse-0.5.2-py3-none-any.whl", hash = "sha256:e99bc85c78160918c3e1d9230834ab8d80fc06c59d03f8db2618f65f65dda55e"}, - {file = "sqlparse-0.5.2.tar.gz", hash = "sha256:9e37b35e16d1cc652a2545f0997c1deb23ea28fa1f3eefe609eee3063c3b105f"}, + {file = "sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca"}, + {file = "sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272"}, ] [package.extras] @@ -3798,7 +3800,7 @@ doc = ["sphinx"] [[package]] name = "strelix-core" version = "0.0.1" -description = "github.com/Strelix/Core" +description = "github.com/Strelix/core" optional = false python-versions = ">=3.10,<4.0" files = [] @@ -3949,13 +3951,13 @@ telegram = ["requests"] [[package]] name = "types-awscrt" -version = "0.23.3" +version = "0.23.6" description = "Type annotations and code completion for awscrt" optional = false python-versions = ">=3.8" files = [ - {file = "types_awscrt-0.23.3-py3-none-any.whl", hash = "sha256:cc0057885cb7ce1e66856123a4c2861b051e9f0716b1767ad72bfe4ca26bbcd4"}, - {file = "types_awscrt-0.23.3.tar.gz", hash = "sha256:043c0ae0fe5d272618294cbeaf1c349a654a9f7c00121be64d27486933ac4a26"}, + {file = "types_awscrt-0.23.6-py3-none-any.whl", hash = "sha256:fbf9c221af5607b24bf17f8431217ce8b9a27917139edbc984891eb63fd5a593"}, + {file = "types_awscrt-0.23.6.tar.gz", hash = "sha256:405bce8c281f9e7c6c92a229225cc0bf10d30729a6a601123213389bd524b8b1"}, ] [[package]] @@ -3996,13 +3998,13 @@ files = [ [[package]] name = "types-protobuf" -version = "5.28.3.20241203" +version = "5.29.1.20241207" description = "Typing stubs for protobuf" optional = false python-versions = ">=3.8" files = [ - {file = "types_protobuf-5.28.3.20241203-py3-none-any.whl", hash = "sha256:5367632a4785394b0504e7c1e4d54a2dceeae9cd4f73a705d0f6499fc99cecb1"}, - {file = "types_protobuf-5.28.3.20241203.tar.gz", hash = "sha256:2e1c962bdf76c576506b5fc0678d28efa6652a54c04ae46562a6209e777bd789"}, + {file = "types_protobuf-5.29.1.20241207-py3-none-any.whl", hash = "sha256:92893c42083e9b718c678badc0af7a9a1307b92afe1599e5cba5f3d35b668b2f"}, + {file = "types_protobuf-5.29.1.20241207.tar.gz", hash = "sha256:2ebcadb8ab3ef2e3e2f067e0882906d64ba0dc65fc5b0fd7a8b692315b4a0be9"}, ] [[package]] @@ -4018,13 +4020,13 @@ files = [ [[package]] name = "types-pycurl" -version = "7.45.3.20240421" +version = "7.45.4.20241216" description = "Typing stubs for pycurl" optional = false python-versions = ">=3.8" files = [ - {file = "types-pycurl-7.45.3.20240421.tar.gz", hash = "sha256:0d5b3b9243e3940e26b782c1cdc0b2438d244ee2e8dcde09cf523635a42ff406"}, - {file = "types_pycurl-7.45.3.20240421-py3-none-any.whl", hash = "sha256:c20b56426cd67e4a80682d054391f6bab725d5fc31baae0c3eef9bbec00629c3"}, + {file = "types_pycurl-7.45.4.20241216-py3-none-any.whl", hash = "sha256:d50eb912216ae2bab4f581bf44cd013f24002f001077a38fcc662a25b525342c"}, + {file = "types_pycurl-7.45.4.20241216.tar.gz", hash = "sha256:ce5ad0978e09ef43ee97b54ac68c183eda27ee0c981f70ec3f1b867ff07611f4"}, ] [[package]] @@ -4044,13 +4046,13 @@ types-setuptools = "*" [[package]] name = "types-python-dateutil" -version = "2.9.0.20241003" +version = "2.9.0.20241206" description = "Typing stubs for python-dateutil" optional = false python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.9.0.20241003.tar.gz", hash = "sha256:58cb85449b2a56d6684e41aeefb4c4280631246a0da1a719bdbe6f3fb0317446"}, - {file = "types_python_dateutil-2.9.0.20241003-py3-none-any.whl", hash = "sha256:250e1d8e80e7bbc3a6c99b907762711d1a1cdd00e978ad39cb5940f6f0a87f3d"}, + {file = "types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53"}, + {file = "types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb"}, ] [[package]] @@ -4135,21 +4137,21 @@ files = [ [[package]] name = "typos" -version = "1.28.2" +version = "1.28.4" description = "Source Code Spelling Correction" optional = false python-versions = ">=3.7" files = [ - {file = "typos-1.28.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2ce911fabe95b4900aa063c159aee3bb883c7412c058b161a7f15d3ae293dcc8"}, - {file = "typos-1.28.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8643a81aec9d36a719b421350e7ab14c3db2441b4527a1ab146b7a1e98f7da27"}, - {file = "typos-1.28.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8299d3b89d44c0b544dc37edd419c2424ce743d6aaaa1df7bccac4f98fb1383"}, - {file = "typos-1.28.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b3441c505f31baff0fd8ed3938c6d26102b8842a3321b2eddd139e6563f77a5"}, - {file = "typos-1.28.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cadf5c68d4818cedb0dde7ab8a07a7f02c4f184859a869871ec14ab4d710ad1"}, - {file = "typos-1.28.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0d1151b202ab11c0a75bd986a0e2afa28e516a7d6180e474a642ddc5d8437091"}, - {file = "typos-1.28.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:91eb18d188f9f62dba5c98d4d8d799b7160a98f2dd09d15153be8c993d17d1a9"}, - {file = "typos-1.28.2-py3-none-win32.whl", hash = "sha256:8af5d31dd79c3f84c161f8f0245224ed8523fee67107a13651a2257a7fc95ce8"}, - {file = "typos-1.28.2-py3-none-win_amd64.whl", hash = "sha256:74948f6c264181ee2881336dbb944dcbf2d168bee661d8d133fa9946f74a5098"}, - {file = "typos-1.28.2.tar.gz", hash = "sha256:c9adf7d20605fd59c27852050a4cb483dc2004fb61bca3f09768ca368d0308d6"}, + {file = "typos-1.28.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0bb4945868432848bdb378137411f0c5d6f7f763e4da64b84b037ad2392b45f8"}, + {file = "typos-1.28.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:54d507db5439e65ebb36a15551ba0fd23d317ed6d4212d0b866a5310a14d8841"}, + {file = "typos-1.28.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bacfb01a2ab60b146f1412252f327e58e32a430613a761d76dbcc6e275ecffe3"}, + {file = "typos-1.28.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3bc312a10df63211b4f8730d551bc086f71ec5fb7a0a587a50f16c3902edf76"}, + {file = "typos-1.28.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f605b3bb8c928cc0a0d46b29335d400630d43da0a9977bc890987a6cc175420a"}, + {file = "typos-1.28.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d854c5f854304efb959d7fe56fef5720163738687a6db6232bbd951ee2190167"}, + {file = "typos-1.28.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ce87ddde847c535354dbe036691473fc6029f2c895f47340167874185b14bb29"}, + {file = "typos-1.28.4-py3-none-win32.whl", hash = "sha256:92a1a2eaa88f682f9f23152d3f20c932d64e18390819e69b2d006c3e83afe3e3"}, + {file = "typos-1.28.4-py3-none-win_amd64.whl", hash = "sha256:23dbb854917e4d8eaba6ff364d4849d09fc70faeb0f88bc35c814b622b6d045a"}, + {file = "typos-1.28.4.tar.gz", hash = "sha256:7afd8ad79ab8b84f7adb12350d5630abc5e061c8a76802ddbc29eea256689600"}, ] [[package]] @@ -4335,4 +4337,4 @@ test = ["coverage (>=5.3)", "tomli (>=2.0.1)", "tox"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "a41b52ec2a03574be2144fdf7e433f5202f60749c3694dab85677b6fd6a2beca" +content-hash = "d9dc57b78282a853554f645aa31b18f7537a2ef7e6fb3f915d679a4e5d38c718" diff --git a/pyproject.toml b/pyproject.toml index f26ca8305..8c25d98fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,6 @@ authors = ["TreyWW"] readme = "README.md" [tool.poetry.dependencies] -strelix-core = { path = "../core/", develop = true } python = ">=3.10,<4.0" bleach = "6.1.0" # used for HTML sanitation boto3 = "1.34.76" # AWS @@ -24,6 +23,7 @@ drf-yasg = "^1.21.7" setuptools = "^70.1.1" xhtml2pdf = "^0.2.16" stripe = "^10.8.0" +strelix-core = {path = "E:/WebDev/core", develop = true} [tool.poetry.group.mypy.dependencies] mypy = "1.7.1" django-stubs = { version = "4.2.7" } diff --git a/backend/core/service/boto3/scheduler/__init__.py b/settings/__init__.py similarity index 100% rename from backend/core/service/boto3/scheduler/__init__.py rename to settings/__init__.py diff --git a/settings/helpers.py b/settings/helpers.py index 2b225ad80..bedadfd2f 100644 --- a/settings/helpers.py +++ b/settings/helpers.py @@ -15,7 +15,7 @@ SendBulkEmailResponseTypeDef, ) -from backend.core.types.emails import ( +from core.types.emails import ( SingleEmailInput, BulkTemplatedEmailInput, SingleTemplatedEmailContent, diff --git a/settings/local_settings.py b/settings/local_settings.py index 203805b82..cc995ea61 100644 --- a/settings/local_settings.py +++ b/settings/local_settings.py @@ -36,13 +36,14 @@ print(f"[BACKEND] Using {DB_TYPE} database: {os.environ.get('DATABASE_NAME')}") else: + SQLITE_PATH = os.environ.get("SQLITE_PATH", BASE_DIR / "db.sqlite3") DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", + "NAME": SQLITE_PATH, } } - print("[BACKEND] Using sqlite3 database", flush=True) + print(f"[BACKEND] Using sqlite3 database with path {SQLITE_PATH}", flush=True) local_allowed_host = os.environ.get("LOCAL_ALLOWED_HOST") local_allowed_hosts = [host for host in [local_allowed_host] if host is not None] diff --git a/settings/settings.py b/settings/settings.py index bd163237b..681d3dc88 100644 --- a/settings/settings.py +++ b/settings/settings.py @@ -75,7 +75,7 @@ REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ # "rest_framework.authentication.TokenAuthentication", - "backend.core.api.public.authentication.CustomBearerAuthentication" # also adds custom model + "core.api.public.authentication.CustomBearerAuthentication" # also adds custom model ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", @@ -89,7 +89,7 @@ SWAGGER_SETTINGS = { "USE_SESSION_AUTH": False, - "DEFAULT_INFO": "backend.core.api.public.swagger_ui.INFO", + "DEFAULT_INFO": "core.api.public.swagger_ui.INFO", "SECURITY_DEFINITIONS": {"Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"}}, } @@ -287,7 +287,7 @@ } } -AUTH_USER_MODEL = "backend.User" +AUTH_USER_MODEL = "core.User" LANGUAGE_CODE = "en-us" @@ -323,7 +323,7 @@ # SOCIAL_AUTH_LOGIN_URL = "/login/external/" # SOCIAL_AUTH_NEW_USER_REDIRECT_URL = "/login/external/new_user/" # SOCIAL_AUTH_LOGIN_REDIRECT_URL = "/" -SOCIAL_AUTH_USER_MODEL = "backend.User" +SOCIAL_AUTH_USER_MODEL = "core.User" AWS_TAGS_APP_NAME = get_var("AWS_TAGS_APP_NAME", default="myfinances") diff --git a/tests/api/test_account_settings.py b/tests/api/test_account_settings.py index 1a272ea1c..ebf11615d 100644 --- a/tests/api/test_account_settings.py +++ b/tests/api/test_account_settings.py @@ -10,7 +10,7 @@ def setUp(self): super().setUp() self.url_path = "/api/settings/account_preferences/" self.url_name = "api:settings:account_preferences" - self.view_function_path = "backend.core.api.settings.preferences.update_account_preferences" + self.view_function_path = "core.api.settings.preferences.update_account_preferences" # Test that the URL resolves to the correct view function def test_url_matches_api(self): diff --git a/tests/handler.py b/tests/handler.py index de5ea3d2a..5af52fdfd 100644 --- a/tests/handler.py +++ b/tests/handler.py @@ -17,7 +17,7 @@ def assert_url_matches_view(url_path, url_name, view_function_path): Args: url_path (str): The full URL path of the view. (e.g. api//clients/fetch/) url_name (str): The name of the URL to reverse. (e.g. api:clients:fetch) - view_function_path (str): The expected path of the view function (e.g., "backend.core.api.clients.fetch.fetch_all_clients"). + view_function_path (str): The expected path of the view function (e.g., "core.api.clients.fetch.fetch_all_clients"). """ resolved_func = resolve(url_path).func resolved_func_name = f"{resolved_func.__module__}.{resolved_func.__name__}" diff --git a/tests/views/test_other.py b/tests/views/test_other.py deleted file mode 100644 index 0e9417715..000000000 --- a/tests/views/test_other.py +++ /dev/null @@ -1,26 +0,0 @@ -from django.urls import reverse, resolve - -from backend.core.views.auth.login import logout_view -from tests.handler import ViewTestCase - - -class OtherItemsTestCase(ViewTestCase): - def test_logout_from_url_not_logged_in_redirects(self): - response = self.client.get(reverse("auth:logout")) - self.assertEqual(response.status_code, 302) - self.assertEqual(resolve(reverse("auth:logout")).func, logout_view) - - def test_logout_from_url_logged_in(self): - self.login_user() - response = self.client.get(reverse("auth:logout")) - self.assertNotIn("_auth_user_id", self.client.session) - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse("auth:login")) - - def testlogin_view_matches_with_urls_view(self): - # func = resolve("/login/").func - # func_name = f"{func.__module__}.{func.__name__}" - self.assertEqual( - "/auth/login/", - reverse("auth:login"), - ) From fda48b3aeba28078f117e3e4c3447fe23d872a8c Mon Sep 17 00:00:00 2001 From: Trey <73353716+TreyWW@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:15:43 +0000 Subject: [PATCH 03/21] Removed migrations for a fresh start, added extended templates, made a start to modal revamp Signed-off-by: Trey <73353716+TreyWW@users.noreply.github.com> --- backend/context_processors.py | 4 +- backend/migrations/0001_initial.py | 749 +++++++++++------- .../0002_alter_receipt_date_uploaded.py | 17 - backend/migrations/0002_initial.py | 420 ++++++++++ ...client_company_client_is_representative.py | 22 - .../0004_invoice_client_is_representative.py | 17 - backend/migrations/0005_invoiceproduct.py | 42 - ...erchant_store_receipt_purchase_category.py | 25 - ...7_alter_receipt_merchant_store_and_more.py | 22 - .../migrations/0008_receiptdownloadtoken.py | 47 -- .../0009_alter_invoice_sort_code.py | 17 - .../migrations/0010_user_logged_in_as_team.py | 23 - backend/migrations/0011_alter_team_leader.py | 24 - ...rganization_alter_receipt_user_and_more.py | 44 - ...ganization_client_organization_and_more.py | 82 -- ...ion_extra_type_notification_extra_value.py | 23 - ...alter_notification_user_alter_team_name.py | 29 - ...voice_logo_alter_receipt_image_and_more.py | 43 - backend/migrations/0017_featureflags.py | 30 - backend/migrations/0018_user_role.py | 27 - ...019_alter_featureflags_options_and_more.py | 70 -- ...lter_verificationcodes_options_and_more.py | 32 - ...alter_verificationcodes_expiry_and_more.py | 25 - ...alter_verificationcodes_expiry_and_more.py | 30 - .../0023_apikey_invoiceonetimeschedule.py | 64 -- ...pire_invoiceurl_system_created_and_more.py | 30 - ...oiceonetimeschedule_stored_schedule_arn.py | 18 - ...ount_amount_invoice_discount_percentage.py | 26 - backend/migrations/0027_invoice_currency.py | 30 - ...increaserequest_quotaoverrides_and_more.py | 111 --- ...rganization_alter_invoice_user_and_more.py | 30 - .../migrations/0030_alter_invoice_items.py | 18 - ...ags_description_alter_featureflags_name.py | 25 - ...fied_alter_client_organization_and_more.py | 98 --- .../0033_alter_auditlog_organization.py | 19 - ...il_quotaincreaserequest_reason_and_more.py | 66 -- .../migrations/0035_client_contact_method.py | 18 - ...036_alter_client_address_clientdefaults.py | 61 -- backend/migrations/0036_apiauthtoken.py | 23 - .../migrations/0037_merge_20240619_2223.py | 13 - .../0038_alter_apiauthtoken_options.py | 17 - ...ctive_apiauthtoken_description_and_more.py | 59 -- ...token_scopes_apiauthtoken_team_and_more.py | 54 -- .../0041_alter_apiauthtoken_user.py | 20 - ...piauthtoken_key_apiauthtoken_hashed_key.py | 39 - ...ation_remove_apiauthtoken_team_and_more.py | 137 ---- ...ltvalues_delete_clientdefaults_and_more.py | 88 -- .../0045_usersettings_disabled_features.py | 18 - ...ereminder_boto_schedule_status_and_more.py | 197 ----- ...0047_defaultvalues_default_invoice_logo.py | 21 - ...lter_defaultvalues_default_invoice_logo.py | 21 - backend/migrations/0049_filestoragefile.py | 60 -- backend/migrations/0050_multifileupload.py | 48 -- ...p_subscriptionplan_planfeature_and_more.py | 173 ---- .../0052_filestoragefile_file_uri_path.py | 19 - ...ance_id_alter_planfeature_name_and_more.py | 46 -- .../0054_transferusage_storageusage.py | 57 -- .../0055_remove_planfeature_group_and_more.py | 89 --- .../0056_user_stripe_customer_id.py | 18 - backend/migrations/0057_user_entitlements.py | 18 - ...0058_organization_entitlements_and_more.py | 23 - ...voicerecurringprofile_managers_and_more.py | 89 --- .../0060_user_require_change_password.py | 18 - ...ultvalues_invoice_from_address_and_more.py | 43 - ...es_invoice_account_holder_name_and_more.py | 28 - ...ing_invoices_invoice_cancelled_and_more.py | 36 - ...e_invoice_payment_status_invoice_status.py | 24 - ...r_expire_passwordsecret_active_and_more.py | 42 - ...emove_verificationcodes_expiry_and_more.py | 30 - ...67_remove_apiauthtoken_expired_and_more.py | 27 - ...d_at_invoice_status_updated_at_and_more.py | 36 - .../migrations/0069_alter_auditlog_action.py | 18 - ...0070_remove_invoice_invoice_id_and_more.py | 34 - backend/modals.py | 58 ++ backend/urls.py | 2 +- frontend/templates/base/+left_drawer.html | 103 ++- frontend/templates/base/_head.html | 71 +- frontend/templates/base/auth.html | 38 +- frontend/templates/base/base.html | 59 +- frontend/templates/base/htmx.html | 17 +- frontend/templates/base/topbar/_topbar.html | 117 +-- frontend/templates/pages/dashboard.html | 2 +- settings/settings.py | 2 +- 83 files changed, 1032 insertions(+), 3598 deletions(-) delete mode 100644 backend/migrations/0002_alter_receipt_date_uploaded.py create mode 100644 backend/migrations/0002_initial.py delete mode 100644 backend/migrations/0003_client_company_client_is_representative.py delete mode 100644 backend/migrations/0004_invoice_client_is_representative.py delete mode 100644 backend/migrations/0005_invoiceproduct.py delete mode 100644 backend/migrations/0006_receipt_merchant_store_receipt_purchase_category.py delete mode 100644 backend/migrations/0007_alter_receipt_merchant_store_and_more.py delete mode 100644 backend/migrations/0008_receiptdownloadtoken.py delete mode 100644 backend/migrations/0009_alter_invoice_sort_code.py delete mode 100644 backend/migrations/0010_user_logged_in_as_team.py delete mode 100644 backend/migrations/0011_alter_team_leader.py delete mode 100644 backend/migrations/0012_receipt_organization_alter_receipt_user_and_more.py delete mode 100644 backend/migrations/0013_auditlog_organization_client_organization_and_more.py delete mode 100644 backend/migrations/0014_notification_extra_type_notification_extra_value.py delete mode 100644 backend/migrations/0015_alter_notification_user_alter_team_name.py delete mode 100644 backend/migrations/0016_alter_invoice_logo_alter_receipt_image_and_more.py delete mode 100644 backend/migrations/0017_featureflags.py delete mode 100644 backend/migrations/0018_user_role.py delete mode 100644 backend/migrations/0019_alter_featureflags_options_and_more.py delete mode 100644 backend/migrations/0020_alter_verificationcodes_options_and_more.py delete mode 100644 backend/migrations/0021_alter_verificationcodes_expiry_and_more.py delete mode 100644 backend/migrations/0022_loginlog_service_alter_verificationcodes_expiry_and_more.py delete mode 100644 backend/migrations/0023_apikey_invoiceonetimeschedule.py delete mode 100644 backend/migrations/0024_invoiceurl_never_expire_invoiceurl_system_created_and_more.py delete mode 100644 backend/migrations/0025_alter_invoiceonetimeschedule_stored_schedule_arn.py delete mode 100644 backend/migrations/0026_invoice_discount_amount_invoice_discount_percentage.py delete mode 100644 backend/migrations/0027_invoice_currency.py delete mode 100644 backend/migrations/0028_quotalimit_quotaincreaserequest_quotaoverrides_and_more.py delete mode 100644 backend/migrations/0029_alter_invoice_organization_alter_invoice_user_and_more.py delete mode 100644 backend/migrations/0030_alter_invoice_items.py delete mode 100644 backend/migrations/0031_featureflags_description_alter_featureflags_name.py delete mode 100644 backend/migrations/0032_client_email_verified_alter_client_organization_and_more.py delete mode 100644 backend/migrations/0033_alter_auditlog_organization.py delete mode 100644 backend/migrations/0034_invoice_client_email_quotaincreaserequest_reason_and_more.py delete mode 100644 backend/migrations/0035_client_contact_method.py delete mode 100644 backend/migrations/0036_alter_client_address_clientdefaults.py delete mode 100644 backend/migrations/0036_apiauthtoken.py delete mode 100644 backend/migrations/0037_merge_20240619_2223.py delete mode 100644 backend/migrations/0038_alter_apiauthtoken_options.py delete mode 100644 backend/migrations/0039_apiauthtoken_active_apiauthtoken_description_and_more.py delete mode 100644 backend/migrations/0040_apiauthtoken_scopes_apiauthtoken_team_and_more.py delete mode 100644 backend/migrations/0041_alter_apiauthtoken_user.py delete mode 100644 backend/migrations/0042_remove_apiauthtoken_key_apiauthtoken_hashed_key.py delete mode 100644 backend/migrations/0043_rename_team_organization_remove_apiauthtoken_team_and_more.py delete mode 100644 backend/migrations/0044_defaultvalues_delete_clientdefaults_and_more.py delete mode 100644 backend/migrations/0045_usersettings_disabled_features.py delete mode 100644 backend/migrations/0046_rename_status_invoicereminder_boto_schedule_status_and_more.py delete mode 100644 backend/migrations/0047_defaultvalues_default_invoice_logo.py delete mode 100644 backend/migrations/0048_alter_defaultvalues_default_invoice_logo.py delete mode 100644 backend/migrations/0049_filestoragefile.py delete mode 100644 backend/migrations/0050_multifileupload.py delete mode 100644 backend/migrations/0051_planfeaturegroup_subscriptionplan_planfeature_and_more.py delete mode 100644 backend/migrations/0052_filestoragefile_file_uri_path.py delete mode 100644 backend/migrations/0053_usage_instance_id_alter_planfeature_name_and_more.py delete mode 100644 backend/migrations/0054_transferusage_storageusage.py delete mode 100644 backend/migrations/0055_remove_planfeature_group_and_more.py delete mode 100644 backend/migrations/0056_user_stripe_customer_id.py delete mode 100644 backend/migrations/0057_user_entitlements.py delete mode 100644 backend/migrations/0058_organization_entitlements_and_more.py delete mode 100644 backend/migrations/0059_alter_invoicerecurringprofile_managers_and_more.py delete mode 100644 backend/migrations/0060_user_require_change_password.py delete mode 100644 backend/migrations/0061_defaultvalues_invoice_from_address_and_more.py delete mode 100644 backend/migrations/0062_defaultvalues_invoice_account_holder_name_and_more.py delete mode 100644 backend/migrations/0063_defaultvalues_email_template_recurring_invoices_invoice_cancelled_and_more.py delete mode 100644 backend/migrations/0064_remove_invoice_payment_status_invoice_status.py delete mode 100644 backend/migrations/0065_remove_invoiceurl_never_expire_passwordsecret_active_and_more.py delete mode 100644 backend/migrations/0066_delete_apikey_remove_verificationcodes_expiry_and_more.py delete mode 100644 backend/migrations/0067_remove_apiauthtoken_expired_and_more.py delete mode 100644 backend/migrations/0068_invoice_created_at_invoice_status_updated_at_and_more.py delete mode 100644 backend/migrations/0069_alter_auditlog_action.py delete mode 100644 backend/migrations/0070_remove_invoice_invoice_id_and_more.py create mode 100644 backend/modals.py diff --git a/backend/context_processors.py b/backend/context_processors.py index 5ba9e8f12..4dbac686c 100644 --- a/backend/context_processors.py +++ b/backend/context_processors.py @@ -63,7 +63,9 @@ def get_git_revision(base_path): data["day_names_monday_first"] = [day for day in calendar.day_name] if hasattr(request, "htmx") and request.htmx.boosted: - data["base"] = "core/base/htmx.html" + data["base"] = "base/htmx.html" + else: + data["base"] = "base/base.html" return data diff --git a/backend/migrations/0001_initial.py b/backend/migrations/0001_initial.py index b6572b794..a7107828c 100644 --- a/backend/migrations/0001_initial.py +++ b/backend/migrations/0001_initial.py @@ -1,24 +1,23 @@ -# Generated by Django 4.2.5 on 2023-11-25 20:13 +# Generated by Django 5.1.4 on 2024-12-21 22:26 -import django.contrib.auth.validators -import django.utils.timezone +import backend.data.default_email_templates +import core.models +import django.core.validators +import django.db.models.manager import shortuuid.django_fields -from django.conf import settings +import uuid from django.db import migrations, models -import backend.models - class Migration(migrations.Migration): + initial = True - dependencies = [ - ("auth", "0012_alter_user_first_name_max_length"), - ] + dependencies = [] operations = [ migrations.CreateModel( - name="User", + name="Client", fields=[ ( "id", @@ -29,96 +28,155 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("password", models.CharField(max_length=128, verbose_name="password")), + ("active", models.BooleanField(default=True)), + ("name", models.CharField(max_length=64)), ( - "last_login", - models.DateTimeField(blank=True, null=True, verbose_name="last login"), + "phone_number", + models.CharField(blank=True, max_length=100, null=True), ), + ("email", models.EmailField(blank=True, max_length=254, null=True)), + ("email_verified", models.BooleanField(default=False)), + ("company", models.CharField(blank=True, max_length=100, null=True)), ( - "is_superuser", - models.BooleanField( - default=False, - help_text="Designates that this user has all permissions without explicitly assigning them.", - verbose_name="superuser status", + "contact_method", + models.CharField(blank=True, max_length=100, null=True), + ), + ("is_representative", models.BooleanField(default=False)), + ("address", models.TextField(blank=True, max_length=100, null=True)), + ("city", models.CharField(blank=True, max_length=100, null=True)), + ("country", models.CharField(blank=True, max_length=100, null=True)), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="DefaultValues", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", ), ), ( - "username", + "currency", models.CharField( - error_messages={"unique": "A user with that username already exists."}, - help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", - max_length=150, - unique=True, - validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], - verbose_name="username", + choices=[ + ("GBP", "British Pound Sterling"), + ("EUR", "Euro"), + ("USD", "United States Dollar"), + ("JPY", "Japanese Yen"), + ("INR", "Indian Rupee"), + ("AUD", "Australian Dollar"), + ("CAD", "Canadian Dollar"), + ], + default="GBP", + max_length=3, ), ), + ("invoice_due_date_value", models.PositiveSmallIntegerField(default=7)), ( - "first_name", - models.CharField(blank=True, max_length=150, verbose_name="first name"), + "invoice_due_date_type", + models.CharField( + choices=[ + ("days_after", "Days After"), + ("date_following", "Date Following"), + ("date_current", "Date Current"), + ], + default="days_after", + max_length=20, + ), ), + ("invoice_date_value", models.PositiveSmallIntegerField(default=15)), ( - "last_name", - models.CharField(blank=True, max_length=150, verbose_name="last name"), + "invoice_date_type", + models.CharField( + choices=[ + ("day_of_month", "Day Of Month"), + ("days_after", "Days After"), + ], + default="day_of_month", + max_length=20, + ), ), ( - "email", - models.EmailField(blank=True, max_length=254, verbose_name="email address"), + "invoice_from_name", + models.CharField(blank=True, max_length=100, null=True), ), ( - "is_staff", - models.BooleanField( - default=False, - help_text="Designates whether the user can log into this admin site.", - verbose_name="staff status", - ), + "invoice_from_company", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "invoice_from_address", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "invoice_from_city", + models.CharField(blank=True, max_length=100, null=True), ), ( - "is_active", - models.BooleanField( - default=True, - help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", - verbose_name="active", + "invoice_from_county", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "invoice_from_country", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "invoice_from_email", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "invoice_account_number", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "invoice_sort_code", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "invoice_account_holder_name", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "email_template_recurring_invoices_invoice_created", + models.TextField( + default=backend.data.default_email_templates.recurring_invoices_invoice_created_default_email_template ), ), ( - "date_joined", - models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined"), + "email_template_recurring_invoices_invoice_overdue", + models.TextField( + default=backend.data.default_email_templates.recurring_invoices_invoice_overdue_default_email_template + ), ), ( - "groups", - models.ManyToManyField( - blank=True, - help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", - related_name="user_set", - related_query_name="user", - to="auth.group", - verbose_name="groups", + "email_template_recurring_invoices_invoice_cancelled", + models.TextField( + default=backend.data.default_email_templates.recurring_invoices_invoice_cancelled_default_email_template ), ), ( - "user_permissions", - models.ManyToManyField( + "default_invoice_logo", + models.ImageField( blank=True, - help_text="Specific permissions for this user.", - related_name="user_set", - related_query_name="user", - to="auth.permission", - verbose_name="user permissions", + null=True, + storage=core.models._private_storage, + upload_to="invoice_logos/", ), ), ], options={ - "verbose_name": "user", - "verbose_name_plural": "users", "abstract": False, }, - managers=[ - ("objects", backend.models.CustomUserManager()), - ], ), migrations.CreateModel( - name="Client", + name="FileStorageFile", fields=[ ( "id", @@ -129,24 +187,20 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("active", models.BooleanField(default=True)), - ("name", models.CharField(max_length=64)), ( - "phone_number", - models.CharField(blank=True, max_length=100, null=True), - ), - ("email", models.EmailField(blank=True, max_length=254, null=True)), - ("address", models.CharField(blank=True, max_length=100, null=True)), - ("city", models.CharField(blank=True, max_length=100, null=True)), - ("country", models.CharField(blank=True, max_length=100, null=True)), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, + "file", + models.FileField( + storage=core.models._private_storage, + upload_to=core.models.upload_to_user_separate_folder, ), ), + ("file_uri_path", models.CharField(max_length=500)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), ], + options={ + "abstract": False, + }, ), migrations.CreateModel( name="Invoice", @@ -160,11 +214,14 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("invoice_id", models.IntegerField(blank=True, null=True, unique=True)), ( "client_name", models.CharField(blank=True, max_length=100, null=True), ), + ( + "client_email", + models.EmailField(blank=True, max_length=254, null=True), + ), ( "client_company", models.CharField(blank=True, max_length=100, null=True), @@ -185,6 +242,7 @@ class Migration(migrations.Migration): "client_country", models.CharField(blank=True, max_length=100, null=True), ), + ("client_is_representative", models.BooleanField(default=False)), ("self_name", models.CharField(blank=True, max_length=100, null=True)), ( "self_company", @@ -203,7 +261,7 @@ class Migration(migrations.Migration): "self_country", models.CharField(blank=True, max_length=100, null=True), ), - ("sort_code", models.CharField(blank=True, max_length=100, null=True)), + ("sort_code", models.CharField(blank=True, max_length=8, null=True)), ( "account_holder_name", models.CharField(blank=True, max_length=100, null=True), @@ -212,42 +270,69 @@ class Migration(migrations.Migration): "account_number", models.CharField(blank=True, max_length=100, null=True), ), - ("reference", models.CharField(blank=True, max_length=100, null=True)), - ( - "invoice_number", - models.CharField(blank=True, max_length=100, null=True), - ), ("vat_number", models.CharField(blank=True, max_length=100, null=True)), ( "logo", - models.ImageField(blank=True, null=True, upload_to="invoice_logos"), + models.ImageField( + blank=True, + null=True, + storage=core.models._private_storage, + upload_to="invoice_logos", + ), ), ("notes", models.TextField(blank=True, null=True)), + ( + "currency", + models.CharField( + choices=[ + ("GBP", "British Pound Sterling"), + ("EUR", "Euro"), + ("USD", "United States Dollar"), + ("JPY", "Japanese Yen"), + ("INR", "Indian Rupee"), + ("AUD", "Australian Dollar"), + ("CAD", "Canadian Dollar"), + ], + default="GBP", + max_length=3, + ), + ), ("date_created", models.DateTimeField(auto_now_add=True)), - ("date_due", models.DateField()), ("date_issued", models.DateField(blank=True, null=True)), ( - "payment_status", + "discount_amount", + models.DecimalField(decimal_places=2, default=0, max_digits=15), + ), + ( + "discount_percentage", + models.DecimalField( + decimal_places=2, + default=0, + max_digits=5, + validators=[django.core.validators.MaxValueValidator(100)], + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("reference", models.CharField(blank=True, max_length=16, null=True)), + ("date_due", models.DateField()), + ( + "status", models.CharField( choices=[ + ("draft", "Draft"), ("pending", "Pending"), ("paid", "Paid"), - ("overdue", "Overdue"), ], - default="pending", + default="draft", max_length=10, ), ), - ( - "client_to", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="backend.client", - ), - ), + ("status_updated_at", models.DateTimeField(auto_now_add=True)), ], + options={ + "abstract": False, + }, ), migrations.CreateModel( name="InvoiceItem", @@ -279,7 +364,7 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name="Team", + name="InvoiceProduct", fields=[ ( "id", @@ -290,22 +375,20 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("name", models.CharField(max_length=100)), - ( - "leader", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), + ("name", models.CharField(max_length=50)), + ("description", models.CharField(max_length=100)), + ("quantity", models.IntegerField()), ( - "members", - models.ManyToManyField(related_name="teams_joined", to=settings.AUTH_USER_MODEL), + "rate", + models.DecimalField(blank=True, decimal_places=2, max_digits=15, null=True), ), ], + options={ + "abstract": False, + }, ), migrations.CreateModel( - name="UserSettings", + name="InvoiceRecurringProfile", fields=[ ( "id", @@ -316,7 +399,98 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("dark_mode", models.BooleanField(default=True)), + ( + "boto_schedule_arn", + models.CharField(blank=True, max_length=2048, null=True), + ), + ( + "boto_schedule_uuid", + models.UUIDField(blank=True, default=None, null=True), + ), + ("boto_last_updated", models.DateTimeField(auto_now=True)), + ("received", models.BooleanField(default=False)), + ( + "boto_schedule_status", + models.CharField( + choices=[ + ("pending", "Pending"), + ("creating", "Creating"), + ("completed", "Completed"), + ("failed", "Failed"), + ("deleting", "Deleting"), + ("cancelled", "Cancelled"), + ], + default="pending", + max_length=100, + ), + ), + ( + "client_name", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "client_email", + models.EmailField(blank=True, max_length=254, null=True), + ), + ( + "client_company", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "client_address", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "client_city", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "client_county", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "client_country", + models.CharField(blank=True, max_length=100, null=True), + ), + ("client_is_representative", models.BooleanField(default=False)), + ("self_name", models.CharField(blank=True, max_length=100, null=True)), + ( + "self_company", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "self_address", + models.CharField(blank=True, max_length=100, null=True), + ), + ("self_city", models.CharField(blank=True, max_length=100, null=True)), + ( + "self_county", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "self_country", + models.CharField(blank=True, max_length=100, null=True), + ), + ("sort_code", models.CharField(blank=True, max_length=8, null=True)), + ( + "account_holder_name", + models.CharField(blank=True, max_length=100, null=True), + ), + ( + "account_number", + models.CharField(blank=True, max_length=100, null=True), + ), + ("vat_number", models.CharField(blank=True, max_length=100, null=True)), + ( + "logo", + models.ImageField( + blank=True, + null=True, + storage=core.models._private_storage, + upload_to="invoice_logos", + ), + ), + ("notes", models.TextField(blank=True, null=True)), ( "currency", models.CharField( @@ -333,51 +507,72 @@ class Migration(migrations.Migration): max_length=3, ), ), + ("date_created", models.DateTimeField(auto_now_add=True)), + ("date_issued", models.DateField(blank=True, null=True)), ( - "profile_picture", - models.ImageField(blank=True, null=True, upload_to="profile_pictures/"), + "discount_amount", + models.DecimalField(decimal_places=2, default=0, max_digits=15), ), ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - related_name="user_profile", - to=settings.AUTH_USER_MODEL, + "discount_percentage", + models.DecimalField( + decimal_places=2, + default=0, + max_digits=5, + validators=[django.core.validators.MaxValueValidator(100)], ), ), - ], - options={ - "verbose_name": "User Settings", - "verbose_name_plural": "User Settings", - }, - ), - migrations.CreateModel( - name="TracebackError", - fields=[ + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("active", models.BooleanField(default=True)), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", + "status", + models.CharField( + choices=[ + ("ongoing", "Ongoing"), + ("paused", "paused"), + ("cancelled", "cancelled"), + ], + default="paused", + max_length=10, ), ), - ("error", models.CharField(max_length=5000, null=True)), - ("date", models.DateTimeField(auto_now=True)), ( - "user", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, + "frequency", + models.CharField( + choices=[ + ("weekly", "Weekly"), + ("monthly", "Monthly"), + ("yearly", "Yearly"), + ], + default="monthly", + max_length=20, ), ), + ("end_date", models.DateField(blank=True, null=True)), + ("due_after_days", models.PositiveSmallIntegerField(default=7)), + ( + "day_of_week", + models.PositiveSmallIntegerField(blank=True, null=True), + ), + ( + "day_of_month", + models.PositiveSmallIntegerField(blank=True, null=True), + ), + ( + "month_of_year", + models.PositiveSmallIntegerField(blank=True, null=True), + ), + ], + options={ + "abstract": False, + }, + managers=[ + ("with_items", django.db.models.manager.Manager()), ], ), migrations.CreateModel( - name="TeamInvitation", + name="InvoiceReminder", fields=[ ( "id", @@ -388,67 +583,85 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("code", models.CharField(max_length=10)), - ("expires", models.DateTimeField(blank=True, null=True)), - ("active", models.BooleanField(default=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), ( - "invited_by", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), + "boto_schedule_arn", + models.CharField(blank=True, max_length=2048, null=True), ), ( - "team", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="team_invitations", - to="backend.team", + "boto_schedule_uuid", + models.UUIDField(blank=True, default=None, null=True), + ), + ("boto_last_updated", models.DateTimeField(auto_now=True)), + ("received", models.BooleanField(default=False)), + ( + "boto_schedule_status", + models.CharField( + choices=[ + ("pending", "Pending"), + ("creating", "Creating"), + ("completed", "Completed"), + ("failed", "Failed"), + ("deleting", "Deleting"), + ("cancelled", "Cancelled"), + ], + default="pending", + max_length=100, ), ), + ("days", models.PositiveIntegerField(blank=True, null=True)), ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="team_invitations", - to=settings.AUTH_USER_MODEL, + "reminder_type", + models.CharField( + choices=[ + ("before_due", "Before Due"), + ("after_due", "After Due"), + ("on_overdue", "On Overdue"), + ], + default="before_due", + max_length=100, ), ), ], options={ - "verbose_name": "Team Invitation", - "verbose_name_plural": "Team Invitations", + "verbose_name": "Invoice Reminder", + "verbose_name_plural": "Invoice Reminders", }, ), migrations.CreateModel( - name="Receipt", + name="InvoiceURL", fields=[ ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", + "expires", + models.DateTimeField( + blank=True, + help_text="When the item will expire", + null=True, + verbose_name="Expires", ), ), - ("name", models.CharField(max_length=100)), - ("image", models.ImageField(upload_to="receipts")), - ("total_price", models.FloatField(blank=True, null=True)), - ("date", models.DateField(blank=True, null=True)), - ("date_uploaded", models.DateTimeField(auto_now=True)), - ("receipt_parsed", models.JSONField(blank=True, null=True)), + ("active", models.BooleanField(default=True)), ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, + "uuid", + shortuuid.django_fields.ShortUUIDField( + alphabet=None, + length=8, + max_length=8, + prefix="", + primary_key=True, + serialize=False, ), ), + ("system_created", models.BooleanField(default=False)), + ("created_on", models.DateTimeField(auto_now_add=True)), ], + options={ + "verbose_name": "Invoice URL", + "verbose_name_plural": "Invoice URLs", + }, ), migrations.CreateModel( - name="PasswordSecret", + name="MonthlyReport", fields=[ ( "id", @@ -459,59 +672,50 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("secret", models.TextField(max_length=300)), - ("expires", models.DateTimeField(blank=True, null=True)), ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - related_name="password_secrets", - to=settings.AUTH_USER_MODEL, - ), + "uuid", + models.UUIDField(default=uuid.uuid4, editable=False, unique=True), ), - ], - ), - migrations.CreateModel( - name="Notification", - fields=[ + ("name", models.CharField(blank=True, max_length=100, null=True)), ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), + "profit", + models.DecimalField(decimal_places=2, default=0, max_digits=15), ), - ("message", models.CharField(max_length=100)), + ("invoices_sent", models.PositiveIntegerField(default=0)), + ("start_date", models.DateField()), + ("end_date", models.DateField()), + ("recurring_customers", models.PositiveIntegerField(default=0)), ( - "action", - models.CharField( - choices=[ - ("normal", "Normal"), - ("modal", "Modal"), - ("redirect", "Redirect"), - ], - default="normal", - max_length=10, - ), + "payments_in", + models.DecimalField(decimal_places=2, default=0, max_digits=15), ), ( - "action_value", - models.CharField(blank=True, max_length=100, null=True), + "payments_out", + models.DecimalField(decimal_places=2, default=0, max_digits=15), ), - ("date", models.DateTimeField(auto_now_add=True)), ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, + "currency", + models.CharField( + choices=[ + ("GBP", "British Pound Sterling"), + ("EUR", "Euro"), + ("USD", "United States Dollar"), + ("JPY", "Japanese Yen"), + ("INR", "Indian Rupee"), + ("AUD", "Australian Dollar"), + ("CAD", "Canadian Dollar"), + ], + default="GBP", + max_length=3, ), ), ], + options={ + "abstract": False, + }, ), migrations.CreateModel( - name="LoginLog", + name="MonthlyReportRow", fields=[ ( "id", @@ -522,66 +726,49 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("date", models.DateTimeField(auto_now_add=True)), + ("date", models.DateField()), + ("reference_number", models.CharField(max_length=100)), + ("item_type", models.CharField(max_length=100)), + ("client_name", models.CharField(blank=True, max_length=64, null=True)), ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), + "paid_in", + models.DecimalField(decimal_places=2, default=0, max_digits=15), + ), + ( + "paid_out", + models.DecimalField(decimal_places=2, default=0, max_digits=15), ), ], ), migrations.CreateModel( - name="InvoiceURL", + name="MultiFileUpload", fields=[ ( - "uuid", - shortuuid.django_fields.ShortUUIDField( - alphabet=None, - length=8, - max_length=8, - prefix="", + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, + verbose_name="ID", ), ), - ("created_on", models.DateTimeField(auto_now_add=True)), - ("expires", models.DateTimeField(blank=True, null=True)), - ("active", models.BooleanField(default=True)), + ("started_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), ( - "created_by", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), + "finished_at", + models.DateTimeField(blank=True, editable=False, null=True), ), ( - "invoice", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="invoice_urls", - to="backend.invoice", - ), + "uuid", + models.UUIDField(default=uuid.uuid4, editable=False, unique=True), ), ], options={ - "verbose_name": "Invoice URL", - "verbose_name_plural": "Invoice URLs", + "abstract": False, }, ), - migrations.AddField( - model_name="invoice", - name="items", - field=models.ManyToManyField(to="backend.invoiceitem"), - ), - migrations.AddField( - model_name="invoice", - name="user", - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), migrations.CreateModel( - name="Error", + name="Receipt", fields=[ ( "id", @@ -592,21 +779,30 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("error", models.CharField(max_length=250, null=True)), - ("error_code", models.CharField(max_length=100, null=True)), - ("error_colour", models.CharField(default="danger", max_length=25)), - ("date", models.DateTimeField(auto_now=True)), + ("name", models.CharField(max_length=100)), ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), + "image", + models.ImageField(storage=core.models._private_storage, upload_to="receipts"), + ), + ("total_price", models.FloatField(blank=True, null=True)), + ("date", models.DateField(blank=True, null=True)), + ("date_uploaded", models.DateTimeField(auto_now_add=True)), + ("receipt_parsed", models.JSONField(blank=True, null=True)), + ( + "merchant_store", + models.CharField(blank=True, max_length=255, null=True), + ), + ( + "purchase_category", + models.CharField(blank=True, max_length=200, null=True), ), ], + options={ + "abstract": False, + }, ), migrations.CreateModel( - name="AuditLog", + name="ReceiptDownloadToken", fields=[ ( "id", @@ -617,16 +813,9 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("action", models.CharField(max_length=100)), - ("date", models.DateTimeField(auto_now_add=True)), ( - "user", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to=settings.AUTH_USER_MODEL, - ), + "token", + models.UUIDField(default=uuid.uuid4, editable=False, unique=True), ), ], ), diff --git a/backend/migrations/0002_alter_receipt_date_uploaded.py b/backend/migrations/0002_alter_receipt_date_uploaded.py deleted file mode 100644 index 190b1949f..000000000 --- a/backend/migrations/0002_alter_receipt_date_uploaded.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.5 on 2023-11-25 22:41 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("backend", "0001_initial"), - ] - - operations = [ - migrations.AlterField( - model_name="receipt", - name="date_uploaded", - field=models.DateTimeField(auto_now_add=True), - ), - ] diff --git a/backend/migrations/0002_initial.py b/backend/migrations/0002_initial.py new file mode 100644 index 000000000..f121c1a32 --- /dev/null +++ b/backend/migrations/0002_initial.py @@ -0,0 +1,420 @@ +# Generated by Django 5.1.4 on 2024-12-21 22:26 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("backend", "0001_initial"), + ("core", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="client", + name="organization", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="core.organization", + ), + ), + migrations.AddField( + model_name="client", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="defaultvalues", + name="client", + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="default_values", + to="backend.client", + ), + ), + migrations.AddField( + model_name="defaultvalues", + name="organization", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="core.organization", + ), + ), + migrations.AddField( + model_name="defaultvalues", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="filestoragefile", + name="last_edited_by", + field=models.ForeignKey( + blank=True, + editable=False, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="files_edited", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="filestoragefile", + name="organization", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="core.organization", + ), + ), + migrations.AddField( + model_name="filestoragefile", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="invoice", + name="client_to", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="backend.client", + ), + ), + migrations.AddField( + model_name="invoice", + name="organization", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="core.organization", + ), + ), + migrations.AddField( + model_name="invoice", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="invoice", + name="items", + field=models.ManyToManyField(blank=True, to="backend.invoiceitem"), + ), + migrations.AddField( + model_name="invoiceproduct", + name="organization", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="core.organization", + ), + ), + migrations.AddField( + model_name="invoiceproduct", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="invoicerecurringprofile", + name="client_to", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="backend.client", + ), + ), + migrations.AddField( + model_name="invoicerecurringprofile", + name="items", + field=models.ManyToManyField(blank=True, to="backend.invoiceitem"), + ), + migrations.AddField( + model_name="invoicerecurringprofile", + name="organization", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="core.organization", + ), + ), + migrations.AddField( + model_name="invoicerecurringprofile", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="invoice", + name="invoice_recurring_profile", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="generated_invoices", + to="backend.invoicerecurringprofile", + ), + ), + migrations.AddField( + model_name="invoicereminder", + name="invoice", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="invoice_reminders", + to="backend.invoice", + ), + ), + migrations.AddField( + model_name="invoiceurl", + name="created_by", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="invoiceurl", + name="invoice", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="invoice_urls", + to="backend.invoice", + ), + ), + migrations.AddField( + model_name="monthlyreport", + name="organization", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="core.organization", + ), + ), + migrations.AddField( + model_name="monthlyreport", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="monthlyreportrow", + name="client", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="backend.client", + ), + ), + migrations.AddField( + model_name="monthlyreport", + name="items", + field=models.ManyToManyField(blank=True, to="backend.monthlyreportrow"), + ), + migrations.AddField( + model_name="multifileupload", + name="files", + field=models.ManyToManyField(related_name="multi_file_uploads", to="backend.filestoragefile"), + ), + migrations.AddField( + model_name="multifileupload", + name="organization", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="core.organization", + ), + ), + migrations.AddField( + model_name="multifileupload", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="receipt", + name="organization", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="core.organization", + ), + ), + migrations.AddField( + model_name="receipt", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="receiptdownloadtoken", + name="file", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="backend.receipt"), + ), + migrations.AddField( + model_name="receiptdownloadtoken", + name="user", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddConstraint( + model_name="client", + constraint=models.CheckConstraint( + condition=models.Q( + models.Q(("organization__isnull", False), ("user__isnull", True)), + models.Q(("organization__isnull", True), ("user__isnull", False)), + _connector="OR", + ), + name="backend_client_check_user_or_organization", + ), + ), + migrations.AddConstraint( + model_name="defaultvalues", + constraint=models.CheckConstraint( + condition=models.Q( + models.Q(("organization__isnull", False), ("user__isnull", True)), + models.Q(("organization__isnull", True), ("user__isnull", False)), + _connector="OR", + ), + name="backend_defaultvalues_check_user_or_organization", + ), + ), + migrations.AddConstraint( + model_name="filestoragefile", + constraint=models.CheckConstraint( + condition=models.Q( + models.Q(("organization__isnull", False), ("user__isnull", True)), + models.Q(("organization__isnull", True), ("user__isnull", False)), + _connector="OR", + ), + name="backend_filestoragefile_check_user_or_organization", + ), + ), + migrations.AddConstraint( + model_name="invoiceproduct", + constraint=models.CheckConstraint( + condition=models.Q( + models.Q(("organization__isnull", False), ("user__isnull", True)), + models.Q(("organization__isnull", True), ("user__isnull", False)), + _connector="OR", + ), + name="backend_invoiceproduct_check_user_or_organization", + ), + ), + migrations.AddConstraint( + model_name="invoicerecurringprofile", + constraint=models.CheckConstraint( + condition=models.Q( + models.Q(("organization__isnull", False), ("user__isnull", True)), + models.Q(("organization__isnull", True), ("user__isnull", False)), + _connector="OR", + ), + name="backend_invoicerecurringprofile_check_user_or_organization", + ), + ), + migrations.AddConstraint( + model_name="invoice", + constraint=models.CheckConstraint( + condition=models.Q( + models.Q(("organization__isnull", False), ("user__isnull", True)), + models.Q(("organization__isnull", True), ("user__isnull", False)), + _connector="OR", + ), + name="backend_invoice_check_user_or_organization", + ), + ), + migrations.AddConstraint( + model_name="monthlyreport", + constraint=models.CheckConstraint( + condition=models.Q( + models.Q(("organization__isnull", False), ("user__isnull", True)), + models.Q(("organization__isnull", True), ("user__isnull", False)), + _connector="OR", + ), + name="backend_monthlyreport_check_user_or_organization", + ), + ), + migrations.AddConstraint( + model_name="multifileupload", + constraint=models.CheckConstraint( + condition=models.Q( + models.Q(("organization__isnull", False), ("user__isnull", True)), + models.Q(("organization__isnull", True), ("user__isnull", False)), + _connector="OR", + ), + name="backend_multifileupload_check_user_or_organization", + ), + ), + migrations.AddConstraint( + model_name="receipt", + constraint=models.CheckConstraint( + condition=models.Q( + models.Q(("organization__isnull", False), ("user__isnull", True)), + models.Q(("organization__isnull", True), ("user__isnull", False)), + _connector="OR", + ), + name="backend_receipt_check_user_or_organization", + ), + ), + ] diff --git a/backend/migrations/0003_client_company_client_is_representative.py b/backend/migrations/0003_client_company_client_is_representative.py deleted file mode 100644 index 8e6bc3a65..000000000 --- a/backend/migrations/0003_client_company_client_is_representative.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.1.7 on 2023-12-12 14:37 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("backend", "0002_alter_receipt_date_uploaded"), - ] - - operations = [ - migrations.AddField( - model_name="client", - name="company", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AddField( - model_name="client", - name="is_representative", - field=models.BooleanField(default=False), - ), - ] diff --git a/backend/migrations/0004_invoice_client_is_representative.py b/backend/migrations/0004_invoice_client_is_representative.py deleted file mode 100644 index 4b5d16191..000000000 --- a/backend/migrations/0004_invoice_client_is_representative.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.1.7 on 2023-12-15 08:40 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("backend", "0003_client_company_client_is_representative"), - ] - - operations = [ - migrations.AddField( - model_name="invoice", - name="client_is_representative", - field=models.BooleanField(default=False), - ), - ] diff --git a/backend/migrations/0005_invoiceproduct.py b/backend/migrations/0005_invoiceproduct.py deleted file mode 100644 index f9d9a7678..000000000 --- a/backend/migrations/0005_invoiceproduct.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 4.1.7 on 2023-12-18 13:24 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("backend", "0004_invoice_client_is_representative"), - ] - - operations = [ - migrations.CreateModel( - name="InvoiceProduct", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=50)), - ("description", models.CharField(max_length=100)), - ("quantity", models.IntegerField()), - ( - "rate", - models.DecimalField(blank=True, decimal_places=2, max_digits=15, null=True), - ), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/backend/migrations/0006_receipt_merchant_store_receipt_purchase_category.py b/backend/migrations/0006_receipt_merchant_store_receipt_purchase_category.py deleted file mode 100644 index 955990917..000000000 --- a/backend/migrations/0006_receipt_merchant_store_receipt_purchase_category.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.2.7 on 2024-01-08 20:32 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - dependencies = [ - ("backend", "0005_invoiceproduct"), - ] - - operations = [ - migrations.AddField( - model_name="receipt", - name="merchant_store", - field=models.CharField(default=None, max_length=200), - preserve_default=False, - ), - migrations.AddField( - model_name="receipt", - name="purchase_category", - field=models.CharField(default=django.utils.timezone.now, max_length=200), - preserve_default=False, - ), - ] diff --git a/backend/migrations/0007_alter_receipt_merchant_store_and_more.py b/backend/migrations/0007_alter_receipt_merchant_store_and_more.py deleted file mode 100644 index 1d061f8be..000000000 --- a/backend/migrations/0007_alter_receipt_merchant_store_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.7 on 2024-01-09 08:21 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("backend", "0006_receipt_merchant_store_receipt_purchase_category"), - ] - - operations = [ - migrations.AlterField( - model_name="receipt", - name="merchant_store", - field=models.CharField(blank=True, max_length=255, null=True), - ), - migrations.AlterField( - model_name="receipt", - name="purchase_category", - field=models.CharField(blank=True, max_length=200, null=True), - ), - ] diff --git a/backend/migrations/0008_receiptdownloadtoken.py b/backend/migrations/0008_receiptdownloadtoken.py deleted file mode 100644 index ccd39f78a..000000000 --- a/backend/migrations/0008_receiptdownloadtoken.py +++ /dev/null @@ -1,47 +0,0 @@ -# Generated by Django 4.2.7 on 2024-01-16 19:06 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - dependencies = [ - ("backend", "0007_alter_receipt_merchant_store_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="ReceiptDownloadToken", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "token", - models.UUIDField(default=uuid.uuid4, editable=False, unique=True), - ), - ( - "file", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="backend.receipt", - ), - ), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/backend/migrations/0009_alter_invoice_sort_code.py b/backend/migrations/0009_alter_invoice_sort_code.py deleted file mode 100644 index 6b0a9e71f..000000000 --- a/backend/migrations/0009_alter_invoice_sort_code.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.1.7 on 2024-01-31 08:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("backend", "0008_receiptdownloadtoken"), - ] - - operations = [ - migrations.AlterField( - model_name="invoice", - name="sort_code", - field=models.CharField(blank=True, max_length=8, null=True), - ), - ] diff --git a/backend/migrations/0010_user_logged_in_as_team.py b/backend/migrations/0010_user_logged_in_as_team.py deleted file mode 100644 index d6978976c..000000000 --- a/backend/migrations/0010_user_logged_in_as_team.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.0.1 on 2024-02-04 19:20 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0009_alter_invoice_sort_code"), - ] - - operations = [ - migrations.AddField( - model_name="user", - name="logged_in_as_team", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="backend.team", - ), - ), - ] diff --git a/backend/migrations/0011_alter_team_leader.py b/backend/migrations/0011_alter_team_leader.py deleted file mode 100644 index 535e5a5f2..000000000 --- a/backend/migrations/0011_alter_team_leader.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.0.1 on 2024-02-04 20:36 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0010_user_logged_in_as_team"), - ] - - operations = [ - migrations.AlterField( - model_name="team", - name="leader", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="teams_leader_of", - to=settings.AUTH_USER_MODEL, - ), - ), - ] diff --git a/backend/migrations/0012_receipt_organization_alter_receipt_user_and_more.py b/backend/migrations/0012_receipt_organization_alter_receipt_user_and_more.py deleted file mode 100644 index da5f66196..000000000 --- a/backend/migrations/0012_receipt_organization_alter_receipt_user_and_more.py +++ /dev/null @@ -1,44 +0,0 @@ -# Generated by Django 5.0.1 on 2024-02-05 13:41 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0011_alter_team_leader"), - ] - - operations = [ - migrations.AddField( - model_name="receipt", - name="organization", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="backend.team", - ), - ), - migrations.AlterField( - model_name="receipt", - name="user", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddConstraint( - model_name="receipt", - constraint=models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_receipt_check_user_or_organization", - ), - ), - ] diff --git a/backend/migrations/0013_auditlog_organization_client_organization_and_more.py b/backend/migrations/0013_auditlog_organization_client_organization_and_more.py deleted file mode 100644 index cbad74139..000000000 --- a/backend/migrations/0013_auditlog_organization_client_organization_and_more.py +++ /dev/null @@ -1,82 +0,0 @@ -# Generated by Django 5.0.1 on 2024-02-05 15:30 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0012_receipt_organization_alter_receipt_user_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="auditlog", - name="organization", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="backend.team", - ), - ), - migrations.AddField( - model_name="client", - name="organization", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="backend.team", - ), - ), - migrations.AddField( - model_name="invoice", - name="organization", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="backend.team", - ), - ), - migrations.AlterField( - model_name="client", - name="user", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AlterField( - model_name="invoice", - name="user", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddConstraint( - model_name="client", - constraint=models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_client_check_user_or_organization", - ), - ), - migrations.AddConstraint( - model_name="invoice", - constraint=models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_invoice_check_user_or_organization", - ), - ), - ] diff --git a/backend/migrations/0014_notification_extra_type_notification_extra_value.py b/backend/migrations/0014_notification_extra_type_notification_extra_value.py deleted file mode 100644 index 60b907752..000000000 --- a/backend/migrations/0014_notification_extra_type_notification_extra_value.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.0.1 on 2024-02-06 08:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0013_auditlog_organization_client_organization_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="notification", - name="extra_type", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AddField( - model_name="notification", - name="extra_value", - field=models.CharField(blank=True, max_length=100, null=True), - ), - ] diff --git a/backend/migrations/0015_alter_notification_user_alter_team_name.py b/backend/migrations/0015_alter_notification_user_alter_team_name.py deleted file mode 100644 index 131a4ee8a..000000000 --- a/backend/migrations/0015_alter_notification_user_alter_team_name.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 5.0.1 on 2024-02-07 08:10 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0014_notification_extra_type_notification_extra_value"), - ] - - operations = [ - migrations.AlterField( - model_name="notification", - name="user", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="user_notifications", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AlterField( - model_name="team", - name="name", - field=models.CharField(max_length=100, unique=True), - ), - ] diff --git a/backend/migrations/0016_alter_invoice_logo_alter_receipt_image_and_more.py b/backend/migrations/0016_alter_invoice_logo_alter_receipt_image_and_more.py deleted file mode 100644 index 1372786f2..000000000 --- a/backend/migrations/0016_alter_invoice_logo_alter_receipt_image_and_more.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 5.0.2 on 2024-02-14 19:26 - -from django.db import migrations, models - -import settings.settings - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0015_alter_notification_user_alter_team_name"), - ] - - operations = [ - migrations.AlterField( - model_name="invoice", - name="logo", - field=models.ImageField( - blank=True, - null=True, - storage=settings.settings.CustomPrivateMediaStorage(), - upload_to="invoice_logos", - ), - ), - migrations.AlterField( - model_name="receipt", - name="image", - field=models.ImageField( - storage=settings.settings.CustomPrivateMediaStorage(), - upload_to="receipts", - ), - ), - migrations.AlterField( - model_name="usersettings", - name="profile_picture", - field=models.ImageField( - blank=True, - null=True, - storage=settings.settings.CustomPublicMediaStorage(), - upload_to="profile_pictures/", - ), - ), - ] diff --git a/backend/migrations/0017_featureflags.py b/backend/migrations/0017_featureflags.py deleted file mode 100644 index 22721a0c2..000000000 --- a/backend/migrations/0017_featureflags.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.0.2 on 2024-02-18 20:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0016_alter_invoice_logo_alter_receipt_image_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="FeatureFlags", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=100)), - ("value", models.BooleanField(default=False)), - ("updated_at", models.DateTimeField(auto_now=True)), - ], - ), - ] diff --git a/backend/migrations/0018_user_role.py b/backend/migrations/0018_user_role.py deleted file mode 100644 index 5bbecc7c9..000000000 --- a/backend/migrations/0018_user_role.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 5.0.1 on 2024-02-20 10:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0017_featureflags"), - ] - - operations = [ - migrations.AddField( - model_name="user", - name="role", - field=models.CharField( - choices=[ - ("DEV", "Developer"), - ("STAFF", "Staff"), - ("USER", "User"), - ("TESTER", "Tester"), - ], - default="USER", - max_length=10, - ), - ), - ] diff --git a/backend/migrations/0019_alter_featureflags_options_and_more.py b/backend/migrations/0019_alter_featureflags_options_and_more.py deleted file mode 100644 index af94ee737..000000000 --- a/backend/migrations/0019_alter_featureflags_options_and_more.py +++ /dev/null @@ -1,70 +0,0 @@ -# Generated by Django 5.0.2 on 2024-02-22 18:16 - -import datetime -import uuid - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0018_user_role"), - ] - - operations = [ - migrations.AlterModelOptions( - name="featureflags", - options={ - "verbose_name": "Feature Flag", - "verbose_name_plural": "Feature Flags", - }, - ), - migrations.AddField( - model_name="user", - name="awaiting_email_verification", - field=models.BooleanField(default=True), - ), - migrations.CreateModel( - name="VerificationCodes", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "uuid", - models.UUIDField(default=uuid.uuid4, editable=False, unique=True), - ), - ("created", models.DateTimeField(auto_now_add=True)), - ( - "expiry", - models.DateTimeField(default=datetime.datetime(2024, 2, 22, 21, 16, 55, 46745, tzinfo=datetime.timezone.utc)), - ), - ( - "service", - models.CharField( - choices=[ - ("create_account", "Create Account"), - ("reset_password", "Reset Password"), - ], - max_length=14, - ), - ), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - ] diff --git a/backend/migrations/0020_alter_verificationcodes_options_and_more.py b/backend/migrations/0020_alter_verificationcodes_options_and_more.py deleted file mode 100644 index 0285241ea..000000000 --- a/backend/migrations/0020_alter_verificationcodes_options_and_more.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 5.0.2 on 2024-02-22 20:41 - -import datetime - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0019_alter_featureflags_options_and_more"), - ] - - operations = [ - migrations.AlterModelOptions( - name="verificationcodes", - options={ - "verbose_name": "Verification Code", - "verbose_name_plural": "Verification Codes", - }, - ), - migrations.AddField( - model_name="verificationcodes", - name="token", - field=models.TextField(default="BZQQWE", editable=False), - ), - migrations.AlterField( - model_name="verificationcodes", - name="expiry", - field=models.DateTimeField(default=datetime.datetime(2024, 2, 22, 23, 41, 26, 332896, tzinfo=datetime.timezone.utc)), - ), - ] diff --git a/backend/migrations/0021_alter_verificationcodes_expiry_and_more.py b/backend/migrations/0021_alter_verificationcodes_expiry_and_more.py deleted file mode 100644 index 71adcf375..000000000 --- a/backend/migrations/0021_alter_verificationcodes_expiry_and_more.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 5.0.2 on 2024-02-23 19:00 - -import datetime - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0020_alter_verificationcodes_options_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="verificationcodes", - name="expiry", - field=models.DateTimeField(default=datetime.datetime(2024, 2, 23, 22, 0, 25, 744643, tzinfo=datetime.timezone.utc)), - ), - migrations.AlterField( - model_name="verificationcodes", - name="token", - field=models.TextField(default="XBNKTM", editable=False), - ), - ] diff --git a/backend/migrations/0022_loginlog_service_alter_verificationcodes_expiry_and_more.py b/backend/migrations/0022_loginlog_service_alter_verificationcodes_expiry_and_more.py deleted file mode 100644 index ae25f4c2c..000000000 --- a/backend/migrations/0022_loginlog_service_alter_verificationcodes_expiry_and_more.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.0.2 on 2024-02-25 11:42 - -from django.db import migrations, models - -import backend.models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0021_alter_verificationcodes_expiry_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="loginlog", - name="service", - field=models.CharField(choices=[("manual", "Manual"), ("magic_link", "Magic Link")], default="manual", max_length=14), - ), - migrations.AlterField( - model_name="verificationcodes", - name="expiry", - field=models.DateTimeField(default=backend.models.add_3hrs_from_now), - ), - migrations.AlterField( - model_name="verificationcodes", - name="token", - field=models.TextField(default=backend.models.RandomCode, editable=False), - ), - ] diff --git a/backend/migrations/0023_apikey_invoiceonetimeschedule.py b/backend/migrations/0023_apikey_invoiceonetimeschedule.py deleted file mode 100644 index f472a2c9f..000000000 --- a/backend/migrations/0023_apikey_invoiceonetimeschedule.py +++ /dev/null @@ -1,64 +0,0 @@ -# Generated by Django 5.0.3 on 2024-03-08 23:17 - -import django.db.models.deletion -from django.db import migrations, models - -import backend.models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0022_loginlog_service_alter_verificationcodes_expiry_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="APIKey", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("service", models.CharField(choices=[("aws_api_destination", "Aws Api Destination")], max_length=20, null=True)), - ("key", models.CharField(default=backend.models.RandomAPICode, max_length=100)), - ("last_used", models.DateTimeField(auto_now_add=True)), - ], - options={ - "verbose_name": "API Key", - "verbose_name_plural": "API Keys", - }, - ), - migrations.CreateModel( - name="InvoiceOnetimeSchedule", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("stored_schedule_arn", models.CharField(blank=True, max_length=100, null=True)), - ("received", models.BooleanField(default=False)), - ( - "status", - models.CharField( - choices=[ - ("pending", "Pending"), - ("creating", "Creating"), - ("completed", "Completed"), - ("failed", "Failed"), - ("deleting", "Deleting"), - ("cancelled", "Cancelled"), - ], - default="pending", - max_length=100, - ), - ), - ("due", models.DateTimeField()), - ( - "invoice", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, related_name="onetime_invoice_schedules", to="backend.invoice" - ), - ), - ], - options={ - "verbose_name": "One-Time Invoice Schedule", - "verbose_name_plural": "One-Time Invoice Schedules", - }, - ), - ] diff --git a/backend/migrations/0024_invoiceurl_never_expire_invoiceurl_system_created_and_more.py b/backend/migrations/0024_invoiceurl_never_expire_invoiceurl_system_created_and_more.py deleted file mode 100644 index 0eed8347d..000000000 --- a/backend/migrations/0024_invoiceurl_never_expire_invoiceurl_system_created_and_more.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.0.3 on 2024-03-09 13:50 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0023_apikey_invoiceonetimeschedule"), - ] - - operations = [ - migrations.AddField( - model_name="invoiceurl", - name="never_expire", - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name="invoiceurl", - name="system_created", - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name="invoiceurl", - name="created_by", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/backend/migrations/0025_alter_invoiceonetimeschedule_stored_schedule_arn.py b/backend/migrations/0025_alter_invoiceonetimeschedule_stored_schedule_arn.py deleted file mode 100644 index 410dac5f9..000000000 --- a/backend/migrations/0025_alter_invoiceonetimeschedule_stored_schedule_arn.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.0.3 on 2024-03-16 14:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0024_invoiceurl_never_expire_invoiceurl_system_created_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="invoiceonetimeschedule", - name="stored_schedule_arn", - field=models.CharField(blank=True, max_length=500, null=True), - ), - ] diff --git a/backend/migrations/0026_invoice_discount_amount_invoice_discount_percentage.py b/backend/migrations/0026_invoice_discount_amount_invoice_discount_percentage.py deleted file mode 100644 index 04c63b4f1..000000000 --- a/backend/migrations/0026_invoice_discount_amount_invoice_discount_percentage.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 5.0.3 on 2024-03-29 20:00 - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0025_alter_invoiceonetimeschedule_stored_schedule_arn"), - ] - - operations = [ - migrations.AddField( - model_name="invoice", - name="discount_amount", - field=models.DecimalField(decimal_places=2, default=0, max_digits=15), - ), - migrations.AddField( - model_name="invoice", - name="discount_percentage", - field=models.DecimalField( - decimal_places=2, default=0, max_digits=5, validators=[django.core.validators.MaxValueValidator(100)] - ), - ), - ] diff --git a/backend/migrations/0027_invoice_currency.py b/backend/migrations/0027_invoice_currency.py deleted file mode 100644 index f2c9a9de4..000000000 --- a/backend/migrations/0027_invoice_currency.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.0.3 on 2024-03-31 23:19 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0026_invoice_discount_amount_invoice_discount_percentage"), - ] - - operations = [ - migrations.AddField( - model_name="invoice", - name="currency", - field=models.CharField( - choices=[ - ("GBP", "British Pound Sterling"), - ("EUR", "Euro"), - ("USD", "United States Dollar"), - ("JPY", "Japanese Yen"), - ("INR", "Indian Rupee"), - ("AUD", "Australian Dollar"), - ("CAD", "Canadian Dollar"), - ], - default="GBP", - max_length=3, - ), - ), - ] diff --git a/backend/migrations/0028_quotalimit_quotaincreaserequest_quotaoverrides_and_more.py b/backend/migrations/0028_quotalimit_quotaincreaserequest_quotaoverrides_and_more.py deleted file mode 100644 index ca011aa92..000000000 --- a/backend/migrations/0028_quotalimit_quotaincreaserequest_quotaoverrides_and_more.py +++ /dev/null @@ -1,111 +0,0 @@ -# Generated by Django 5.0.3 on 2024-04-01 17:55 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0027_invoice_currency"), - ] - - operations = [ - migrations.CreateModel( - name="QuotaLimit", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("slug", models.CharField(editable=False, max_length=100, unique=True)), - ("name", models.CharField(editable=False, max_length=100)), - ("description", models.TextField(blank=True, max_length=500, null=True)), - ("value", models.IntegerField()), - ("updated_at", models.DateTimeField(auto_now=True)), - ("adjustable", models.BooleanField(default=True)), - ( - "limit_type", - models.CharField( - choices=[ - ("per_month", "Per Month"), - ("per_day", "Per Day"), - ("per_client", "Per Client"), - ("per_invoice", "Per Invoice"), - ("per_team", "Per Team"), - ("per_quota", "Per Quota"), - ("forever", "Forever"), - ], - default="per_month", - max_length=20, - ), - ), - ], - options={ - "verbose_name": "Quota Limit", - "verbose_name_plural": "Quota Limits", - }, - ), - migrations.CreateModel( - name="QuotaIncreaseRequest", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("new_value", models.IntegerField()), - ("current_value", models.IntegerField()), - ("updated_at", models.DateTimeField(auto_now=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ( - "status", - models.CharField( - choices=[("pending", "Pending"), ("approved", "Approved"), ("rejected", "Rejected")], - default="pending", - max_length=20, - ), - ), - ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ( - "quota_limit", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, related_name="quota_increase_requests", to="backend.quotalimit" - ), - ), - ], - options={ - "verbose_name": "Quota Increase Request", - "verbose_name_plural": "Quota Increase Requests", - }, - ), - migrations.CreateModel( - name="QuotaOverrides", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("value", models.IntegerField()), - ("updated_at", models.DateTimeField(auto_now=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ( - "quota_limit", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="quota_overrides", to="backend.quotalimit"), - ), - ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - "verbose_name": "Quota Override", - "verbose_name_plural": "Quota Overrides", - }, - ), - migrations.CreateModel( - name="QuotaUsage", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("extra_data", models.IntegerField(blank=True, null=True)), - ( - "quota_limit", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="quota_usage", to="backend.quotalimit"), - ), - ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - "verbose_name": "Quota Usage", - "verbose_name_plural": "Quota Usage", - }, - ), - ] diff --git a/backend/migrations/0029_alter_invoice_organization_alter_invoice_user_and_more.py b/backend/migrations/0029_alter_invoice_organization_alter_invoice_user_and_more.py deleted file mode 100644 index 59943713d..000000000 --- a/backend/migrations/0029_alter_invoice_organization_alter_invoice_user_and_more.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.0.3 on 2024-04-01 19:49 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0028_quotalimit_quotaincreaserequest_quotaoverrides_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="invoice", - name="organization", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.team"), - ), - migrations.AlterField( - model_name="invoice", - name="user", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name="user", - name="logged_in_as_team", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to="backend.team"), - ), - ] diff --git a/backend/migrations/0030_alter_invoice_items.py b/backend/migrations/0030_alter_invoice_items.py deleted file mode 100644 index f8906bfde..000000000 --- a/backend/migrations/0030_alter_invoice_items.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.0.3 on 2024-04-04 15:24 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0029_alter_invoice_organization_alter_invoice_user_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="invoice", - name="items", - field=models.ManyToManyField(blank=True, to="backend.invoiceitem"), - ), - ] diff --git a/backend/migrations/0031_featureflags_description_alter_featureflags_name.py b/backend/migrations/0031_featureflags_description_alter_featureflags_name.py deleted file mode 100644 index c93047183..000000000 --- a/backend/migrations/0031_featureflags_description_alter_featureflags_name.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 5.0.4 on 2024-04-05 19:52 -from __future__ import annotations - -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0030_alter_invoice_items"), - ] - - operations = [ - migrations.AddField( - model_name="featureflags", - name="description", - field=models.TextField(blank=True, editable=False, max_length=500, null=True), - ), - migrations.AlterField( - model_name="featureflags", - name="name", - field=models.CharField(editable=False, max_length=100, unique=True), - ), - ] diff --git a/backend/migrations/0032_client_email_verified_alter_client_organization_and_more.py b/backend/migrations/0032_client_email_verified_alter_client_organization_and_more.py deleted file mode 100644 index 31ef9fa2a..000000000 --- a/backend/migrations/0032_client_email_verified_alter_client_organization_and_more.py +++ /dev/null @@ -1,98 +0,0 @@ -# Generated by Django 5.0.4 on 2024-04-10 13:40 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0031_featureflags_description_alter_featureflags_name"), - ] - - operations = [ - migrations.AddField( - model_name="client", - name="email_verified", - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name="client", - name="organization", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.team"), - ), - migrations.AlterField( - model_name="client", - name="user", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.CreateModel( - name="EmailSendStatus", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ("updated_status_at", models.DateTimeField(auto_now_add=True)), - ("recipient", models.TextField()), - ("aws_message_id", models.CharField(blank=True, editable=False, max_length=100, null=True)), - ( - "status", - models.CharField( - choices=[ - ("send", "Send"), - ("reject", "Reject"), - ("bounce", "Bounce"), - ("complaint", "Complaint"), - ("delivery", "Delivery"), - ("open", "Open"), - ("click", "Click"), - ("rendering_failure", "Rendering_Failure"), - ("delivery_delay", "Delivery_Delay"), - ("subscription", "Subscription"), - ("failed_to_send", "Failed_To_Send"), - ("pending", "Pending"), - ], - max_length=20, - ), - ), - ( - "organization", - models.ForeignKey( - blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="emails_created", to="backend.team" - ), - ), - ( - "sent_by", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="emails_sent", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "user", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="emails_created", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - ), - migrations.AddConstraint( - model_name="emailsendstatus", - constraint=models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_emailsendstatus_check_user_or_organization", - ), - ), - ] diff --git a/backend/migrations/0033_alter_auditlog_organization.py b/backend/migrations/0033_alter_auditlog_organization.py deleted file mode 100644 index 202d0f74c..000000000 --- a/backend/migrations/0033_alter_auditlog_organization.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.0.4 on 2024-04-10 16:11 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0032_client_email_verified_alter_client_organization_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="auditlog", - name="organization", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to="backend.team"), - ), - ] diff --git a/backend/migrations/0034_invoice_client_email_quotaincreaserequest_reason_and_more.py b/backend/migrations/0034_invoice_client_email_quotaincreaserequest_reason_and_more.py deleted file mode 100644 index 6657a171c..000000000 --- a/backend/migrations/0034_invoice_client_email_quotaincreaserequest_reason_and_more.py +++ /dev/null @@ -1,66 +0,0 @@ -# Generated by Django 5.0.4 on 2024-04-19 20:15 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0033_alter_auditlog_organization"), - ] - - operations = [ - migrations.AddField( - model_name="invoice", - name="client_email", - field=models.EmailField(blank=True, max_length=254, null=True), - ), - migrations.AddField( - model_name="quotaincreaserequest", - name="reason", - field=models.CharField(default="", max_length=1000), - preserve_default=False, - ), - migrations.CreateModel( - name="InvoiceReminder", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("stored_schedule_arn", models.CharField(blank=True, max_length=500, null=True)), - ("received", models.BooleanField(default=False)), - ( - "status", - models.CharField( - choices=[ - ("pending", "Pending"), - ("creating", "Creating"), - ("completed", "Completed"), - ("failed", "Failed"), - ("deleting", "Deleting"), - ("cancelled", "Cancelled"), - ], - default="pending", - max_length=100, - ), - ), - ("days", models.PositiveIntegerField(blank=True, null=True)), - ( - "reminder_type", - models.CharField( - choices=[("before_due", "Before Due"), ("after_due", "After Due"), ("on_overdue", "On Overdue")], - default="before_due", - max_length=100, - ), - ), - ( - "invoice", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="invoice_reminders", to="backend.invoice"), - ), - ], - options={ - "verbose_name": "Invoice Reminder", - "verbose_name_plural": "Invoice Reminders", - }, - ), - ] diff --git a/backend/migrations/0035_client_contact_method.py b/backend/migrations/0035_client_contact_method.py deleted file mode 100644 index 7cda02391..000000000 --- a/backend/migrations/0035_client_contact_method.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.0.4 on 2024-05-30 01:58 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0034_invoice_client_email_quotaincreaserequest_reason_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="client", - name="contact_method", - field=models.CharField(blank=True, max_length=100, null=True), - ), - ] diff --git a/backend/migrations/0036_alter_client_address_clientdefaults.py b/backend/migrations/0036_alter_client_address_clientdefaults.py deleted file mode 100644 index 940b7486d..000000000 --- a/backend/migrations/0036_alter_client_address_clientdefaults.py +++ /dev/null @@ -1,61 +0,0 @@ -# Generated by Django 5.0.4 on 2024-06-18 17:07 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0035_client_contact_method"), - ] - - operations = [ - migrations.AlterField( - model_name="client", - name="address", - field=models.TextField(blank=True, max_length=100, null=True), - ), - migrations.CreateModel( - name="ClientDefaults", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ( - "currency", - models.CharField( - choices=[ - ("GBP", "British Pound Sterling"), - ("EUR", "Euro"), - ("USD", "United States Dollar"), - ("JPY", "Japanese Yen"), - ("INR", "Indian Rupee"), - ("AUD", "Australian Dollar"), - ("CAD", "Canadian Dollar"), - ], - default="GBP", - max_length=3, - ), - ), - ("invoice_due_date_value", models.PositiveSmallIntegerField(default=7)), - ( - "invoice_due_date_type", - models.CharField( - choices=[("days_after", "Days After"), ("date_following", "Date Following"), ("date_current", "Date Current")], - default="days_after", - max_length=20, - ), - ), - ("invoice_date_value", models.PositiveSmallIntegerField(default=15)), - ( - "invoice_date_type", - models.CharField( - choices=[("day_of_month", "Day Of Month"), ("days_after", "Days After")], default="day_of_month", max_length=20 - ), - ), - ( - "client", - models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name="client_defaults", to="backend.client"), - ), - ], - ), - ] diff --git a/backend/migrations/0036_apiauthtoken.py b/backend/migrations/0036_apiauthtoken.py deleted file mode 100644 index d9ce8ac20..000000000 --- a/backend/migrations/0036_apiauthtoken.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.0.4 on 2024-06-10 16:59 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0035_client_contact_method"), - ] - - operations = [ - migrations.CreateModel( - name="APIAuthToken", - fields=[ - ("key", models.CharField(max_length=40, primary_key=True, serialize=False, verbose_name="Key")), - ("created", models.DateTimeField(auto_now_add=True, verbose_name="Created")), - ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/backend/migrations/0037_merge_20240619_2223.py b/backend/migrations/0037_merge_20240619_2223.py deleted file mode 100644 index a3adee93d..000000000 --- a/backend/migrations/0037_merge_20240619_2223.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Django 5.0.4 on 2024-06-19 21:23 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0036_alter_client_address_clientdefaults"), - ("backend", "0036_apiauthtoken"), - ] - - operations = [] diff --git a/backend/migrations/0038_alter_apiauthtoken_options.py b/backend/migrations/0038_alter_apiauthtoken_options.py deleted file mode 100644 index 79e6017fa..000000000 --- a/backend/migrations/0038_alter_apiauthtoken_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-20 16:42 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0037_merge_20240619_2223"), - ] - - operations = [ - migrations.AlterModelOptions( - name="apiauthtoken", - options={"verbose_name": "API Key", "verbose_name_plural": "API Keys"}, - ), - ] diff --git a/backend/migrations/0039_apiauthtoken_active_apiauthtoken_description_and_more.py b/backend/migrations/0039_apiauthtoken_active_apiauthtoken_description_and_more.py deleted file mode 100644 index b4096b57f..000000000 --- a/backend/migrations/0039_apiauthtoken_active_apiauthtoken_description_and_more.py +++ /dev/null @@ -1,59 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-21 17:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0038_alter_apiauthtoken_options"), - ] - - operations = [ - migrations.RemoveField( - model_name="apiauthtoken", - name="key", - ), - migrations.AddField( - model_name="apiauthtoken", - name="active", - field=models.BooleanField(default=True, help_text="If the key is active", verbose_name="Active"), - ), - migrations.AddField( - model_name="apiauthtoken", - name="description", - field=models.TextField(blank=True, null=True, verbose_name="Description"), - ), - migrations.AddField( - model_name="apiauthtoken", - name="expired", - field=models.BooleanField(default=False, help_text="If the key has expired", verbose_name="Expired"), - ), - migrations.AddField( - model_name="apiauthtoken", - name="expires", - field=models.DateTimeField(blank=True, help_text="Leave blank for no expiry", null=True, verbose_name="Expires"), - ), - migrations.AddField( - model_name="apiauthtoken", - name="id", - field=models.AutoField(primary_key=True, serialize=False, auto_created=True), - preserve_default=False, - ), - migrations.AddField( - model_name="apiauthtoken", - name="last_used", - field=models.DateTimeField(blank=True, null=True, verbose_name="Last Used"), - ), - migrations.AddField( - model_name="apiauthtoken", - name="name", - field=models.CharField(default="bob", max_length=64, verbose_name="Key Name"), - preserve_default=False, - ), - migrations.AddField( - model_name="apiauthtoken", - name="key", - field=models.CharField(max_length=40, unique=True), - ), - ] diff --git a/backend/migrations/0040_apiauthtoken_scopes_apiauthtoken_team_and_more.py b/backend/migrations/0040_apiauthtoken_scopes_apiauthtoken_team_and_more.py deleted file mode 100644 index a71c884de..000000000 --- a/backend/migrations/0040_apiauthtoken_scopes_apiauthtoken_team_and_more.py +++ /dev/null @@ -1,54 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-21 19:51 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0039_apiauthtoken_active_apiauthtoken_description_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="apiauthtoken", - name="scopes", - field=models.JSONField(default=list, help_text="List of permitted scopes", verbose_name="Scopes"), - ), - migrations.AddField( - model_name="apiauthtoken", - name="team", - field=models.ForeignKey( - blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="tokens", to="backend.team" - ), - ), - migrations.AlterField( - model_name="apiauthtoken", - name="id", - field=models.AutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name="apiauthtoken", - name="key", - field=models.CharField(max_length=40, unique=True, verbose_name="Key"), - ), - migrations.CreateModel( - name="TeamMemberPermission", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("scopes", models.JSONField(default=list, help_text="List of permitted scopes", verbose_name="Scopes")), - ("team", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="permissions", to="backend.team")), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, related_name="team_permissions", to=settings.AUTH_USER_MODEL - ), - ), - ], - options={ - "unique_together": {("team", "user")}, - }, - ), - ] diff --git a/backend/migrations/0041_alter_apiauthtoken_user.py b/backend/migrations/0041_alter_apiauthtoken_user.py deleted file mode 100644 index 5fb36919a..000000000 --- a/backend/migrations/0041_alter_apiauthtoken_user.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-28 23:57 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0040_apiauthtoken_scopes_apiauthtoken_team_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="apiauthtoken", - name="user", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/backend/migrations/0042_remove_apiauthtoken_key_apiauthtoken_hashed_key.py b/backend/migrations/0042_remove_apiauthtoken_key_apiauthtoken_hashed_key.py deleted file mode 100644 index ad1934645..000000000 --- a/backend/migrations/0042_remove_apiauthtoken_key_apiauthtoken_hashed_key.py +++ /dev/null @@ -1,39 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-29 18:19 -from django.contrib.auth.hashers import make_password -from django.db import migrations, models, transaction - - -def forwards_func(apps, schema_editor): - APIAuthToken = apps.get_model("backend", "APIAuthToken") - - tokens = APIAuthToken.objects.all() - - for token in tokens: - token.hashed_key = make_password(token.key, salt="api_tokens", hasher="default") - - with transaction.atomic(): - APIAuthToken.objects.bulk_update(tokens, ["hashed_key"]) - - -class Migration(migrations.Migration): - dependencies = [ - ("backend", "0041_alter_apiauthtoken_user"), - ] - - operations = [ - migrations.AddField( - model_name="apiauthtoken", - name="hashed_key", - field=models.CharField(max_length=128, unique=True, null=True, verbose_name="Key"), - ), - migrations.RunPython(forwards_func), # (cant really reverse) - migrations.AlterField( - model_name="apiauthtoken", - name="hashed_key", - field=models.CharField(max_length=128, unique=True, null=False, verbose_name="Key"), - ), - migrations.RemoveField( - model_name="apiauthtoken", - name="key", - ), - ] diff --git a/backend/migrations/0043_rename_team_organization_remove_apiauthtoken_team_and_more.py b/backend/migrations/0043_rename_team_organization_remove_apiauthtoken_team_and_more.py deleted file mode 100644 index ad41712b4..000000000 --- a/backend/migrations/0043_rename_team_organization_remove_apiauthtoken_team_and_more.py +++ /dev/null @@ -1,137 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-30 19:11 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -def forwards_func(apps, schema_editor): - QuotaIncreaseRequest = apps.get_model("backend", "QuotaIncreaseRequest") - - objs = QuotaIncreaseRequest.objects.all() - - for obj in objs: - obj.requester = obj.user - - QuotaIncreaseRequest.objects.bulk_update(objs, ["requester"]) - - -def reverse_func(apps, schema_editor): - pass - # Reverse code not needed - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0042_remove_apiauthtoken_key_apiauthtoken_hashed_key"), - ] - - operations = [ - migrations.RenameModel( - old_name="Team", - new_name="Organization", - ), - migrations.RemoveField( - model_name="apiauthtoken", - name="team", - ), - migrations.AddField( - model_name="apiauthtoken", - name="organization", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - migrations.AddField( - model_name="invoiceproduct", - name="organization", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - migrations.AddField( - model_name="quotaincreaserequest", - name="organization", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - migrations.AddField( - model_name="quotaincreaserequest", - name="requester", - field=models.ForeignKey( - default=1, - on_delete=django.db.models.deletion.CASCADE, - related_name="quota_increase_requests", - to=settings.AUTH_USER_MODEL, - ), - preserve_default=False, - ), - migrations.RunPython(forwards_func, reverse_code=reverse_func), - migrations.AddField( - model_name="quotaoverrides", - name="organization", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - migrations.AddField( - model_name="quotausage", - name="organization", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - migrations.AlterField( - model_name="auditlog", - name="organization", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - migrations.AlterField( - model_name="auditlog", - name="user", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name="emailsendstatus", - name="organization", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - migrations.AlterField( - model_name="emailsendstatus", - name="user", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name="invoiceproduct", - name="user", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name="quotaincreaserequest", - name="user", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name="quotaoverrides", - name="user", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name="quotausage", - name="user", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name="receipt", - name="organization", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - migrations.AlterField( - model_name="receipt", - name="user", - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AddConstraint( - model_name="invoiceproduct", - constraint=models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_invoiceproduct_check_user_or_organization", - ), - ), - ] diff --git a/backend/migrations/0044_defaultvalues_delete_clientdefaults_and_more.py b/backend/migrations/0044_defaultvalues_delete_clientdefaults_and_more.py deleted file mode 100644 index e2b6f8fd0..000000000 --- a/backend/migrations/0044_defaultvalues_delete_clientdefaults_and_more.py +++ /dev/null @@ -1,88 +0,0 @@ -# Generated by Django 5.0.6 on 2024-07-18 21:32 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0043_rename_team_organization_remove_apiauthtoken_team_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="DefaultValues", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ( - "currency", - models.CharField( - choices=[ - ("GBP", "British Pound Sterling"), - ("EUR", "Euro"), - ("USD", "United States Dollar"), - ("JPY", "Japanese Yen"), - ("INR", "Indian Rupee"), - ("AUD", "Australian Dollar"), - ("CAD", "Canadian Dollar"), - ], - default="GBP", - max_length=3, - ), - ), - ("invoice_due_date_value", models.PositiveSmallIntegerField(default=7)), - ( - "invoice_due_date_type", - models.CharField( - choices=[("days_after", "Days After"), ("date_following", "Date Following"), ("date_current", "Date Current")], - default="days_after", - max_length=20, - ), - ), - ("invoice_date_value", models.PositiveSmallIntegerField(default=15)), - ( - "invoice_date_type", - models.CharField( - choices=[("day_of_month", "Day Of Month"), ("days_after", "Days After")], default="day_of_month", max_length=20 - ), - ), - ( - "client", - models.OneToOneField( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="default_values", - to="backend.client", - ), - ), - ( - "organization", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - ( - "user", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ], - options={ - "abstract": False, - }, - ), - migrations.DeleteModel( - name="ClientDefaults", - ), - migrations.AddConstraint( - model_name="defaultvalues", - constraint=models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_defaultvalues_check_user_or_organization", - ), - ), - ] diff --git a/backend/migrations/0045_usersettings_disabled_features.py b/backend/migrations/0045_usersettings_disabled_features.py deleted file mode 100644 index a6e6bfcc1..000000000 --- a/backend/migrations/0045_usersettings_disabled_features.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.0.6 on 2024-07-19 22:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0044_defaultvalues_delete_clientdefaults_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="usersettings", - name="disabled_features", - field=models.JSONField(default=list), - ), - ] diff --git a/backend/migrations/0046_rename_status_invoicereminder_boto_schedule_status_and_more.py b/backend/migrations/0046_rename_status_invoicereminder_boto_schedule_status_and_more.py deleted file mode 100644 index 332f04292..000000000 --- a/backend/migrations/0046_rename_status_invoicereminder_boto_schedule_status_and_more.py +++ /dev/null @@ -1,197 +0,0 @@ -# Generated by Django 5.0.7 on 2024-08-22 15:09 - -import backend.models -import django.core.validators -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0045_usersettings_disabled_features"), - ] - - operations = [ - migrations.RenameField( - model_name="invoicereminder", - old_name="status", - new_name="boto_schedule_status", - ), - migrations.RemoveField( - model_name="invoicereminder", - name="stored_schedule_arn", - ), - migrations.AddField( - model_name="apiauthtoken", - name="administrator_service_type", - field=models.CharField(blank=True, max_length=64, null=True, verbose_name="Administrator Service Type"), - ), - migrations.AddField( - model_name="invoicereminder", - name="boto_last_updated", - field=models.DateTimeField(auto_now=True), - ), - migrations.AddField( - model_name="invoicereminder", - name="boto_schedule_arn", - field=models.CharField(blank=True, max_length=2048, null=True), - ), - migrations.AddField( - model_name="invoicereminder", - name="boto_schedule_uuid", - field=models.UUIDField(blank=True, default=None, null=True), - ), - migrations.AlterField( - model_name="invoice", - name="logo", - field=models.ImageField(blank=True, null=True, storage=backend.models._private_storage, upload_to="invoice_logos"), - ), - migrations.AlterField( - model_name="receipt", - name="image", - field=models.ImageField(storage=backend.models._private_storage, upload_to="receipts"), - ), - migrations.AlterField( - model_name="teammemberpermission", - name="user", - field=models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, related_name="team_permissions", to=settings.AUTH_USER_MODEL - ), - ), - migrations.AlterField( - model_name="usersettings", - name="profile_picture", - field=models.ImageField(blank=True, null=True, storage=backend.models._public_storage, upload_to="profile_pictures/"), - ), - migrations.CreateModel( - name="InvoiceRecurringProfile", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("boto_schedule_arn", models.CharField(blank=True, max_length=2048, null=True)), - ("boto_schedule_uuid", models.UUIDField(blank=True, default=None, null=True)), - ("boto_last_updated", models.DateTimeField(auto_now=True)), - ("received", models.BooleanField(default=False)), - ( - "boto_schedule_status", - models.CharField( - choices=[ - ("pending", "Pending"), - ("creating", "Creating"), - ("completed", "Completed"), - ("failed", "Failed"), - ("deleting", "Deleting"), - ("cancelled", "Cancelled"), - ], - default="pending", - max_length=100, - ), - ), - ("client_name", models.CharField(blank=True, max_length=100, null=True)), - ("client_email", models.EmailField(blank=True, max_length=254, null=True)), - ("client_company", models.CharField(blank=True, max_length=100, null=True)), - ("client_address", models.CharField(blank=True, max_length=100, null=True)), - ("client_city", models.CharField(blank=True, max_length=100, null=True)), - ("client_county", models.CharField(blank=True, max_length=100, null=True)), - ("client_country", models.CharField(blank=True, max_length=100, null=True)), - ("client_is_representative", models.BooleanField(default=False)), - ("self_name", models.CharField(blank=True, max_length=100, null=True)), - ("self_company", models.CharField(blank=True, max_length=100, null=True)), - ("self_address", models.CharField(blank=True, max_length=100, null=True)), - ("self_city", models.CharField(blank=True, max_length=100, null=True)), - ("self_county", models.CharField(blank=True, max_length=100, null=True)), - ("self_country", models.CharField(blank=True, max_length=100, null=True)), - ("sort_code", models.CharField(blank=True, max_length=8, null=True)), - ("account_holder_name", models.CharField(blank=True, max_length=100, null=True)), - ("account_number", models.CharField(blank=True, max_length=100, null=True)), - ("reference", models.CharField(blank=True, max_length=100, null=True)), - ("invoice_number", models.CharField(blank=True, max_length=100, null=True)), - ("vat_number", models.CharField(blank=True, max_length=100, null=True)), - ("logo", models.ImageField(blank=True, null=True, storage=backend.models._private_storage, upload_to="invoice_logos")), - ("notes", models.TextField(blank=True, null=True)), - ( - "currency", - models.CharField( - choices=[ - ("GBP", "British Pound Sterling"), - ("EUR", "Euro"), - ("USD", "United States Dollar"), - ("JPY", "Japanese Yen"), - ("INR", "Indian Rupee"), - ("AUD", "Australian Dollar"), - ("CAD", "Canadian Dollar"), - ], - default="GBP", - max_length=3, - ), - ), - ("date_created", models.DateTimeField(auto_now_add=True)), - ("date_issued", models.DateField(blank=True, null=True)), - ("discount_amount", models.DecimalField(decimal_places=2, default=0, max_digits=15)), - ( - "discount_percentage", - models.DecimalField( - decimal_places=2, default=0, max_digits=5, validators=[django.core.validators.MaxValueValidator(100)] - ), - ), - ("active", models.BooleanField(default=True)), - ( - "status", - models.CharField( - choices=[("ongoing", "Ongoing"), ("paused", "paused"), ("cancelled", "cancelled")], default="paused", max_length=10 - ), - ), - ( - "frequency", - models.CharField( - choices=[("weekly", "Weekly"), ("monthly", "Monthly"), ("yearly", "Yearly")], default="monthly", max_length=20 - ), - ), - ("end_date", models.DateField(blank=True, null=True)), - ("due_after_days", models.PositiveSmallIntegerField(default=7)), - ("day_of_week", models.PositiveSmallIntegerField(blank=True, null=True)), - ("day_of_month", models.PositiveSmallIntegerField(blank=True, null=True)), - ("month_of_year", models.PositiveSmallIntegerField(blank=True, null=True)), - ("client_to", models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to="backend.client")), - ("items", models.ManyToManyField(blank=True, to="backend.invoiceitem")), - ( - "organization", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - ( - "user", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ], - options={ - "abstract": False, - }, - ), - migrations.AddField( - model_name="invoice", - name="invoice_recurring_profile", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="generated_invoices", - to="backend.invoicerecurringprofile", - ), - ), - migrations.DeleteModel( - name="InvoiceOnetimeSchedule", - ), - migrations.AddConstraint( - model_name="invoicerecurringprofile", - constraint=models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_invoicerecurringprofile_check_user_or_organization", - ), - ), - ] diff --git a/backend/migrations/0047_defaultvalues_default_invoice_logo.py b/backend/migrations/0047_defaultvalues_default_invoice_logo.py deleted file mode 100644 index 61a66dd8f..000000000 --- a/backend/migrations/0047_defaultvalues_default_invoice_logo.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 5.1 on 2024-08-23 11:13 - -import settings.settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0046_rename_status_invoicereminder_boto_schedule_status_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="defaultvalues", - name="default_invoice_logo", - field=models.ImageField( - blank=True, null=True, storage=settings.settings.CustomPublicMediaStorage(), upload_to="invoice_logos/" - ), - ), - ] diff --git a/backend/migrations/0048_alter_defaultvalues_default_invoice_logo.py b/backend/migrations/0048_alter_defaultvalues_default_invoice_logo.py deleted file mode 100644 index a93d15ff2..000000000 --- a/backend/migrations/0048_alter_defaultvalues_default_invoice_logo.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 5.1 on 2024-08-23 11:54 - -import backend.models -from django.db import migrations, models - -from backend.models import _private_storage - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0047_defaultvalues_default_invoice_logo"), - ] - - operations = [ - migrations.AlterField( - model_name="defaultvalues", - name="default_invoice_logo", - field=models.ImageField(blank=True, null=True, storage=_private_storage, upload_to="invoice_logos/"), - ), - ] diff --git a/backend/migrations/0049_filestoragefile.py b/backend/migrations/0049_filestoragefile.py deleted file mode 100644 index 19b90a444..000000000 --- a/backend/migrations/0049_filestoragefile.py +++ /dev/null @@ -1,60 +0,0 @@ -# Generated by Django 5.1 on 2024-08-25 20:16 - -import backend.models -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0048_alter_defaultvalues_default_invoice_logo"), - ] - - operations = [ - migrations.CreateModel( - name="FileStorageFile", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ( - "file", - models.FileField(storage=backend.models._private_storage, upload_to=backend.models.upload_to_user_separate_folder), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "last_edited_by", - models.ForeignKey( - blank=True, - editable=False, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="files_edited", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "organization", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - ( - "user", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ], - options={ - "abstract": False, - "constraints": [ - models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_filestoragefile_check_user_or_organization", - ) - ], - }, - ), - ] diff --git a/backend/migrations/0050_multifileupload.py b/backend/migrations/0050_multifileupload.py deleted file mode 100644 index 1b8f11dd9..000000000 --- a/backend/migrations/0050_multifileupload.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 5.1 on 2024-08-26 10:53 - -import django.db.models.deletion -import uuid -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0049_filestoragefile"), - ] - - operations = [ - migrations.CreateModel( - name="MultiFileUpload", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("started_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ("finished_at", models.DateTimeField(blank=True, editable=False, null=True)), - ("uuid", models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), - ("files", models.ManyToManyField(related_name="multi_file_uploads", to="backend.filestoragefile")), - ( - "organization", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - ( - "user", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ], - options={ - "abstract": False, - "constraints": [ - models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_multifileupload_check_user_or_organization", - ) - ], - }, - ), - ] diff --git a/backend/migrations/0051_planfeaturegroup_subscriptionplan_planfeature_and_more.py b/backend/migrations/0051_planfeaturegroup_subscriptionplan_planfeature_and_more.py deleted file mode 100644 index fe7c81423..000000000 --- a/backend/migrations/0051_planfeaturegroup_subscriptionplan_planfeature_and_more.py +++ /dev/null @@ -1,173 +0,0 @@ -# Generated by Django 5.1 on 2024-08-26 17:46 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0050_multifileupload"), - ] - - operations = [ - migrations.CreateModel( - name="PlanFeatureGroup", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("name", models.CharField(max_length=50)), - ], - ), - migrations.CreateModel( - name="SubscriptionPlan", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ( - "name", - models.CharField( - choices=[ - ("free", "Free Plan"), - ("basic", "Basic Plan"), - ("standard", "Standard Plan"), - ("enterprise", "Enterprise Plan"), - ], - max_length=50, - unique=True, - ), - ), - ("price_per_month", models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), - ("description", models.TextField(blank=True, max_length=500, null=True)), - ("maximum_duration_months", models.IntegerField(blank=True, null=True)), - ], - ), - migrations.CreateModel( - name="PlanFeature", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("slug", models.CharField(editable=False, max_length=100, unique=True)), - ("name", models.CharField(editable=False, max_length=100)), - ("description", models.TextField(blank=True, max_length=500, null=True)), - ( - "group", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="features", to="backend.planfeaturegroup"), - ), - ( - "subscription_plan", - models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="features", to="backend.subscriptionplan"), - ), - ], - ), - migrations.CreateModel( - name="PlanFeatureVersion", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("version", models.IntegerField(editable=False)), - ("free_tier_limit", models.FloatField(default=0)), - ("free_period_in_months", models.IntegerField(blank=True, null=True)), - ("unit", models.CharField(max_length=20)), - ("cost_per_unit", models.DecimalField(decimal_places=6, max_digits=10)), - ("units_per_cost", models.FloatField(default=1)), - ("minimum_billable_size", models.FloatField(blank=True, null=True)), - ("valid_from", models.DateTimeField(auto_now_add=True)), - ("valid_to", models.DateTimeField(blank=True, null=True)), - ("plan_feature", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="backend.planfeature")), - ], - ), - migrations.CreateModel( - name="Usage", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("feature", models.CharField(max_length=50)), - ("quantity", models.FloatField()), - ("unit", models.CharField(max_length=20)), - ("timestamp", models.DateTimeField(auto_now_add=True)), - ("end_time", models.DateTimeField(blank=True, null=True)), - ( - "organization", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - ( - "user", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ], - options={ - "abstract": False, - "constraints": [ - models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_usage_check_user_or_organization", - ) - ], - }, - ), - migrations.CreateModel( - name="UserPlan", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("start_date", models.DateField()), - ("is_active", models.BooleanField(default=True)), - ( - "organization", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - ("plan", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="backend.planfeature")), - ( - "user", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ], - options={ - "abstract": False, - "constraints": [ - models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_userplan_check_user_or_organization", - ) - ], - }, - ), - migrations.CreateModel( - name="UserSubscription", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("custom_subscription_price_per_month", models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), - ("start_date", models.DateField()), - ("end_date", models.DateField(blank=True, null=True)), - ( - "organization", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - ( - "subscription_plan", - models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to="backend.subscriptionplan"), - ), - ( - "user", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ], - options={ - "abstract": False, - "constraints": [ - models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_usersubscription_check_user_or_organization", - ) - ], - }, - ), - ] diff --git a/backend/migrations/0052_filestoragefile_file_uri_path.py b/backend/migrations/0052_filestoragefile_file_uri_path.py deleted file mode 100644 index 34ddd213e..000000000 --- a/backend/migrations/0052_filestoragefile_file_uri_path.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1 on 2024-08-27 08:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0051_planfeaturegroup_subscriptionplan_planfeature_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="filestoragefile", - name="file_uri_path", - field=models.CharField(default="/null/", max_length=500), - preserve_default=False, - ), - ] diff --git a/backend/migrations/0053_usage_instance_id_alter_planfeature_name_and_more.py b/backend/migrations/0053_usage_instance_id_alter_planfeature_name_and_more.py deleted file mode 100644 index bbac4f49e..000000000 --- a/backend/migrations/0053_usage_instance_id_alter_planfeature_name_and_more.py +++ /dev/null @@ -1,46 +0,0 @@ -# Generated by Django 5.1 on 2024-08-27 18:33 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0052_filestoragefile_file_uri_path"), - ] - - operations = [ - migrations.AddField( - model_name="usage", - name="instance_id", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AlterField( - model_name="planfeature", - name="name", - field=models.CharField(max_length=100), - ), - migrations.AlterField( - model_name="planfeature", - name="slug", - field=models.CharField(editable=False, max_length=100), - ), - migrations.AlterField( - model_name="subscriptionplan", - name="name", - field=models.CharField(max_length=50, unique=True), - ), - migrations.AlterField( - model_name="usersubscription", - name="end_date", - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AlterField( - model_name="usersubscription", - name="start_date", - field=models.DateTimeField(auto_now_add=True), - ), - migrations.DeleteModel( - name="UserPlan", - ), - ] diff --git a/backend/migrations/0054_transferusage_storageusage.py b/backend/migrations/0054_transferusage_storageusage.py deleted file mode 100644 index 2fe694416..000000000 --- a/backend/migrations/0054_transferusage_storageusage.py +++ /dev/null @@ -1,57 +0,0 @@ -# Generated by Django 5.1 on 2024-08-28 14:43 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0053_usage_instance_id_alter_planfeature_name_and_more"), - ] - - operations = [ - migrations.CreateModel( - name="TransferUsage", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("amount_in_MB", models.DecimalField(decimal_places=2, max_digits=10)), - ("timestamp", models.DateTimeField(auto_now_add=True)), - ("feature", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="backend.planfeature")), - ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name="StorageUsage", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("file_uri_path", models.CharField(max_length=255)), - ("size_in_MB", models.DecimalField(decimal_places=8, max_digits=10)), - ("start_time", models.DateTimeField(auto_now_add=True)), - ("end_time", models.DateTimeField(blank=True, null=True)), - ("feature", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="backend.planfeature")), - ( - "organization", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - ( - "user", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ], - options={ - "abstract": False, - "constraints": [ - models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_storageusage_check_user_or_organization", - ) - ], - }, - ), - ] diff --git a/backend/migrations/0055_remove_planfeature_group_and_more.py b/backend/migrations/0055_remove_planfeature_group_and_more.py deleted file mode 100644 index 54a56f2c5..000000000 --- a/backend/migrations/0055_remove_planfeature_group_and_more.py +++ /dev/null @@ -1,89 +0,0 @@ -# Generated by Django 5.1 on 2024-08-29 13:09 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0054_transferusage_storageusage"), - ] - - operations = [ - migrations.RemoveField( - model_name="planfeature", - name="group", - ), - migrations.RemoveField( - model_name="planfeature", - name="subscription_plan", - ), - migrations.RemoveField( - model_name="planfeatureversion", - name="plan_feature", - ), - migrations.RemoveField( - model_name="storageusage", - name="feature", - ), - migrations.RemoveField( - model_name="transferusage", - name="feature", - ), - # migrations.RemoveField( - # model_name="storageusage", - # name="organization", - # ), - # migrations.RemoveField( - # model_name="storageusage", - # name="user", - # ), - migrations.RemoveField( - model_name="usersubscription", - name="subscription_plan", - ), - # migrations.RemoveField( - # model_name="transferusage", - # name="user", - # ), - # migrations.RemoveField( - # model_name="usage", - # name="organization", - # ), - # migrations.RemoveField( - # model_name="usage", - # name="user", - # ), - # migrations.RemoveField( - # model_name="usersubscription", - # name="organization", - # ), - # migrations.RemoveField( - # model_name="usersubscription", - # name="user", - # ), - migrations.DeleteModel( - name="PlanFeatureGroup", - ), - migrations.DeleteModel( - name="PlanFeatureVersion", - ), - migrations.DeleteModel( - name="PlanFeature", - ), - migrations.DeleteModel( - name="StorageUsage", - ), - migrations.DeleteModel( - name="SubscriptionPlan", - ), - migrations.DeleteModel( - name="TransferUsage", - ), - migrations.DeleteModel( - name="Usage", - ), - migrations.DeleteModel( - name="UserSubscription", - ), - ] diff --git a/backend/migrations/0056_user_stripe_customer_id.py b/backend/migrations/0056_user_stripe_customer_id.py deleted file mode 100644 index 2d2938fe6..000000000 --- a/backend/migrations/0056_user_stripe_customer_id.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1 on 2024-08-29 13:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0055_remove_planfeature_group_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="user", - name="stripe_customer_id", - field=models.CharField(blank=True, max_length=255, null=True), - ), - ] diff --git a/backend/migrations/0057_user_entitlements.py b/backend/migrations/0057_user_entitlements.py deleted file mode 100644 index d5aa91033..000000000 --- a/backend/migrations/0057_user_entitlements.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1 on 2024-09-01 17:55 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0056_user_stripe_customer_id"), - ] - - operations = [ - migrations.AddField( - model_name="user", - name="entitlements", - field=models.JSONField(blank=True, default=list, null=True), - ), - ] diff --git a/backend/migrations/0058_organization_entitlements_and_more.py b/backend/migrations/0058_organization_entitlements_and_more.py deleted file mode 100644 index f526ef1b0..000000000 --- a/backend/migrations/0058_organization_entitlements_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.1 on 2024-09-02 18:02 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0057_user_entitlements"), - ] - - operations = [ - migrations.AddField( - model_name="organization", - name="entitlements", - field=models.JSONField(blank=True, default=list, null=True), - ), - migrations.AddField( - model_name="organization", - name="stripe_customer_id", - field=models.CharField(blank=True, max_length=255, null=True), - ), - ] diff --git a/backend/migrations/0059_alter_invoicerecurringprofile_managers_and_more.py b/backend/migrations/0059_alter_invoicerecurringprofile_managers_and_more.py deleted file mode 100644 index 3f0355aec..000000000 --- a/backend/migrations/0059_alter_invoicerecurringprofile_managers_and_more.py +++ /dev/null @@ -1,89 +0,0 @@ -# Generated by Django 5.1 on 2024-09-08 13:50 - -import django.db.models.deletion -import django.db.models.manager -import uuid -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0058_organization_entitlements_and_more"), - ] - - operations = [ - migrations.AlterModelManagers( - name="invoicerecurringprofile", - managers=[ - ("with_items", django.db.models.manager.Manager()), - ], - ), - migrations.CreateModel( - name="MonthlyReportRow", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("date", models.DateField()), - ("reference_number", models.CharField(max_length=100)), - ("item_type", models.CharField(max_length=100)), - ("client_name", models.CharField(blank=True, max_length=64, null=True)), - ("paid_in", models.DecimalField(decimal_places=2, default=0, max_digits=15)), - ("paid_out", models.DecimalField(decimal_places=2, default=0, max_digits=15)), - ("client", models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.client")), - ], - ), - migrations.CreateModel( - name="MonthlyReport", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), - ("uuid", models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), - ("name", models.CharField(blank=True, max_length=100, null=True)), - ("profit", models.DecimalField(decimal_places=2, default=0, max_digits=15)), - ("invoices_sent", models.PositiveIntegerField(default=0)), - ("start_date", models.DateField()), - ("end_date", models.DateField()), - ("recurring_customers", models.PositiveIntegerField(default=0)), - ("payments_in", models.DecimalField(decimal_places=2, default=0, max_digits=15)), - ("payments_out", models.DecimalField(decimal_places=2, default=0, max_digits=15)), - ( - "currency", - models.CharField( - choices=[ - ("GBP", "British Pound Sterling"), - ("EUR", "Euro"), - ("USD", "United States Dollar"), - ("JPY", "Japanese Yen"), - ("INR", "Indian Rupee"), - ("AUD", "Australian Dollar"), - ("CAD", "Canadian Dollar"), - ], - default="GBP", - max_length=3, - ), - ), - ( - "organization", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="backend.organization"), - ), - ( - "user", - models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ("items", models.ManyToManyField(blank=True, to="backend.monthlyreportrow")), - ], - options={ - "abstract": False, - "constraints": [ - models.CheckConstraint( - check=models.Q( - models.Q(("organization__isnull", False), ("user__isnull", True)), - models.Q(("organization__isnull", True), ("user__isnull", False)), - _connector="OR", - ), - name="backend_monthlyreport_check_user_or_organization", - ) - ], - }, - ), - ] diff --git a/backend/migrations/0060_user_require_change_password.py b/backend/migrations/0060_user_require_change_password.py deleted file mode 100644 index e17cf8764..000000000 --- a/backend/migrations/0060_user_require_change_password.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1 on 2024-09-14 21:55 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0059_alter_invoicerecurringprofile_managers_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="user", - name="require_change_password", - field=models.BooleanField(default=False), - ), - ] diff --git a/backend/migrations/0061_defaultvalues_invoice_from_address_and_more.py b/backend/migrations/0061_defaultvalues_invoice_from_address_and_more.py deleted file mode 100644 index 053b5b856..000000000 --- a/backend/migrations/0061_defaultvalues_invoice_from_address_and_more.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 5.1 on 2024-09-16 19:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0060_user_require_change_password"), - ] - - operations = [ - migrations.AddField( - model_name="defaultvalues", - name="invoice_from_address", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AddField( - model_name="defaultvalues", - name="invoice_from_city", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AddField( - model_name="defaultvalues", - name="invoice_from_company", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AddField( - model_name="defaultvalues", - name="invoice_from_country", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AddField( - model_name="defaultvalues", - name="invoice_from_county", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AddField( - model_name="defaultvalues", - name="invoice_from_name", - field=models.CharField(blank=True, max_length=100, null=True), - ), - ] diff --git a/backend/migrations/0062_defaultvalues_invoice_account_holder_name_and_more.py b/backend/migrations/0062_defaultvalues_invoice_account_holder_name_and_more.py deleted file mode 100644 index 076be836f..000000000 --- a/backend/migrations/0062_defaultvalues_invoice_account_holder_name_and_more.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 5.1 on 2024-09-16 20:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0061_defaultvalues_invoice_from_address_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="defaultvalues", - name="invoice_account_holder_name", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AddField( - model_name="defaultvalues", - name="invoice_account_number", - field=models.CharField(blank=True, max_length=100, null=True), - ), - migrations.AddField( - model_name="defaultvalues", - name="invoice_sort_code", - field=models.CharField(blank=True, max_length=100, null=True), - ), - ] diff --git a/backend/migrations/0063_defaultvalues_email_template_recurring_invoices_invoice_cancelled_and_more.py b/backend/migrations/0063_defaultvalues_email_template_recurring_invoices_invoice_cancelled_and_more.py deleted file mode 100644 index 5aefa7ed3..000000000 --- a/backend/migrations/0063_defaultvalues_email_template_recurring_invoices_invoice_cancelled_and_more.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 5.1 on 2024-09-28 18:46 - -import backend.data.default_email_templates -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0062_defaultvalues_invoice_account_holder_name_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="defaultvalues", - name="email_template_recurring_invoices_invoice_cancelled", - field=models.TextField( - default=backend.data.default_email_templates.recurring_invoices_invoice_cancelled_default_email_template - ), - ), - migrations.AddField( - model_name="defaultvalues", - name="email_template_recurring_invoices_invoice_created", - field=models.TextField(default=backend.data.default_email_templates.recurring_invoices_invoice_created_default_email_template), - ), - migrations.AddField( - model_name="defaultvalues", - name="email_template_recurring_invoices_invoice_overdue", - field=models.TextField(default=backend.data.default_email_templates.recurring_invoices_invoice_overdue_default_email_template), - ), - migrations.AddField( - model_name="defaultvalues", - name="invoice_from_email", - field=models.CharField(blank=True, max_length=100, null=True), - ), - ] diff --git a/backend/migrations/0064_remove_invoice_payment_status_invoice_status.py b/backend/migrations/0064_remove_invoice_payment_status_invoice_status.py deleted file mode 100644 index d0049a0ed..000000000 --- a/backend/migrations/0064_remove_invoice_payment_status_invoice_status.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.1 on 2024-09-28 19:36 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0063_defaultvalues_email_template_recurring_invoices_invoice_cancelled_and_more"), - ] - - operations = [ - migrations.RemoveField( - model_name="invoice", - name="payment_status", - ), - migrations.AddField( - model_name="invoice", - name="status", - field=models.CharField( - choices=[("draft", "Draft"), ("pending", "Pending"), ("paid", "Paid")], default="pending", max_length=10 - ), - ), - ] diff --git a/backend/migrations/0065_remove_invoiceurl_never_expire_passwordsecret_active_and_more.py b/backend/migrations/0065_remove_invoiceurl_never_expire_passwordsecret_active_and_more.py deleted file mode 100644 index 647f8b132..000000000 --- a/backend/migrations/0065_remove_invoiceurl_never_expire_passwordsecret_active_and_more.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 5.1.1 on 2024-10-02 20:00 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0064_remove_invoice_payment_status_invoice_status"), - ] - - operations = [ - migrations.RemoveField( - model_name="invoiceurl", - name="never_expire", - ), - migrations.AddField( - model_name="passwordsecret", - name="active", - field=models.BooleanField(default=True), - ), - migrations.AlterField( - model_name="invoice", - name="status", - field=models.CharField(choices=[("draft", "Draft"), ("pending", "Pending"), ("paid", "Paid")], default="draft", max_length=10), - ), - migrations.AlterField( - model_name="invoiceurl", - name="expires", - field=models.DateTimeField(blank=True, help_text="When the item will expire", null=True, verbose_name="Expires"), - ), - migrations.AlterField( - model_name="passwordsecret", - name="expires", - field=models.DateTimeField(blank=True, help_text="When the item will expire", null=True, verbose_name="Expires"), - ), - migrations.AlterField( - model_name="teaminvitation", - name="expires", - field=models.DateTimeField(blank=True, help_text="When the item will expire", null=True, verbose_name="Expires"), - ), - ] diff --git a/backend/migrations/0066_delete_apikey_remove_verificationcodes_expiry_and_more.py b/backend/migrations/0066_delete_apikey_remove_verificationcodes_expiry_and_more.py deleted file mode 100644 index 4eaffce95..000000000 --- a/backend/migrations/0066_delete_apikey_remove_verificationcodes_expiry_and_more.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.1.1 on 2024-10-06 07:52 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0065_remove_invoiceurl_never_expire_passwordsecret_active_and_more"), - ] - - operations = [ - migrations.DeleteModel( - name="APIKey", - ), - migrations.RemoveField( - model_name="verificationcodes", - name="expiry", - ), - migrations.AddField( - model_name="verificationcodes", - name="active", - field=models.BooleanField(default=True), - ), - migrations.AddField( - model_name="verificationcodes", - name="expires", - field=models.DateTimeField(blank=True, help_text="When the item will expire", null=True, verbose_name="Expires"), - ), - ] diff --git a/backend/migrations/0067_remove_apiauthtoken_expired_and_more.py b/backend/migrations/0067_remove_apiauthtoken_expired_and_more.py deleted file mode 100644 index 1272d48dd..000000000 --- a/backend/migrations/0067_remove_apiauthtoken_expired_and_more.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 5.1.1 on 2024-10-19 19:03 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0066_delete_apikey_remove_verificationcodes_expiry_and_more"), - ] - - operations = [ - migrations.RemoveField( - model_name="apiauthtoken", - name="expired", - ), - migrations.AlterField( - model_name="apiauthtoken", - name="active", - field=models.BooleanField(default=True), - ), - migrations.AlterField( - model_name="apiauthtoken", - name="expires", - field=models.DateTimeField(blank=True, help_text="When the item will expire", null=True, verbose_name="Expires"), - ), - ] diff --git a/backend/migrations/0068_invoice_created_at_invoice_status_updated_at_and_more.py b/backend/migrations/0068_invoice_created_at_invoice_status_updated_at_and_more.py deleted file mode 100644 index 66513144b..000000000 --- a/backend/migrations/0068_invoice_created_at_invoice_status_updated_at_and_more.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 5.1.1 on 2024-11-08 19:57 - -import django.utils.timezone -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0067_remove_apiauthtoken_expired_and_more"), - ] - - operations = [ - migrations.AddField( - model_name="invoice", - name="created_at", - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), - preserve_default=False, - ), - migrations.AddField( - model_name="invoice", - name="status_updated_at", - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), - preserve_default=False, - ), - migrations.AddField( - model_name="invoice", - name="updated_at", - field=models.DateTimeField(auto_now=True), - ), - migrations.AddField( - model_name="invoicerecurringprofile", - name="updated_at", - field=models.DateTimeField(auto_now=True), - ), - ] diff --git a/backend/migrations/0069_alter_auditlog_action.py b/backend/migrations/0069_alter_auditlog_action.py deleted file mode 100644 index e9aae2fbf..000000000 --- a/backend/migrations/0069_alter_auditlog_action.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.1 on 2024-11-15 09:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0068_invoice_created_at_invoice_status_updated_at_and_more"), - ] - - operations = [ - migrations.AlterField( - model_name="auditlog", - name="action", - field=models.CharField(max_length=300), - ), - ] diff --git a/backend/migrations/0070_remove_invoice_invoice_id_and_more.py b/backend/migrations/0070_remove_invoice_invoice_id_and_more.py deleted file mode 100644 index a059eaa86..000000000 --- a/backend/migrations/0070_remove_invoice_invoice_id_and_more.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 5.1.1 on 2024-11-17 15:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("backend", "0069_alter_auditlog_action"), - ] - - operations = [ - migrations.RemoveField( - model_name="invoice", - name="invoice_id", - ), - migrations.RemoveField( - model_name="invoice", - name="invoice_number", - ), - migrations.RemoveField( - model_name="invoicerecurringprofile", - name="invoice_number", - ), - migrations.RemoveField( - model_name="invoicerecurringprofile", - name="reference", - ), - migrations.AlterField( - model_name="invoice", - name="reference", - field=models.CharField(blank=True, max_length=16, null=True), - ), - ] diff --git a/backend/modals.py b/backend/modals.py new file mode 100644 index 000000000..2023fd3ba --- /dev/null +++ b/backend/modals.py @@ -0,0 +1,58 @@ +from core.service.modals.registry import Modal +from core.types.requests import WebRequest +from core.utils.feature_flags import get_feature_status +from django.contrib import messages +from django.shortcuts import render + +from backend.finance.models import InvoiceURL +from backend.models import Client + + +class InvoicesToDestinationModal(Modal): + modal_name = "invoices_to_destination" + + def get(self, request: WebRequest): + context = self.get_context(request) + + if existing_client := request.GET.get("client"): + context["existing_client_id"] = existing_client + + return render(request, self.get_template_name(), context) + + +class EmailContext: + def get_context(self, request: WebRequest) -> dict: + return { + "content_min_length": 64, + "content_max_length": 1000, + "email_list": Client.filter_by_owner(owner=request.actor).filter(email__isnull=False).values_list("email", flat=True), + } + + +class SendSingleEmailModal(Modal, EmailContext): + modal_name = "send_single_email" + + def get(self, request: WebRequest): + if not get_feature_status("areUserEmailsAllowed"): + messages.error(request, "Emails are disabled") + return render(request, "base/toast.html") + + context = self.get_context(request) + + if request.GET.get("type") == "invoice_code_send": + invoice_url: InvoiceURL | None = InvoiceURL.objects.filter(uuid=request.GET.get("code")).prefetch_related("invoice").first() + + if not invoice_url or not invoice_url.invoice.has_access(request.user): + messages.error(request, "You don't have access to this invoice") + return render(request, "base/toast.html", {"autohide": False}) + + context["invoice"] = invoice_url.invoice + context["selected_clients"] = [ + invoice_url.invoice.client_to.email if invoice_url.invoice.client_to else invoice_url.invoice.client_email + for value in [invoice_url.invoice.client_to.email if invoice_url.invoice.client_to else invoice_url.invoice.client_email] + if value is not None + ] + + context["email_list"] = list(context["email_list"]) + context["selected_clients"] + + return render(request, self.get_template_name(), context) diff --git a/backend/urls.py b/backend/urls.py index 5cdb90037..e2b7cdc8c 100644 --- a/backend/urls.py +++ b/backend/urls.py @@ -50,7 +50,7 @@ view_invoice_with_uuid_endpoint, name="invoices view invoice", ), - path("api/", include("backend.api.urls")), + path("api/", include("backend.api.urls", namespace="api")), ] + static(settings.STATIC_URL, document_root=settings.STATICFILES_DIRS[0]) handler500 = "core.views.other.errors.universal" diff --git a/frontend/templates/base/+left_drawer.html b/frontend/templates/base/+left_drawer.html index 3c8a986db..5c78a18f3 100644 --- a/frontend/templates/base/+left_drawer.html +++ b/frontend/templates/base/+left_drawer.html @@ -1,35 +1,26 @@ +{% extends "core/base/+left_drawer.html" %} {% load feature_enabled %} {% feature_enabled "areUserEmailsAllowed" as are_user_emails_allowed %} -{#{% personal_feature_enabled request.user "invoices" as feature_enabled_invoices %}#} -{#{% personal_feature_enabled request.user "receipts" as feature_enabled_receipts %}#} -{#{% personal_feature_enabled request.user "email_sending" as feature_enabled_emails %}#} -
- - -
+ {% endif %} + {#
  • #} + {# File Storage#} + {#
  • #} +{% endblock drawer_items %} \ No newline at end of file diff --git a/frontend/templates/base/_head.html b/frontend/templates/base/_head.html index 2e634e3dd..014fef3ce 100644 --- a/frontend/templates/base/_head.html +++ b/frontend/templates/base/_head.html @@ -1,70 +1 @@ -{% load static %} -{% load render_bundle from webpack_loader %} -{% load tz_detect %} - - - - My Finances - - - - - - {% if import_method == "public_cdn" %} - - - - - {# #} - - - - {# #} - - - {# #} - - {# #} - {# - #} - - {# #} - {# Not in use at the moment MAY USE LATER ^^ #} - - - - {# #} - - {% else %} - {# #} - {# #} - {# #} - {# {% render_bundle 'all' 'js' %}#} - {% render_bundle 'init' 'js' %} - {% render_bundle 'tableify' 'js' %} - {% render_bundle 'htmx' 'js' %} - {% render_bundle 'font_awesome' 'js' %} - - - {% endif %} - {% render_bundle 'receipt_downloads' 'js' %} - {{ analytics|safe }} - {% tz_detect %} - {# #} - +{% extends "core/base/_head.html" %} \ No newline at end of file diff --git a/frontend/templates/base/auth.html b/frontend/templates/base/auth.html index a5a6a95eb..bc617cf75 100644 --- a/frontend/templates/base/auth.html +++ b/frontend/templates/base/auth.html @@ -1,37 +1 @@ -{% load static %} - - {% include 'base/_head.html' %} - -
    -
    -
    -
    -
    -
    -

    Dashboard

    -
    - -
    -

    What do you get to manage?

    -

    ✓ Client Lists

    -

    ✓ Invoices

    -

    ✓ Receipt Storage

    -

    ✓ Financial Reports

    -
    -
    -
    -
    - {# {% component "messages_list" %}#} - {% include "base/toasts.html" %} -

    - {% block title %} - {% endblock title %} -

    - {% block content %} - {% endblock content %} -
    -
    -
    -
    - - +{% extends "core/base/auth.html" %} \ No newline at end of file diff --git a/frontend/templates/base/base.html b/frontend/templates/base/base.html index 37a54d72d..4619d0b7e 100644 --- a/frontend/templates/base/base.html +++ b/frontend/templates/base/base.html @@ -1,55 +1,4 @@ - - - {% include 'base/_head.html' %} - -
    - -
    - {% include 'base/topbar/_topbar.html' %} -
    -
    - {% include 'base/breadcrumbs.html' with breadcrumb_first_load=True %} -
    {% include 'base/toasts.html' %}
    - -
    - -
    - {% block content %} - {% endblock content %} -
    - {% component "base:left_drawer" %} -
    - -
    - - -
    -
    - - +{% extends "core/base/base.html" %} + +{% block topbar %}{% include 'base/topbar/_topbar.html' %}{% endblock %} +{% block drawer %}{% include "base/+left_drawer.html" %}{% endblock drawer %} \ No newline at end of file diff --git a/frontend/templates/base/htmx.html b/frontend/templates/base/htmx.html index 3836e49ad..8a5e838c4 100644 --- a/frontend/templates/base/htmx.html +++ b/frontend/templates/base/htmx.html @@ -1,11 +1,6 @@ -
    - {% block content %} - {% endblock content %} -
    -{# Profile Picture dropdown item #} -
    - {% component "base:topbar:icon_dropdown" %} -
    -{% include "base/+left_drawer.html" with swap=True %} -{% include "base/breadcrumbs.html" with swap=True %} -{% include "base/toasts.html" %} +{% extends "core/base/htmx.html" %} +{##} +{% block left_drawer %}{% include "base/+left_drawer.html" with swap=True %}{% endblock %} + +{#{% include "base/breadcrumbs.html" with swap=True %}#} +{#{% include "base/toasts.html" %}#} diff --git a/frontend/templates/base/topbar/_topbar.html b/frontend/templates/base/topbar/_topbar.html index c387696e6..3ea6f2eb2 100644 --- a/frontend/templates/base/topbar/_topbar.html +++ b/frontend/templates/base/topbar/_topbar.html @@ -1,72 +1,28 @@ -{% load static %} +{% extends "core/base/topbar/_topbar.html" %} {% load feature_enabled %} {% personal_feature_enabled request.user "invoices" as feature_enabled_invoices %} {% personal_feature_enabled request.user "receipts" as feature_enabled_receipts %} {% personal_feature_enabled request.user "email_sending" as feature_enabled_emails %} {% personal_feature_enabled request.user "monthly_reports" as feature_enabled_monthly_reports %} - +{% endblock topbar_list %} \ No newline at end of file diff --git a/frontend/templates/pages/dashboard.html b/frontend/templates/pages/dashboard.html index 31ccd60b5..f3dd59102 100644 --- a/frontend/templates/pages/dashboard.html +++ b/frontend/templates/pages/dashboard.html @@ -1,4 +1,4 @@ -{% extends base|default:"core/base/base.html" %} +{% extends base|default:"base/base.html" %} {% block content %}
    diff --git a/settings/settings.py b/settings/settings.py index 681d3dc88..92993c056 100644 --- a/settings/settings.py +++ b/settings/settings.py @@ -47,11 +47,11 @@ INSTALLED_APPS = [ "django_extensions", "django.contrib.admin", + "backend", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", - "backend", "mathfilters", "django.contrib.humanize", "django_htmx", From 21b402178f1ed4926864b4ce26fb1a0cb7d4ab1b Mon Sep 17 00:00:00 2001 From: Trey <73353716+TreyWW@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:31:51 +0000 Subject: [PATCH 04/21] Fixed all modals to work with new system Signed-off-by: Trey <73353716+TreyWW@users.noreply.github.com> --- backend/api/urls.py | 2 +- backend/apps.py | 4 + backend/modals.py | 216 ++++++++++++++- .../topbar/_notification_dropdown_items.html | 4 +- .../destinations/_from_destination.html | 2 +- .../pages/invoices/dashboard/manage.html | 2 +- .../single/edit/edit_from_destination.html | 56 ++-- .../single/edit/edit_to_destination.html | 64 ++--- .../single/manage_access/_table_row.html | 2 +- .../single/schedules/reminders/container.html | 85 +++--- .../templates/pages/quotas/_fetch_body.html | 54 ++-- .../pages/receipts/_search_results.html | 245 +++++++++--------- .../templates/pages/receipts/dashboard.html | 2 +- .../pages/settings/pages/profile.html | 11 +- .../templates/pages/settings/teams/main.html | 94 +++---- 15 files changed, 534 insertions(+), 309 deletions(-) diff --git a/backend/api/urls.py b/backend/api/urls.py index d6bd0a347..e73faae3d 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -4,7 +4,7 @@ from django.urls import path urlpatterns = [ - path("finance/", include("backend.finance.api.urls")), + path("clients/", include("backend.clients.api.urls")), ] app_name = "api" diff --git a/backend/apps.py b/backend/apps.py index 3ba516cc5..32eba1ed8 100644 --- a/backend/apps.py +++ b/backend/apps.py @@ -1,3 +1,5 @@ +import importlib + from django.apps import AppConfig @@ -7,6 +9,8 @@ class BackendConfig(AppConfig): def ready(self): from .finance import signals + importlib.import_module("backend.modals") + # from .clients import signals # from .storage import signals # from .events import signals diff --git a/backend/modals.py b/backend/modals.py index 2023fd3ba..105638207 100644 --- a/backend/modals.py +++ b/backend/modals.py @@ -4,7 +4,8 @@ from django.contrib import messages from django.shortcuts import render -from backend.finance.models import InvoiceURL +from backend.finance.models import InvoiceURL, Invoice, Receipt +from backend.finance.service.defaults.get import get_account_defaults from backend.models import Client @@ -56,3 +57,216 @@ def get(self, request: WebRequest): context["email_list"] = list(context["email_list"]) + context["selected_clients"] return render(request, self.get_template_name(), context) + + +class EditReceiptModal(Modal): + modal_name = "edit_receipt" + + def get(self, request: WebRequest): + context = self.get_context(request) + + try: + receipt = Receipt.filter_by_owner(request.actor).get(pk=request.GET.get("receipt_id")) + except Receipt.DoesNotExist: + return self.Response(request, context) + + receipt_date = receipt.date.strftime("%Y-%m-%d") if receipt.date else "" + context.update( + { + "modal_id": f"modal_{receipt.id}_receipts_upload", + "receipt_id": request.GET.get("receipt_id"), + "receipt_name": receipt.name, + "receipt_date": receipt_date, + "merchant_store_name": receipt.merchant_store, + "purchase_category": receipt.purchase_category, + "total_price": receipt.total_price, + "has_receipt_image": True if receipt.image else False, + "edit_flag": True, + } + ) + + return self.Response(request, context) + + +class UploadReceiptModal(Modal): + modal_name = "upload_receipt" + + def get(self, request: WebRequest): + context = self.get_context(request) + + context.update({"modal_id": "modal_receipts_upload"}) + + return self.Response(request, context) + + +class EditInvoiceToModal(Modal): + modal_name = "edit_invoice_to" + + def get(self, request: WebRequest): + context = self.get_context(request) + + invoice_id = request.GET.get("invoice_id") + + try: + invoice = Invoice.filter_by_owner(request.actor).get(id=invoice_id) # todo: add permission checks + except Invoice.DoesNotExist: + return self.Response(request, context) + + if invoice.client_to: + context["to_name"] = invoice.client_to.name + context["to_company"] = invoice.client_to.company + context["to_email"] = invoice.client_to.email + context["to_address"] = invoice.client_to.address + context["existing_client_id"] = ( + invoice.client_to.id + ) # context["to_city"] = invoice.client_to.city # context["to_county"] = invoice.client_to.county # context["to_country"] = invoice.client_to.country + else: + context["to_name"] = invoice.client_name + context["to_company"] = invoice.client_company + context["to_email"] = invoice.client_email + context["is_representative"] = invoice.client_is_representative + context["to_address"] = ( + invoice.client_address + ) # context["to_city"] = invoice.client_city # context["to_county"] = invoice.client_county # context["to_country"] = invoice.client_country + + return self.Response(request, context) + + +class EditInvoiceFromModal(Modal): + modal_name = "edit_invoice_from" + + def get(self, request: WebRequest): + context = self.get_context(request) + + invoice_id = request.GET.get("invoice_id") + + try: + invoice = Invoice.filter_by_owner(request.actor).get(id=invoice_id) # todo: add permission checks + except Invoice.DoesNotExist: + return self.Response(request, context) + + context["from_name"] = invoice.self_name + context["from_company"] = invoice.self_company + context["from_address"] = invoice.self_address + context["from_city"] = invoice.self_city + context["from_county"] = invoice.self_county + context["from_country"] = invoice.self_country + return self.Response(request, context) + + +# create_invoice_from +class CreateInvoiceFromModal(Modal): + modal_name = "create_invoice_from" + + def get(self, request: WebRequest): + context = self.get_context(request) + + defaults = get_account_defaults(request.actor) + + context["from_name"] = getattr(defaults, f"invoice_from_name") + context["from_company"] = getattr(defaults, f"invoice_from_company") + context["from_address"] = getattr(defaults, f"invoice_from_address") + context["from_city"] = getattr(defaults, f"invoice_from_city") + context["from_county"] = getattr(defaults, f"invoice_from_county") + context["from_country"] = getattr(defaults, f"invoice_from_country") + + return self.Response(request, context) + + +class InvoiceContext: + def get_context(self, request: WebRequest) -> dict: + try: + invoice = Invoice.filter_by_owner(request.actor).get(id=request.GET.get("invoice_id")) + if invoice.has_access(request.user): + return {"invoice": invoice} + except Invoice.DoesNotExist: + return {} + + +class EditInvoiceDiscountModal(Modal, InvoiceContext): + modal_name = "invoices_edit_discount" + + def get(self, request: WebRequest): + context = self.get_context(request) + + return self.Response(request, context) + + +# class ViewQuotaLimitInfoModal(Modal): +# modal_name = 'view_quota_limit_info' +# +# def get(self, request: WebRequest): +# context = self.get_context(request) +# +# try: +# quota = QuotaLimit.objects.prefetch_related("quota_overrides").get(slug=context_value) +# context["quota"] = quota +# context["current_limit"] = quota.get_quota_limit(user=request.user, quota_limit=quota) +# usage = quota.strict_get_quotas(user=request.user, quota_limit=quota) +# context["quota_usage"] = usage.count() if usage != "Not Available" else "Not available" +# print(context["quota_usage"]) +# except QuotaLimit.DoesNotExist: +# ... +# +# return self.Response(request, context) + + +class CreateInvoiceReminderModal(Modal): + modal_name = "create_invoice_reminder" + + def get(self, request: WebRequest): + context = self.get_context(request) + + try: + invoice = Invoice.filter_by_owner(request.actor).get(id=request.GET.get("invoice_id")) + if invoice.has_access(request.user): + context["invoice"] = invoice + else: + messages.error(request, "You don't have access to this invoice") + return render(request, "base/toasts.html") + except Invoice.DoesNotExist: + return self.Response(request, context) + + return self.Response(request, context) + + +class SendEmailContext: + def get_context(self, request: WebRequest) -> dict: + if not get_feature_status("areUserEmailsAllowed"): + messages.error(request, "Emails are disabled") + return render(request, "base/toast.html") + + context = {} + + context["content_min_length"] = 64 + # quota = QuotaLimit.objects.prefetch_related("quota_overrides").get(slug="emails-email_character_count") + # context["content_max_length"] = quota.get_quota_limit(user=request.user, quota_limit=quota) + context["content_max_length"] = 1000 + + context["email_list"] = Client.filter_by_owner(owner=request.actor).filter(email__isnull=False).values_list("email", flat=True) + + return context + + +class InvoiceCodeSendModal(Modal, SendEmailContext): + modal_name = "invoice_code_send" + + def get(self, request: WebRequest): + context = self.get_context(request) + + invoice_url: InvoiceURL | None = InvoiceURL.objects.filter(uuid=request.GET.get("code")).prefetch_related("invoice").first() + + if not invoice_url or not invoice_url.invoice.has_access(request.user): + messages.error(request, "You don't have access to this invoice") + return render(request, "base/toast.html", {"autohide": False}) + + context["invoice"] = invoice_url.invoice + context["selected_clients"] = [ + invoice_url.invoice.client_to.email if invoice_url.invoice.client_to else invoice_url.invoice.client_email + for value in [invoice_url.invoice.client_to.email if invoice_url.invoice.client_to else invoice_url.invoice.client_email] + if value is not None + ] + + context["email_list"] = list(context["email_list"]) + context["selected_clients"] + + return self.Response(request, context) diff --git a/frontend/templates/base/topbar/_notification_dropdown_items.html b/frontend/templates/base/topbar/_notification_dropdown_items.html index 7d9e36d30..c63d45ef7 100644 --- a/frontend/templates/base/topbar/_notification_dropdown_items.html +++ b/frontend/templates/base/topbar/_notification_dropdown_items.html @@ -21,7 +21,7 @@ {# hx-trigger="click once"#} {# hx-swap="beforeend"#} {# hx-target="#modal_container"#} -{# hx-get="{% url "api:base:modal retrieve with context" modal_name=notification.action_value context_type=notification.extra_type context_value=notification.extra_value %}">#} +{# hx-get="{% url "api:base:modal retrieve" modal_name=notification.action_value context_type=notification.extra_type context_value=notification.extra_value %}">#} {# {{ notification.message }}#} {# #} {# #} @@ -65,7 +65,7 @@ hx-trigger="click once" hx-swap="beforeend" hx-target="#modal_container" - hx-get="{% url "api:base:modal retrieve with context" modal_name=notification.action_value context_type=notification.extra_type context_value=notification.extra_value %}"> + hx-get="{% url "api:base:modal retrieve" modal_name=notification.action_value %}?{{ notification.extra_type }}={{ notification.extra_value }}"> {{ notification.message }} diff --git a/frontend/templates/pages/invoices/create/destinations/_from_destination.html b/frontend/templates/pages/invoices/create/destinations/_from_destination.html index 066b8fc76..27b24a981 100644 --- a/frontend/templates/pages/invoices/create/destinations/_from_destination.html +++ b/frontend/templates/pages/invoices/create/destinations/_from_destination.html @@ -5,7 +5,7 @@ hx-trigger="click once" hx-swap="beforeend" hx-target="#modal_container" - hx-get="{% url 'api:base:modal retrieve with context' modal_name="invoices_from_destination" context_type='create_invoice_from' context_value='b' %}"> + hx-get="{% url 'api:base:modal retrieve' modal_name="create_invoice_from" %}"> {% endif %}

    {{ from_name | default:"No name" }}

    diff --git a/frontend/templates/pages/invoices/dashboard/manage.html b/frontend/templates/pages/invoices/dashboard/manage.html index 1931fb886..314e2754a 100644 --- a/frontend/templates/pages/invoices/dashboard/manage.html +++ b/frontend/templates/pages/invoices/dashboard/manage.html @@ -104,7 +104,7 @@ hx-swap="beforeend" hx-target="#modal_container" _="on click call modal_invoices_edit_discount.showModal()" - hx-get="{% url "api:base:modal retrieve with context" context_type="invoice" context_value=invoice.id modal_name="invoices_edit_discount" %}"> + hx-get="{% url "api:base:modal retrieve" modal_name="invoices_edit_discount" %}?invoice_id={{ invoice.id }}"> Edit Discount diff --git a/frontend/templates/pages/invoices/single/edit/edit_from_destination.html b/frontend/templates/pages/invoices/single/edit/edit_from_destination.html index f39321506..abd196d2f 100644 --- a/frontend/templates/pages/invoices/single/edit/edit_from_destination.html +++ b/frontend/templates/pages/invoices/single/edit/edit_from_destination.html @@ -5,31 +5,31 @@ hx-trigger="click" hx-swap="beforeend" hx-target="#modal_container" - hx-get="{% url 'api:base:modal retrieve with context' modal_name='invoices_from_destination' context_type='edit_invoice_from' context_value=invoice_object.id %}"> - {% endif %} - - -
    -

    {{ from_name | default:"No name" }}

    -

    {{ from_company | default:"No company" }}

    -

    {{ from_address | default:"No address" }}

    -

    {{ from_city | default:"No city" }}

    -

    {{ from_county | default:"No county" }}

    -

    {{ from_country | default:"No country" }}

    -
    - - - - - - - - {% if not swapping %}{% endif %} + hx-get="{% url 'api:base:modal retrieve' modal_name='edit_invoice_from' %}?invoice_id={{ invoice_object.id }}"> +{% endif %} + + +
    +

    {{ from_name | default:"No name" }}

    +

    {{ from_company | default:"No company" }}

    +

    {{ from_address | default:"No address" }}

    +

    {{ from_city | default:"No city" }}

    +

    {{ from_county | default:"No county" }}

    +

    {{ from_country | default:"No country" }}

    +
    + + + + + + + +{% if not swapping %}{% endif %} diff --git a/frontend/templates/pages/invoices/single/edit/edit_to_destination.html b/frontend/templates/pages/invoices/single/edit/edit_to_destination.html index c00a25b26..8b92e8079 100644 --- a/frontend/templates/pages/invoices/single/edit/edit_to_destination.html +++ b/frontend/templates/pages/invoices/single/edit/edit_to_destination.html @@ -6,36 +6,36 @@

    To

    hx-trigger="click once" hx-swap="beforeend" hx-target="#modal_container" - hx-get=" {% url "api:base:modal retrieve with context" modal_name="invoices_to_destination" context_type="edit_invoice_to" context_value=invoice_object.id %}"> - {% endif %} - -
    -

    {{ to_name | default:"No name" }}

    -

    {{ to_company | default:"No company" }}

    -

    {{ to_email | default:"No email" }}

    -

    {{ to_address | default:"No address" }}

    -

    {{ to_city | default:"No city" }}

    -

    {{ to_county | default:"No county" }}

    -

    {{ to_country | default:"No country" }}

    -

    - {% if is_representative %} - Is a - {% else %} - Is not a - {% endif %} + hx-get=" {% url "api:base:modal retrieve" modal_name="edit_invoice_to" %}?invoice_id={{ invoice_object.id }}"> +{% endif %} + +

    +

    {{ to_name | default:"No name" }}

    +

    {{ to_company | default:"No company" }}

    +

    {{ to_email | default:"No email" }}

    +

    {{ to_address | default:"No address" }}

    +

    {{ to_city | default:"No city" }}

    +

    {{ to_county | default:"No county" }}

    +

    {{ to_country | default:"No country" }}

    +

    + {% if is_representative %} + Is a + {% else %} + Is not a + {% endif %} representative -

    -
    - - - - - - - - - - - {% if not swapping %}{% endif %} +

    +
    + + + + + + + + + + +{% if not swapping %}{% endif %} diff --git a/frontend/templates/pages/invoices/single/manage_access/_table_row.html b/frontend/templates/pages/invoices/single/manage_access/_table_row.html index 7a0c31db3..6bcca4350 100644 --- a/frontend/templates/pages/invoices/single/manage_access/_table_row.html +++ b/frontend/templates/pages/invoices/single/manage_access/_table_row.html @@ -19,7 +19,7 @@ hx-trigger="from:#single_send_button, click once" hx-swap="beforeend" hx-target="#modal_container" - hx-get="{% url "api:base:modal retrieve with context" modal_name='send_bulk_email' context_value=code context_type='invoice_code_send' %}"> + hx-get="{% url "api:base:modal retrieve" modal_name='invoice_code_send' %}?code={{ code }}"> diff --git a/frontend/templates/pages/invoices/single/schedules/reminders/container.html b/frontend/templates/pages/invoices/single/schedules/reminders/container.html index 8303f3fb6..cd5fb0ffd 100644 --- a/frontend/templates/pages/invoices/single/schedules/reminders/container.html +++ b/frontend/templates/pages/invoices/single/schedules/reminders/container.html @@ -18,7 +18,7 @@

    View Reminders

    diff --git a/frontend/templates/pages/settings/teams/main.html b/frontend/templates/pages/settings/teams/main.html index 1f25858ba..4bd50e29f 100644 --- a/frontend/templates/pages/settings/teams/main.html +++ b/frontend/templates/pages/settings/teams/main.html @@ -8,7 +8,7 @@ {% if team.is_leader %}
    diff --git a/frontend/templates/modals/change_profile_picture.html b/frontend/templates/modals/change_profile_picture.html index a6a9be406..2843d9440 100644 --- a/frontend/templates/modals/change_profile_picture.html +++ b/frontend/templates/modals/change_profile_picture.html @@ -2,7 +2,7 @@ {% fill "content" %}