diff --git a/docs/developer/README.md b/docs/developer/README.md index 7f85d8070..f95d01aa4 100644 --- a/docs/developer/README.md +++ b/docs/developer/README.md @@ -10,9 +10,9 @@ If you're new to Django, see [Getting Started with Django](https://www.djangopro ```shell cd src - docker-compose build + docker compose build ``` -* Run the server: `docker-compose up` +* Run the server: `docker compose up` Press Ctrl-c when you'd like to exit or pass `-d` to run in detached mode. @@ -50,7 +50,7 @@ Resources: ## Setting Vars -Non-secret environment variables for local development are set in [src/docker-compose.yml](../../src/docker-compose.yml). +Non-secret environment variables for local development are set in [src/docker compose.yml](../../src/docker compose.yml). Secrets (for example, if you'd like to have a working Login.gov authentication) go in `.env` in [src/](../../src/) with contents like this: @@ -159,15 +159,15 @@ The CODEOWNERS file sets the tagged individuals as default reviewers on any Pull ## Viewing Logs -If you run via `docker-compose up`, you'll see the logs in your terminal. +If you run via `docker compose up`, you'll see the logs in your terminal. -If you run via `docker-compose up -d`, you can get logs with `docker-compose logs -f`. +If you run via `docker compose up -d`, you can get logs with `docker compose logs -f`. You can change the logging verbosity, if needed. Do a web search for "django log level". ## Mock data -[load.py](../../src/registrar/management/commands/load.py) called from docker-compose (locally) and reset-db.yml (upper) loads the fixtures from [fixtures_user.py](../../src/registrar/fixtures/fixtures_users.py) and the rest of the data-loading fixtures in that fixtures folder, giving you some test data to play with while developing. +[load.py](../../src/registrar/management/commands/load.py) called from docker compose (locally) and reset-db.yml (upper) loads the fixtures from [fixtures_user.py](../../src/registrar/fixtures/fixtures_users.py) and the rest of the data-loading fixtures in that fixtures folder, giving you some test data to play with while developing. See the [database-access README](./database-access.md) for information on how to pull data to update these fixtures. @@ -179,26 +179,26 @@ To get a container running: ```shell cd src -docker-compose build -docker-compose up -d +docker compose build +docker compose up -d ``` Django's test suite: ```shell -docker-compose exec app ./manage.py test +docker compose exec app ./manage.py test ``` OR ```shell -docker-compose exec app python -Wa ./manage.py test # view deprecation warnings +docker compose exec app python -Wa ./manage.py test # view deprecation warnings ``` Linters: ```shell -docker-compose exec app ./manage.py lint +docker compose exec app ./manage.py lint ``` ### Get availability for domain requests to work locally @@ -266,7 +266,7 @@ accessibility rules. The scan runs as part of our CI setup (see type ```shell -docker-compose run pa11y npm run pa11y-ci +docker compose run pa11y npm run pa11y-ci ``` The URLs that `pa11y-ci` will scan are configured in `src/.pa11yci`. When new @@ -280,7 +280,7 @@ security rules. The scan runs as part of our CI setup (see type ```shell -docker-compose run owasp +docker compose run owasp ``` ## Images, stylesheets, and JavaScript @@ -297,7 +297,7 @@ We utilize the [uswds-compile tool](https://designsystem.digital.gov/documentati ### Making and viewing style changes -When you run `docker-compose up` the `node` service in the container will begin to watch for changes in the `registrar/assets` folder, and will recompile once any changes are made. +When you run `docker compose up` the `node` service in the container will begin to watch for changes in the `registrar/assets` folder, and will recompile once any changes are made. Within the `registrar/assets` folder, the `_theme` folder contains three files initially generated by `uswds-compile`: 1. `_uswds-theme-custom-styles` contains all the custom styles created for this application diff --git a/src/Pipfile b/src/Pipfile index 47f515904..f3750f48e 100644 --- a/src/Pipfile +++ b/src/Pipfile @@ -35,6 +35,7 @@ django-admin-multiple-choice-list-filter = "*" django-import-export = "*" django-waffle = "*" cryptography = "*" +rapidfuzz = "*" [dev-packages] django-debug-toolbar = "*" diff --git a/src/Pipfile.lock b/src/Pipfile.lock index fd3d8bc6f..804ca2051 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "65ce6ae45dc6d29dcd98f8949d9756eee06d30705544fbb332238b1ca2365db5" + "sha256": "a3fdb82da639e933f6fb62df9fe918543cc820e14db20ee69746b243f3585f18" }, "pipfile-spec": 6, "requires": {}, @@ -32,37 +32,37 @@ }, "boto3": { "hashes": [ - "sha256:1545c943f36db41853cdfdb6ff09c4eda9220dd95bd2fae76fc73091603525d1", - "sha256:9b272268794172b0b8bb9fb1f3c470c3b6c0ffb92fbd4882465cc740e40fbdcd" + "sha256:2e3411bb43285caad1c8e1a3186d025ba65a6342e26bad493f6b8feb3d1a1680", + "sha256:9b0ff0b34c9cf7328546c532c20b081f09055ff485f4d57c19146c36877048c5" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.37.18" + "markers": "python_version >= '3.9'", + "version": "==1.38.43" }, "botocore": { "hashes": [ - "sha256:99e8eefd5df6347ead15df07ce55f4e62a51ea7b54de1127522a08597923b726", - "sha256:a8b97d217d82b3c4f6bcc906e264df7ebb51e2c6a62b3548a97cd173fb8759a1" + "sha256:2ee60ac0b08e80e9be2aa2841d42e438d5bc4f82549560a682837655097a3db7", + "sha256:c453c5c16c157c5427058bb3cc2c5ad35ee2e43336f0ccbfcc6092c5635505c6" ], - "markers": "python_version >= '3.8'", - "version": "==1.37.18" + "markers": "python_version >= '3.9'", + "version": "==1.38.43" }, "cachetools": { "hashes": [ - "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", - "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a" + "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e", + "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==5.5.2" + "markers": "python_version >= '3.9'", + "version": "==6.1.0" }, "certifi": { "hashes": [ - "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", - "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" + "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", + "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b" ], - "markers": "python_version >= '3.6'", - "version": "==2025.1.31" + "markers": "python_version >= '3.7'", + "version": "==2025.6.15" }, "cfenv": { "hashes": [ @@ -147,143 +147,145 @@ }, "charset-normalizer": { "hashes": [ - "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", - "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa", - "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", - "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", - "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", - "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", - "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", - "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", - "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", - "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", - "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", - "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", - "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", - "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", - "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", - "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", - "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", - "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", - "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", - "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", - "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e", - "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a", - "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", - "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", - "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", - "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", - "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", - "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", - "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", - "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", - "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", - "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", - "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", - "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", - "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", - "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", - "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", - "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", - "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", - "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", - "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", - "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", - "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", - "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf", - "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487", - "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d", - "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd", - "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", - "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534", - "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", - "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", - "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", - "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", - "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", - "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", - "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", - "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", - "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d", - "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", - "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", - "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", - "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", - "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", - "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", - "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", - "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", - "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", - "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", - "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", - "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", - "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", - "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", - "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", - "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", - "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", - "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", - "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", - "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e", - "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", - "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", - "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", - "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", - "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", - "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", - "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", - "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", - "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", - "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089", - "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", - "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e", - "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", - "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616" + "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", + "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", + "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", + "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", + "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", + "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", + "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d", + "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", + "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184", + "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", + "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", + "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64", + "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", + "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", + "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", + "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344", + "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", + "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", + "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", + "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", + "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", + "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", + "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", + "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", + "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", + "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", + "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", + "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", + "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58", + "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", + "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", + "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2", + "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", + "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", + "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", + "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", + "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", + "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f", + "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", + "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", + "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", + "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", + "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", + "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", + "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", + "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", + "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4", + "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", + "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", + "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", + "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", + "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", + "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", + "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", + "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", + "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", + "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", + "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa", + "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", + "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", + "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", + "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", + "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", + "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", + "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02", + "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", + "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", + "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", + "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", + "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", + "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", + "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", + "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", + "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", + "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", + "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", + "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", + "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", + "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", + "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", + "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", + "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", + "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", + "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", + "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", + "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", + "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", + "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da", + "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", + "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f", + "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", + "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f" ], "markers": "python_version >= '3.7'", - "version": "==3.4.1" + "version": "==3.4.2" }, "cryptography": { "hashes": [ - "sha256:04abd71114848aa25edb28e225ab5f268096f44cf0127f3d36975bdf1bdf3390", - "sha256:0529b1d5a0105dd3731fa65680b45ce49da4d8115ea76e9da77a875396727b41", - "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", - "sha256:268e4e9b177c76d569e8a145a6939eca9a5fec658c932348598818acf31ae9a5", - "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", - "sha256:2bf7bf75f7df9715f810d1b038870309342bff3069c5bd8c6b96128cb158668d", - "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", - "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", - "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5", - "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", - "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", - "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79", - "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", - "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", - "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", - "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", - "sha256:7ca25849404be2f8e4b3c59483d9d3c51298a22c1c61a0e84415104dacaf5562", - "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", - "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", - "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3", - "sha256:909c97ab43a9c0c0b0ada7a1281430e4e5ec0458e6d9244c0e821bbf152f061d", - "sha256:96e7a5e9d6e71f9f4fca8eebfd603f8e86c5225bb18eb621b2c1e50b290a9471", - "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", - "sha256:9eb9d22b0a5d8fd9925a7764a054dca914000607dff201a24c791ff5c799e1fa", - "sha256:af4ff3e388f2fa7bff9f7f2b31b87d5651c45731d3e8cfa0944be43dff5cfbdb", - "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", - "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", - "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa", - "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", - "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", - "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", - "sha256:d1b3031093a366ac767b3feb8bcddb596671b3aaff82d4050f984da0c248b615", - "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", - "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7", - "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308" + "sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8", + "sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4", + "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6", + "sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862", + "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750", + "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2", + "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999", + "sha256:3530382a43a0e524bc931f187fc69ef4c42828cf7d7f592f7f249f602b5a4ab0", + "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069", + "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d", + "sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c", + "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1", + "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036", + "sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349", + "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872", + "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22", + "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d", + "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad", + "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637", + "sha256:6b613164cb8425e2f8db5849ffb84892e523bf6d26deb8f9bb76ae86181fa12b", + "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57", + "sha256:7aad98a25ed8ac917fdd8a9c1e706e5a0956e06c498be1f713b61734333a4507", + "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee", + "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6", + "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8", + "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4", + "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723", + "sha256:96d4819e25bf3b685199b304a0029ce4a3caf98947ce8a066c9137cc78ad2c58", + "sha256:a77c6fb8d76e9c9f99f2f3437c1a4ac287b34eaf40997cfab1e9bd2be175ac39", + "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2", + "sha256:b97737a3ffbea79eebb062eb0d67d72307195035332501722a9ca86bab9e3ab2", + "sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d", + "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97", + "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b", + "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257", + "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff", + "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e" ], "index": "pypi", "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'", - "version": "==44.0.2" + "version": "==45.0.4" }, "defusedxml": { "hashes": [ @@ -303,10 +305,10 @@ }, "dj-database-url": { "hashes": [ - "sha256:ae52e8e634186b57e5a45e445da5dc407a819c2ceed8a53d1fac004cc5288787", - "sha256:bb0d414ba0ac5cd62773ec7f86f8cc378a9dbb00a80884c2fc08cc570452521e" + "sha256:749a7a42d88d6c741c1d2f4ab24c2ae0d5cd12f00f2d1d55ff9f5fadabe8a2c3", + "sha256:cbb84b2e3f372460b1e43692bf9fdc0c32e78930ee101db470cba56105fca1e5" ], - "version": "==2.3.0" + "version": "==3.0.0" }, "dj-email-url": { "hashes": [ @@ -334,20 +336,20 @@ }, "django-allow-cidr": { "hashes": [ - "sha256:11126c5bb9df3a61ff9d97304856ba7e5b26d46c6d456709a6d9e28483bff47f", - "sha256:382c5d7a9807279e3e96e4f4892b59163a2b30128c596902bf5f80e133e1ccbb" + "sha256:724ce76b7b4a25c641ddcd33777e2e95da622dd2c11937f0d76d0cd3d54b1622", + "sha256:d6f80230621dd5b19ec1665b85abf8218b02556ff7cf0ddda41e2607267a3277" ], "index": "pypi", - "version": "==0.7.1" + "version": "==0.8.0" }, "django-auditlog": { "hashes": [ - "sha256:92db1cf4a51ceca5c26b3ff46997d9e3305a02da1bd435e2efb5b8b6d300ce1f", - "sha256:9de49f80a4911135d136017123cd73461f869b4947eec14d5e76db4b88182f3f" + "sha256:435345b4055d16abfb4ada4bf11320f9e2f6d343874464471fa0041f13f3a474", + "sha256:6432a83fdf4397a726488d101fedcb62daafd6d4b825a0fc4c50e3657f5883cd" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==3.0.0" + "markers": "python_version >= '3.9'", + "version": "==3.1.2" }, "django-cache-url": { "hashes": [ @@ -367,11 +369,11 @@ }, "django-csp": { "hashes": [ - "sha256:19b2978b03fcd73517d7d67acbc04fbbcaec0facc3e83baa502965892d1e0719", - "sha256:ef0f1a9f7d8da68ae6e169c02e9ac661c0ecf04db70e0d1d85640512a68471c0" + "sha256:b27010bb702eb20a3dad329178df2b61a2b82d338b70fbdc13c3a3bd28712833", + "sha256:d5a0a05463a6b75a4f1fc1828c58c89af8db9364d09fc6e12f122b4d7f3d00dc" ], "index": "pypi", - "version": "==3.8" + "version": "==4.0" }, "django-fsm": { "hashes": [ @@ -383,12 +385,12 @@ }, "django-import-export": { "hashes": [ - "sha256:5514d09636e84e823a42cd5e79292f70f20d6d2feed117a145f5b64a5b44f168", - "sha256:bd3fe0aa15a2bce9de4be1a2f882e2c4539fdbfdfa16f2052c98dd7aec0f085c" + "sha256:1a79d851a95cad1fb0acdb668378bb0b5c9811bb8d70393d6bdd2158c1f53b00", + "sha256:e735db31b89cf6ff3bf7bb6ae205a7f175b5614d829893ebe39769ea7e94e443" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==4.3.7" + "version": "==4.3.8" }, "django-login-required-middleware": { "hashes": [ @@ -402,20 +404,20 @@ "phonenumberslite" ], "hashes": [ - "sha256:196c917b70c01a98e327f482eb8a4a4a55a29891db551f99078585397370b3ba", - "sha256:8a560fe1b01b94c9de8cde22bc373b695f023cc6df4baba00264cb079da9f631" + "sha256:9ba66873bf47401b13fc57797fbf85e785864a6bfdc8b9daa3a9012886567045", + "sha256:f19450625574ad3a2de047c409527d0e57699496849e71e53b53e8679ff67b27" ], - "markers": "python_version >= '3.8'", - "version": "==8.0.0" + "markers": "python_version >= '3.9'", + "version": "==8.1.0" }, "django-waffle": { "hashes": [ - "sha256:774f45b929627c9d303620c85419ce1da54066f2082d741af014f5bbd747e372", - "sha256:97709550f4e75ce2a20b13e29f39777e1439a968569f2ee89398ca368afd586c" + "sha256:3312851d9d926b76b9e90712355781700a383b82b5bf2b61e1f1be97532c0f3d", + "sha256:62f9d00eedf68dafb82657beab56e601bddedc1ea1ccfef91d83df8658708509" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==4.2.0" + "markers": "python_version >= '3.9'", + "version": "==5.0.0" }, "django-widget-tweaks": { "hashes": [ @@ -431,20 +433,20 @@ "django" ], "hashes": [ - "sha256:03db7ee2d50ec697b68814cd175a3a05a7c7954804e4e419ca8b570dc5a835cf", - "sha256:45bc56f1d53bbc59d8dd69bba97377dd88ec28b8229d81cedbd455b21789445b" + "sha256:22669a58d53c5b86a25d0231c4a41a6ebeb82d3942b8fbd9cf645890c92a1843", + "sha256:2b6c78a77dfefb57ca30d43a232270ecc82adabf67ab318e018084b9a3529e9b" ], "markers": "python_version >= '3.9'", - "version": "==14.1.1" + "version": "==14.2.0" }, "faker": { "hashes": [ - "sha256:8955706c56c28099585e9e2b6f814eb0a3a227eb36a2ee3eb9ab577c4764eacc", - "sha256:948bd27706478d3aa0b6f9f58b9f25207098f6ca79852c7b49c44a8ced2bc59b" + "sha256:7f69d579588c23d5ce671f3fa872654ede0e67047820255f43a4aa1925b89780", + "sha256:cb81c09ebe06c32a10971d1bbdb264bb0e22b59af59548f011ac4809556ce533" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==37.0.2" + "version": "==37.4.0" }, "fred-epplib": { "git": "https://github.com/cisagov/epplib.git", @@ -467,128 +469,110 @@ }, "gevent": { "hashes": [ - "sha256:1c3443b0ed23dcb7c36a748d42587168672953d368f2956b17fad36d43b58836", - "sha256:1d4fadc319b13ef0a3c44d2792f7918cf1bca27cacd4d41431c22e6b46668026", - "sha256:1ea50009ecb7f1327347c37e9eb6561bdbc7de290769ee1404107b9a9cba7cf1", - "sha256:2142704c2adce9cd92f6600f371afb2860a446bfd0be5bd86cca5b3e12130766", - "sha256:351d1c0e4ef2b618ace74c91b9b28b3eaa0dd45141878a964e03c7873af09f62", - "sha256:356b73d52a227d3313f8f828025b665deada57a43d02b1cf54e5d39028dbcf8d", - "sha256:3d882faa24f347f761f934786dde6c73aa6c9187ee710189f12dcc3a63ed4a50", - "sha256:58851f23c4bdb70390f10fc020c973ffcf409eb1664086792c8b1e20f25eef43", - "sha256:68bee86b6e1c041a187347ef84cf03a792f0b6c7238378bf6ba4118af11feaae", - "sha256:7398c629d43b1b6fd785db8ebd46c0a353880a6fab03d1cf9b6788e7240ee32e", - "sha256:816b3883fa6842c1cf9d2786722014a0fd31b6312cca1f749890b9803000bad6", - "sha256:81d918e952954675f93fb39001da02113ec4d5f4921bf5a0cc29719af6824e5d", - "sha256:85329d556aaedced90a993226d7d1186a539c843100d393f2349b28c55131c85", - "sha256:8619d5c888cb7aebf9aec6703e410620ef5ad48cdc2d813dd606f8aa7ace675f", - "sha256:8bd1419114e9e4a3ed33a5bad766afff9a3cf765cb440a582a1b3a9bc80c1aca", - "sha256:92e0d7759de2450a501effd99374256b26359e801b2d8bf3eedd3751973e87f5", - "sha256:92fe5dfee4e671c74ffaa431fd7ffd0ebb4b339363d24d0d944de532409b935e", - "sha256:97e2f3999a5c0656f42065d02939d64fffaf55861f7d62b0107a08f52c984897", - "sha256:9d3b249e4e1f40c598ab8393fc01ae6a3b4d51fc1adae56d9ba5b315f6b2d758", - "sha256:a3d75fa387b69c751a3d7c5c3ce7092a171555126e136c1d21ecd8b50c7a6e46", - "sha256:a5f1701ce0f7832f333dd2faf624484cbac99e60656bfbb72504decd42970f0f", - "sha256:b24d800328c39456534e3bc3e1684a28747729082684634789c2f5a8febe7671", - "sha256:b5efe72e99b7243e222ba0c2c2ce9618d7d36644c166d63373af239da1036bab", - "sha256:b7bfcfe08d038e1fa6de458891bca65c1ada6d145474274285822896a858c870", - "sha256:beede1d1cff0c6fafae3ab58a0c470d7526196ef4cd6cc18e7769f207f2ea4eb", - "sha256:c6b775381f805ff5faf250e3a07c0819529571d19bb2a9d474bee8c3f90d66af", - "sha256:c9c935b83d40c748b6421625465b7308d87c7b3717275acd587eef2bd1c39546", - "sha256:ca845138965c8c56d1550499d6b923eb1a2331acfa9e13b817ad8305dde83d11", - "sha256:d618e118fdb7af1d6c1a96597a5cd6ac84a9f3732b5be8515c6a66e098d498b6", - "sha256:d6c0a065e31ef04658f799215dddae8752d636de2bed61365c358f9c91e7af61", - "sha256:d740206e69dfdfdcd34510c20adcb9777ce2cc18973b3441ab9767cd8948ca8a", - "sha256:d7886b63ebfb865178ab28784accd32f287d5349b3ed71094c86e4d3ca738af5", - "sha256:d9347690f4e53de2c4af74e62d6fabc940b6d4a6cad555b5a379f61e7d3f2a8e", - "sha256:d9ca80711e6553880974898d99357fb649e062f9058418a92120ca06c18c3c59", - "sha256:e24181d172f50097ac8fc272c8c5b030149b630df02d1c639ee9f878a470ba2b", - "sha256:ec68e270543ecd532c4c1d70fca020f90aa5486ad49c4f3b8b2e64a66f5c9274", - "sha256:f43f47e702d0c8e1b8b997c00f1601486f9f976f84ab704f8f11536e3fa144c9", - "sha256:ff96c5739834c9a594db0e12bf59cb3fa0e5102fc7b893972118a3166733d61c" + "sha256:017a7384c0cd1a5907751c991535a0699596e89725468a7fc39228312e10efa1", + "sha256:0bacf89a65489d26c7087669af89938d5bfd9f7afb12a07b57855b9fad6ccbd0", + "sha256:12380aba5c316e9ff53cc21d8ab80f4a91c0df3ada58f65d4f5eb2cf693db00e", + "sha256:1a93062609e8fa67ec97cd5fb9206886774b2a09b24887f40148c9c37e6fb71c", + "sha256:24484f80f14befb8822bf29554cfb3a26a26cb69cd1e5a8be9e23b4bd7a96e25", + "sha256:2534c23dc32bed62b659ed4fd9e198906179e68b26c9276a897e04163bdde806", + "sha256:2797885e9aeffdc98e1846723e5aa212e7ce53007dbef40d6fd2add264235c41", + "sha256:29ab729d50ae85077a68e0385f129f5b01052d01a0ae6d7fdc1824f5337905e4", + "sha256:2d316529b70d325b183b2f3f5cde958911ff7be12eb2b532b5c301f915dbbf1e", + "sha256:37ee34b77c7553777c0b8379915f75934c3f9c8cd32f7cd098ea43c9323c2276", + "sha256:3fae8533f9d0ef3348a1f503edcfb531ef7a0236b57da1e24339aceb0ce52922", + "sha256:469c86d02fccad7e2a3d82fe22237e47ecb376fbf4710bc18747b49c50716817", + "sha256:582c948fa9a23188b890d0bc130734a506d039a2e5ad87dae276a456cc683e61", + "sha256:5b6106e2414b1797133786258fa1962a5e836480e4d5e861577f9fc63b673a5a", + "sha256:60ad4ca9ca2c4cc8201b607c229cd17af749831e371d006d8a91303bb5568eb1", + "sha256:7b95815fe44f318ebbfd733b6428b4cb18cc5e68f1c40e8501dd69cc1f42a83d", + "sha256:7f0694daab1a041b69a53f53c2141c12994892b2503870515cabe6a5dbd2a928", + "sha256:80d20592aeabcc4e294fd441fd43d45cb537437fd642c374ea9d964622fad229", + "sha256:8e5a0fab5e245b15ec1005b3666b0a2e867c26f411c8fe66ae1afe07174a30e9", + "sha256:8fdc7446895fa184890d8ca5ea61e502691114f9db55c9b76adc33f3086c4368", + "sha256:9fa6aa0da224ed807d3b76cdb4ee8b54d4d4d5e018aed2478098e685baae7896", + "sha256:a022a9de9275ce0b390b7315595454258c525dc8287a03f1a6cacc5878ab7cbc", + "sha256:a8ba0257542ccbb72a8229dc34d00844ccdfba110417e4b7b34599548d0e20e9", + "sha256:b83aff2441c7d4ee93e519989713b7c2607d4510abe990cd1d04f641bc6c03af", + "sha256:b87a4b66edb3808d4d07bbdb0deed5a710cf3d3c531e082759afd283758bb649", + "sha256:bb673eb291c19370f69295f7a881a536451408481e2e3deec3f41dedb7c281ec", + "sha256:bc899212d90f311784c58938a9c09c59802fb6dc287a35fabdc36d180f57f575", + "sha256:c1325ed44225c8309c0dd188bdbbbee79e1df8c11ceccac226b861c7d52e4837", + "sha256:c7b32d9c3b5294b39ea9060e20c582e49e1ec81edbfeae6cf05f8ad0829cb13d", + "sha256:c7b80a37f2fb45ee4a8f7e64b77dd8a842d364384046e394227b974a4e9c9a52", + "sha256:cad0821dff998c7c60dd238f92cd61380342c47fb9e92e1a8705d9b5ac7c16e8", + "sha256:cde6aaac36b54332e10ea2a5bc0de6a8aba6c205c92603fe4396e3777c88e05d", + "sha256:d87c0a1bd809d8f70f96b9b229779ec6647339830b8888a192beed33ac8d129f", + "sha256:e30169ef9cc0a57930bfd8fe14d86bc9d39fb96d278e3891e85cbe7b46058a97", + "sha256:e5f358e81e27b1a7f2fb2f5219794e13ab5f59ce05571aa3877cfac63adb97db", + "sha256:e72ad5f8d9c92df017fb91a1f6a438cfb63b0eff4b40904ff81b40cb8150078c", + "sha256:f076779050029a82feb0cb1462021d3404d22f80fa76a181b1a7889cd4d6b519", + "sha256:f6ba33c13db91ffdbb489a4f3d177a261ea1843923e1d68a5636c53fe98fa5ce", + "sha256:fcd5bcad3102bde686d0adcc341fade6245186050ce14386d547ccab4bd54310" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==24.11.1" + "version": "==25.5.1" }, "greenlet": { "hashes": [ - "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", - "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7", - "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", - "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", - "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", - "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", - "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", - "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", - "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", - "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa", - "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", - "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", - "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", - "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22", - "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9", - "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", - "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba", - "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3", - "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", - "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", - "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291", - "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", - "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", - "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", - "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", - "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef", - "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c", - "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", - "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c", - "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", - "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", - "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8", - "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d", - "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", - "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145", - "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", - "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", - "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e", - "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", - "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1", - "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef", - "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", - "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", - "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", - "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437", - "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd", - "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981", - "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", - "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", - "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798", - "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", - "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", - "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", - "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", - "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af", - "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", - "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", - "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42", - "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e", - "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81", - "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", - "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", - "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc", - "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de", - "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111", - "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", - "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", - "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", - "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", - "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", - "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803", - "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", - "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f" + "sha256:003c930e0e074db83559edc8705f3a2d066d4aa8c2f198aff1e454946efd0f26", + "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36", + "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892", + "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83", + "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688", + "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be", + "sha256:22eb5ba839c4b2156f18f76768233fe44b23a31decd9cc0d4cc8141c211fd1b4", + "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d", + "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5", + "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", + "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", + "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86", + "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c", + "sha256:42efc522c0bd75ffa11a71e09cd8a399d83fafe36db250a87cf1dacfaa15dc64", + "sha256:4532f0d25df67f896d137431b13f4cdce89f7e3d4a96387a41290910df4d3a57", + "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b", + "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", + "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95", + "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3", + "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147", + "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db", + "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb", + "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c", + "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00", + "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849", + "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba", + "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac", + "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822", + "sha256:7e70ea4384b81ef9e84192e8a77fb87573138aa5d4feee541d8014e452b434da", + "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97", + "sha256:8324319cbd7b35b97990090808fdc99c27fe5338f87db50514959f8059999805", + "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34", + "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141", + "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", + "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", + "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b", + "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", + "sha256:8c37ef5b3787567d322331d5250e44e42b58c8c713859b8a04c6065f27efbf72", + "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", + "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc", + "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163", + "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302", + "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", + "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392", + "sha256:aaa7aae1e7f75eaa3ae400ad98f8644bb81e1dc6ba47ce8a93d3f17274e08322", + "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d", + "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264", + "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b", + "sha256:ce539fb52fb774d0802175d37fcff5c723e2c7d249c65916257f0a940cee8904", + "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf", + "sha256:d760f9bdfe79bff803bad32b4d8ffb2c1d2ce906313fc10a83976ffb73d64ca7", + "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a", + "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712", + "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==3.1.1" + "markers": "python_version >= '3.9'", + "version": "==3.2.3" }, "gunicorn": { "hashes": [ @@ -617,155 +601,149 @@ }, "lxml": { "hashes": [ - "sha256:016b96c58e9a4528219bb563acf1aaaa8bc5452e7651004894a973f03b84ba81", - "sha256:05123fad495a429f123307ac6d8fd6f977b71e9a0b6d9aeeb8f80c017cb17131", - "sha256:057e30d0012439bc54ca427a83d458752ccda725c1c161cc283db07bcad43cf9", - "sha256:06a20d607a86fccab2fc15a77aa445f2bdef7b49ec0520a842c5c5afd8381576", - "sha256:094b28ed8a8a072b9e9e2113a81fda668d2053f2ca9f2d202c2c8c7c2d6516b1", - "sha256:0bcfadea3cdc68e678d2b20cb16a16716887dd00a881e16f7d806c2138b8ff0c", - "sha256:0d6b2fa86becfa81f0a0271ccb9eb127ad45fb597733a77b92e8a35e53414914", - "sha256:0f2cfae0688fd01f7056a17367e3b84f37c545fb447d7282cf2c242b16262607", - "sha256:106b7b5d2977b339f1e97efe2778e2ab20e99994cbb0ec5e55771ed0795920c8", - "sha256:133f3493253a00db2c870d3740bc458ebb7d937bd0a6a4f9328373e0db305709", - "sha256:136bf638d92848a939fd8f0e06fcf92d9f2e4b57969d94faae27c55f3d85c05b", - "sha256:155e1a5693cf4b55af652f5c0f78ef36596c7f680ff3ec6eb4d7d85367259b2c", - "sha256:1637fa31ec682cd5760092adfabe86d9b718a75d43e65e211d5931809bc111e7", - "sha256:172d65f7c72a35a6879217bcdb4bb11bc88d55fb4879e7569f55616062d387c2", - "sha256:17b5d7f8acf809465086d498d62a981fa6a56d2718135bb0e4aa48c502055f5c", - "sha256:198bb4b4dd888e8390afa4f170d4fa28467a7eaf857f1952589f16cfbb67af27", - "sha256:1b6f92e35e2658a5ed51c6634ceb5ddae32053182851d8cad2a5bc102a359b33", - "sha256:1b92fe86e04f680b848fff594a908edfa72b31bfc3499ef7433790c11d4c8cd8", - "sha256:1bcc211542f7af6f2dfb705f5f8b74e865592778e6cafdfd19c792c244ccce19", - "sha256:1c93ed3c998ea8472be98fb55aed65b5198740bfceaec07b2eba551e55b7b9ae", - "sha256:203b1d3eaebd34277be06a3eb880050f18a4e4d60861efba4fb946e31071a295", - "sha256:22ec2b3c191f43ed21f9545e9df94c37c6b49a5af0a874008ddc9132d49a2d9c", - "sha256:231cf4d140b22a923b1d0a0a4e0b4f972e5893efcdec188934cc65888fd0227b", - "sha256:236610b77589faf462337b3305a1be91756c8abc5a45ff7ca8f245a71c5dab70", - "sha256:29bfc8d3d88e56ea0a27e7c4897b642706840247f59f4377d81be8f32aa0cfbf", - "sha256:2b8969dbc8d09d9cd2ae06362c3bad27d03f433252601ef658a49bd9f2b22d79", - "sha256:2dd0b80ac2d8f13ffc906123a6f20b459cb50a99222d0da492360512f3e50f84", - "sha256:2df7ed5edeb6bd5590914cd61df76eb6cce9d590ed04ec7c183cf5509f73530d", - "sha256:2e4a570f6a99e96c457f7bec5ad459c9c420ee80b99eb04cbfcfe3fc18ec6423", - "sha256:2f1be45d4c15f237209bbf123a0e05b5d630c8717c42f59f31ea9eae2ad89394", - "sha256:2f23cf50eccb3255b6e913188291af0150d89dab44137a69e14e4dcb7be981f1", - "sha256:3031e4c16b59424e8d78522c69b062d301d951dc55ad8685736c3335a97fc270", - "sha256:33e06717c00c788ab4e79bc4726ecc50c54b9bfb55355eae21473c145d83c2d2", - "sha256:364de8f57d6eda0c16dcfb999af902da31396949efa0e583e12675d09709881b", - "sha256:3715cdf0dd31b836433af9ee9197af10e3df41d273c19bb249230043667a5dfd", - "sha256:3bb8149840daf2c3f97cebf00e4ed4a65a0baff888bf2605a8d0135ff5cf764e", - "sha256:3c3c8b55c7fc7b7e8877b9366568cc73d68b82da7fe33d8b98527b73857a225f", - "sha256:3d68eeef7b4d08a25e51897dac29bcb62aba830e9ac6c4e3297ee7c6a0cf6439", - "sha256:3dddf0fb832486cc1ea71d189cb92eb887826e8deebe128884e15020bb6e3f61", - "sha256:3edbb9c9130bac05d8c3fe150c51c337a471cc7fdb6d2a0a7d3a88e88a829314", - "sha256:3effe081b3135237da6e4c4530ff2a868d3f80be0bda027e118a5971285d42d0", - "sha256:422c179022ecdedbe58b0e242607198580804253da220e9454ffe848daa1cfd2", - "sha256:42978a68d3825eaac55399eb37a4d52012a205c0c6262199b8b44fcc6fd686e8", - "sha256:4399b4226c4785575fb20998dc571bc48125dc92c367ce2602d0d70e0c455eb0", - "sha256:45fbb70ccbc8683f2fb58bea89498a7274af1d9ec7995e9f4af5604e028233fc", - "sha256:4867361c049761a56bd21de507cab2c2a608c55102311d142ade7dab67b34f32", - "sha256:48fd46bf7155def2e15287c6f2b133a2f78e2d22cdf55647269977b873c65499", - "sha256:4b0d5cdba1b655d5b18042ac9c9ff50bda33568eb80feaaca4fc237b9c4fbfde", - "sha256:4df0ec814b50275ad6a99bc82a38b59f90e10e47714ac9871e1b223895825468", - "sha256:4e52e1b148867b01c05e21837586ee307a01e793b94072d7c7b91d2c2da02ffe", - "sha256:514fe78fc4b87e7a7601c92492210b20a1b0c6ab20e71e81307d9c2e377c64de", - "sha256:524ccfded8989a6595dbdda80d779fb977dbc9a7bc458864fc9a0c2fc15dc877", - "sha256:528f3a0498a8edc69af0559bdcf8a9f5a8bf7c00051a6ef3141fdcf27017bbf5", - "sha256:52d82b0d436edd6a1d22d94a344b9a58abd6c68c357ed44f22d4ba8179b37629", - "sha256:5412500e0dc5481b1ee9cf6b38bb3b473f6e411eb62b83dc9b62699c3b7b79f7", - "sha256:585c4dc429deebc4307187d2b71ebe914843185ae16a4d582ee030e6cfbb4d8a", - "sha256:5865b270b420eda7b68928d70bb517ccbe045e53b1a428129bb44372bf3d7dd5", - "sha256:5881aaa4bf3a2d086c5f20371d3a5856199a0d8ac72dd8d0dbd7a2ecfc26ab73", - "sha256:5885bc586f1edb48e5d68e7a4b4757b5feb2a496b64f462b4d65950f5af3364f", - "sha256:5a11b16a33656ffc43c92a5343a28dc71eefe460bcc2a4923a96f292692709f6", - "sha256:5a997b784a639e05b9d4053ef3b20c7e447ea80814a762f25b8ed5a89d261eac", - "sha256:5be8f5e4044146a69c96077c7e08f0709c13a314aa5315981185c1f00235fe65", - "sha256:63d57fc94eb0bbb4735e45517afc21ef262991d8758a8f2f05dd6e4174944519", - "sha256:673b9d8e780f455091200bba8534d5f4f465944cbdd61f31dc832d70e29064a5", - "sha256:67d2f8ad9dcc3a9e826bdc7802ed541a44e124c29b7d95a679eeb58c1c14ade8", - "sha256:67f5e80adf0aafc7b5454f2c1cb0cde920c9b1f2cbd0485f07cc1d0497c35c5d", - "sha256:68018c4c67d7e89951a91fbd371e2e34cd8cfc71f0bb43b5332db38497025d51", - "sha256:6c4dd3bfd0c82400060896717dd261137398edb7e524527438c54a8c34f736bf", - "sha256:71f31eda4e370f46af42fc9f264fafa1b09f46ba07bdbee98f25689a04b81c20", - "sha256:7512b4d0fc5339d5abbb14d1843f70499cab90d0b864f790e73f780f041615d7", - "sha256:75fa3d6946d317ffc7016a6fcc44f42db6d514b7fdb8b4b28cbe058303cb6e53", - "sha256:779e851fd0e19795ccc8a9bb4d705d6baa0ef475329fe44a13cf1e962f18ff1e", - "sha256:796520afa499732191e39fc95b56a3b07f95256f2d22b1c26e217fb69a9db5b5", - "sha256:7aae7a3d63b935babfdc6864b31196afd5145878ddd22f5200729006366bc4d5", - "sha256:7b82e67c5feb682dbb559c3e6b78355f234943053af61606af126df2183b9ef9", - "sha256:7c0536bd9178f754b277a3e53f90f9c9454a3bd108b1531ffff720e082d824f2", - "sha256:7eda194dd46e40ec745bf76795a7cccb02a6a41f445ad49d3cf66518b0bd9cff", - "sha256:82a4bb10b0beef1434fb23a09f001ab5ca87895596b4581fd53f1e5145a8934a", - "sha256:85c4f11be9cf08917ac2a5a8b6e1ef63b2f8e3799cec194417e76826e5f1de9c", - "sha256:88b72eb7222d918c967202024812c2bfb4048deeb69ca328363fb8e15254c549", - "sha256:89934f9f791566e54c1d92cdc8f8fd0009447a5ecdb1ec6b810d5f8c4955f6be", - "sha256:8b1942b3e4ed9ed551ed3083a2e6e0772de1e5e3aca872d955e2e86385fb7ff9", - "sha256:8ffb141361108e864ab5f1813f66e4e1164181227f9b1f105b042729b6c15125", - "sha256:8fffc08de02071c37865a155e5ea5fce0282e1546fd5bde7f6149fcaa32558ac", - "sha256:91fb6a43d72b4f8863d21f347a9163eecbf36e76e2f51068d59cd004c506f332", - "sha256:928e75a7200a4c09e6efc7482a1337919cc61fe1ba289f297827a5b76d8969c2", - "sha256:96eef5b9f336f623ffc555ab47a775495e7e8846dde88de5f941e2906453a1ce", - "sha256:a0611da6b07dd3720f492db1b463a4d1175b096b49438761cc9f35f0d9eaaef5", - "sha256:a091026c3bf7519ab1e64655a3f52a59ad4a4e019a6f830c24d6430695b1cf6a", - "sha256:a22f66270bd6d0804b02cd49dae2b33d4341015545d17f8426f2c4e22f557a23", - "sha256:a243132767150a44e6a93cd1dde41010036e1cbc63cc3e9fe1712b277d926ce3", - "sha256:a31fa7536ec1fb7155a0cd3a4e3d956c835ad0a43e3610ca32384d01f079ea1c", - "sha256:a364e8e944d92dcbf33b6b494d4e0fb3499dcc3bd9485beb701aa4b4201fa414", - "sha256:a4058f16cee694577f7e4dd410263cd0ef75644b43802a689c2b3c2a7e69453b", - "sha256:a4b382e0e636ed54cd278791d93fe2c4f370772743f02bcbe431a160089025c9", - "sha256:a83d3adea1e0ee36dac34627f78ddd7f093bb9cfc0a8e97f1572a949b695cb98", - "sha256:a8ade0363f776f87f982572c2860cc43c65ace208db49c76df0a21dde4ddd16e", - "sha256:aa59974880ab5ad8ef3afaa26f9bda148c5f39e06b11a8ada4660ecc9fb2feb3", - "sha256:aa826340a609d0c954ba52fd831f0fba2a4165659ab0ee1a15e4aac21f302406", - "sha256:aaca5a812f050ab55426c32177091130b1e49329b3f002a32934cd0245571307", - "sha256:ae82fce1d964f065c32c9517309f0c7be588772352d2f40b1574a214bd6e6098", - "sha256:aed57b541b589fa05ac248f4cb1c46cbb432ab82cbd467d1c4f6a2bdc18aecf9", - "sha256:afa578b6524ff85fb365f454cf61683771d0170470c48ad9d170c48075f86725", - "sha256:b0884e3f22d87c30694e625b1e62e6f30d39782c806287450d9dc2fdf07692fd", - "sha256:b2aca14c235c7a08558fe0a4786a1a05873a01e86b474dfa8f6df49101853a4e", - "sha256:b450d7cabcd49aa7ab46a3c6aa3ac7e1593600a1a0605ba536ec0f1b99a04322", - "sha256:b725e70d15906d24615201e650d5b0388b08a5187a55f119f25874d0103f90dd", - "sha256:bfbbab9316330cf81656fed435311386610f78b6c93cc5db4bebbce8dd146675", - "sha256:c093c7088b40d8266f57ed71d93112bd64c6724d31f0794c1e52cc4857c28e0e", - "sha256:c2e49dc23a10a1296b04ca9db200c44d3eb32c8d8ec532e8c1fd24792276522a", - "sha256:c4393600915c308e546dc7003d74371744234e8444a28622d76fe19b98fa59d1", - "sha256:c5ae125276f254b01daa73e2c103363d3e99e3e10505686ac7d9d2442dd4627a", - "sha256:c6aacf00d05b38a5069826e50ae72751cb5bc27bdc4d5746203988e429b385bb", - "sha256:c76722b5ed4a31ba103e0dc77ab869222ec36efe1a614e42e9bcea88a36186fe", - "sha256:c809eef167bf4a57af4b03007004896f5c60bd38dc3852fcd97a26eae3d4c9e6", - "sha256:c92ea6d9dd84a750b2bae72ff5e8cf5fdd13e58dda79c33e057862c29a8d5b50", - "sha256:cb659702a45136c743bc130760c6f137870d4df3a9e14386478b8a0511abcfca", - "sha256:ce0930a963ff593e8bb6fda49a503911accc67dee7e5445eec972668e672a0f0", - "sha256:d0751528b97d2b19a388b302be2a0ee05817097bab46ff0ed76feeec24951f78", - "sha256:d184f85ad2bb1f261eac55cddfcf62a70dee89982c978e92b9a74a1bfef2e367", - "sha256:d2a3e412ce1849be34b45922bfef03df32d1410a06d1cdeb793a343c2f1fd666", - "sha256:d61ec60945d694df806a9aec88e8f29a27293c6e424f8ff91c80416e3c617645", - "sha256:db0c742aad702fd5d0c6611a73f9602f20aec2007c102630c06d7633d9c8f09a", - "sha256:db4743e30d6f5f92b6d2b7c86b3ad250e0bad8dee4b7ad8a0c44bfb276af89a3", - "sha256:dbf7bebc2275016cddf3c997bf8a0f7044160714c64a9b83975670a04e6d2252", - "sha256:de1fc314c3ad6bc2f6bd5b5a5b9357b8c6896333d27fdbb7049aea8bd5af2d79", - "sha256:df7e5edac4778127f2bf452e0721a58a1cfa4d1d9eac63bdd650535eb8543615", - "sha256:e220f7b3e8656ab063d2eb0cd536fafef396829cafe04cb314e734f87649058f", - "sha256:e3c623923967f3e5961d272718655946e5322b8d058e094764180cdee7bab1af", - "sha256:e69add9b6b7b08c60d7ff0152c7c9a6c45b4a71a919be5abde6f98f1ea16421c", - "sha256:e8e0d177b1fe251c3b1b914ab64135475c5273c8cfd2857964b2e3bb0fe196a7", - "sha256:ef45f31aec9be01379fc6c10f1d9c677f032f2bac9383c827d44f620e8a88407", - "sha256:f1208c1c67ec9e151d78aa3435aa9b08a488b53d9cfac9b699f15255a3461ef2", - "sha256:f12582b8d3b4c6be1d298c49cb7ae64a3a73efaf4c2ab4e37db182e3545815ac", - "sha256:f1de541a9893cf8a1b1db9bf0bf670a2decab42e3e82233d36a74eda7822b4c9", - "sha256:f4eac0584cdc3285ef2e74eee1513a6001681fd9753b259e8159421ed28a72e5", - "sha256:f7b64fcd670bca8800bc10ced36620c6bbb321e7bc1214b9c0c0df269c1dddc2", - "sha256:fb7c61d4be18e930f75948705e9718618862e6fc2ed0d7159b2262be73f167a2" + "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5", + "sha256:073eb6dcdf1f587d9b88c8c93528b57eccda40209cf9be549d469b942b41d70b", + "sha256:09846782b1ef650b321484ad429217f5154da4d6e786636c38e434fa32e94e49", + "sha256:0a01ce7d8479dce84fc03324e3b0c9c90b1ece9a9bb6a1b6c9025e7e4520e78c", + "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b", + "sha256:0cef4feae82709eed352cd7e97ae062ef6ae9c7b5dbe3663f104cd2c0e8d94ba", + "sha256:0e108352e203c7afd0eb91d782582f00a0b16a948d204d4dec8565024fafeea5", + "sha256:0ea0252b51d296a75f6118ed0d8696888e7403408ad42345d7dfd0d1e93309a7", + "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422", + "sha256:1320091caa89805df7dcb9e908add28166113dcd062590668514dbd510798c88", + "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8", + "sha256:14479c2ad1cb08b62bb941ba8e0e05938524ee3c3114644df905d2331c76cd57", + "sha256:151d6c40bc9db11e960619d2bf2ec5829f0aaffb10b41dcf6ad2ce0f3c0b2325", + "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a", + "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982", + "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8", + "sha256:1b717b00a71b901b4667226bba282dd462c42ccf618ade12f9ba3674e1fabc55", + "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2", + "sha256:20e16c08254b9b6466526bc1828d9370ee6c0d60a4b64836bc3ac2917d1e16df", + "sha256:226046e386556a45ebc787871d6d2467b32c37ce76c2680f5c608e25823ffc84", + "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551", + "sha256:24f6df5f24fc3385f622c0c9d63fe34604893bc1a5bdbb2dbf5870f85f9a404a", + "sha256:27a9ded0f0b52098ff89dd4c418325b987feed2ea5cc86e8860b0f844285d740", + "sha256:29f451a4b614a7b5b6c2e043d7b64a15bd8304d7e767055e8ab68387a8cacf4e", + "sha256:2b31a3a77501d86d8ade128abb01082724c0dfd9524f542f2f07d693c9f1175f", + "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60", + "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e", + "sha256:31e63621e073e04697c1b2d23fcb89991790eef370ec37ce4d5d469f40924ed6", + "sha256:32697d2ea994e0db19c1df9e40275ffe84973e4232b5c274f47e7c1ec9763cdd", + "sha256:3a3178b4873df8ef9457a4875703488eb1622632a9cee6d76464b60e90adbfcd", + "sha256:3b9c2754cef6963f3408ab381ea55f47dabc6f78f4b8ebb0f0b25cf1ac1f7609", + "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20", + "sha256:3e6d5557989cdc3ebb5302bbdc42b439733a841891762ded9514e74f60319ad6", + "sha256:4025bf2884ac4370a3243c5aa8d66d3cb9e15d3ddd0af2d796eccc5f0244390e", + "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61", + "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4", + "sha256:43d549b876ce64aa18b2328faff70f5877f8c6dede415f80a2f799d31644d776", + "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779", + "sha256:47fb24cc0f052f0576ea382872b3fc7e1f7e3028e53299ea751839418ade92a6", + "sha256:48b4afaf38bf79109bb060d9016fad014a9a48fb244e11b94f74ae366a64d252", + "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c", + "sha256:4aa412a82e460571fad592d0f93ce9935a20090029ba08eca05c614f99b0cc92", + "sha256:4b7ce10634113651d6f383aa712a194179dcd496bd8c41e191cec2099fa09de5", + "sha256:4cd915c0fb1bed47b5e6d6edd424ac25856252f09120e3e8ba5154b6b921860e", + "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f", + "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54", + "sha256:50441c9de951a153c698b9b99992e806b71c1f36d14b154592580ff4a9d0d877", + "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e", + "sha256:53370c26500d22b45182f98847243efb518d268374a9570409d2e2276232fd37", + "sha256:53d9469ab5460402c19553b56c3648746774ecd0681b1b27ea74d5d8a3ef5590", + "sha256:56dbdbab0551532bb26c19c914848d7251d73edb507c3079d6805fa8bba5b706", + "sha256:5a99d86351f9c15e4a901fc56404b485b1462039db59288b203f8c629260a142", + "sha256:5cca36a194a4eb4e2ed6be36923d3cffd03dcdf477515dea687185506583d4c9", + "sha256:5f11a1526ebd0dee85e7b1e39e39a0cc0d9d03fb527f56d8457f6df48a10dc0c", + "sha256:61c7bbf432f09ee44b1ccaa24896d21075e533cd01477966a5ff5a71d88b2f56", + "sha256:639978bccb04c42677db43c79bdaa23785dc7f9b83bfd87570da8207872f1ce5", + "sha256:63e7968ff83da2eb6fdda967483a7a023aa497d85ad8f05c3ad9b1f2e8c84987", + "sha256:664cdc733bc87449fe781dbb1f309090966c11cc0c0cd7b84af956a02a8a4729", + "sha256:67ed8a40665b84d161bae3181aa2763beea3747f748bca5874b4af4d75998f87", + "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7", + "sha256:6854f8bd8a1536f8a1d9a3655e6354faa6406621cf857dc27b681b69860645c7", + "sha256:696ea9e87442467819ac22394ca36cb3d01848dad1be6fac3fb612d3bd5a12cf", + "sha256:6ef80aeac414f33c24b3815ecd560cee272786c3adfa5f31316d8b349bfade28", + "sha256:72ac9762a9f8ce74c9eed4a4e74306f2f18613a6b71fa065495a67ac227b3056", + "sha256:75133890e40d229d6c5837b0312abbe5bac1c342452cf0e12523477cd3aa21e7", + "sha256:7605c1c32c3d6e8c990dd28a0970a3cbbf1429d5b92279e37fda05fb0c92190e", + "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0", + "sha256:795f61bcaf8770e1b37eec24edf9771b307df3af74d1d6f27d812e15a9ff3872", + "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079", + "sha256:7a62cc23d754bb449d63ff35334acc9f5c02e6dae830d78dab4dd12b78a524f4", + "sha256:7be701c24e7f843e6788353c055d806e8bd8466b52907bafe5d13ec6a6dbaecd", + "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9", + "sha256:7ce1a171ec325192c6a636b64c94418e71a1964f56d002cc28122fceff0b6121", + "sha256:891f7f991a68d20c75cb13c5c9142b2a3f9eb161f1f12a9489c82172d1f133c0", + "sha256:8f82125bc7203c5ae8633a7d5d20bcfdff0ba33e436e4ab0abc026a53a8960b7", + "sha256:91505d3ddebf268bb1588eb0f63821f738d20e1e7f05d3c647a5ca900288760b", + "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d", + "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76", + "sha256:9459e6892f59ecea2e2584ee1058f5d8f629446eab52ba2305ae13a32a059530", + "sha256:9776af1aad5a4b4a1317242ee2bea51da54b2a7b7b48674be736d463c999f37d", + "sha256:97dac543661e84a284502e0cf8a67b5c711b0ad5fb661d1bd505c02f8cf716d7", + "sha256:98a3912194c079ef37e716ed228ae0dcb960992100461b704aea4e93af6b0bb9", + "sha256:9b4a3bd174cc9cdaa1afbc4620c049038b441d6ba07629d89a83b408e54c35cd", + "sha256:9c886b481aefdf818ad44846145f6eaf373a20d200b5ce1a5c8e1bc2d8745410", + "sha256:9ceaf423b50ecfc23ca00b7f50b64baba85fb3fb91c53e2c9d00bc86150c7e40", + "sha256:a11a96c3b3f7551c8a8109aa65e8594e551d5a84c76bf950da33d0fb6dfafab7", + "sha256:a3bcdde35d82ff385f4ede021df801b5c4a5bcdfb61ea87caabcebfc4945dc1b", + "sha256:a7fb111eef4d05909b82152721a59c1b14d0f365e2be4c742a473c5d7372f4f5", + "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5", + "sha256:a8c9b7f16b63e65bbba889acb436a1034a82d34fa09752d754f88d708eca80e1", + "sha256:a8ef956fce64c8551221f395ba21d0724fed6b9b6242ca4f2f7beb4ce2f41997", + "sha256:ab339536aa798b1e17750733663d272038bf28069761d5be57cb4a9b0137b4f8", + "sha256:ac7ba71f9561cd7d7b55e1ea5511543c0282e2b6450f122672a2694621d63b7e", + "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc", + "sha256:aea7c06667b987787c7d1f5e1dfcd70419b711cdb47d6b4bb4ad4b76777a0563", + "sha256:aefe1a7cb852fa61150fcb21a8c8fcea7b58c4cb11fbe59c97a0a4b31cae3c8c", + "sha256:b0989737a3ba6cf2a16efb857fb0dfa20bc5c542737fddb6d893fde48be45433", + "sha256:b108134b9667bcd71236c5a02aad5ddd073e372fb5d48ea74853e009fe38acb6", + "sha256:b12cb6527599808ada9eb2cd6e0e7d3d8f13fe7bbb01c6311255a15ded4c7ab4", + "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4", + "sha256:b67319b4aef1a6c56576ff544b67a2a6fbd7eaee485b241cabf53115e8908b8f", + "sha256:b7c86884ad23d61b025989d99bfdd92a7351de956e01c61307cb87035960bcb1", + "sha256:b92b69441d1bd39f4940f9eadfa417a25862242ca2c396b406f9272ef09cdcaa", + "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f", + "sha256:bda3ea44c39eb74e2488297bb39d47186ed01342f0022c8ff407c250ac3f498e", + "sha256:be2ba4c3c5b7900246a8f866580700ef0d538f2ca32535e991027bdaba944063", + "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4", + "sha256:c5d32f5284012deaccd37da1e2cd42f081feaa76981f0eaa474351b68df813c5", + "sha256:c6364038c519dffdbe07e3cf42e6a7f8b90c275d4d1617a69bb59734c1a2d571", + "sha256:c70e93fba207106cb16bf852e421c37bbded92acd5964390aad07cb50d60f5cf", + "sha256:ca755eebf0d9e62d6cb013f1261e510317a41bf4650f22963474a663fdfe02aa", + "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d", + "sha256:ce31158630a6ac85bddd6b830cffd46085ff90498b397bd0a259f59d27a12188", + "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de", + "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", + "sha256:d2ed1b3cb9ff1c10e6e8b00941bb2e5bb568b307bfc6b17dffbbe8be5eecba86", + "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82", + "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f", + "sha256:dc0af80267edc68adf85f2a5d9be1cdf062f973db6790c1d065e45025fa26140", + "sha256:de5b4e1088523e2b6f730d0509a9a813355b7f5659d70eb4f319c76beea2e250", + "sha256:de6f6bb8a7840c7bf216fb83eec4e2f79f7325eca8858167b68708b929ab2172", + "sha256:df53330a3bff250f10472ce96a9af28628ff1f4efc51ccba351a8820bca2a8ba", + "sha256:e094ec83694b59d263802ed03a8384594fcce477ce484b0cbcd0008a211ca751", + "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff", + "sha256:e7bc6df34d42322c5289e37e9971d6ed114e3776b45fa879f734bded9d1fea9c", + "sha256:eaf24066ad0b30917186420d51e2e3edf4b0e2ea68d8cd885b14dc8afdcf6556", + "sha256:ecf4c4b83f1ab3d5a7ace10bafcb6f11df6156857a3c418244cef41ca9fa3e44", + "sha256:ef5a7178fcc73b7d8c07229e89f8eb45b2908a9238eb90dcfc46571ccf0383b8", + "sha256:f5cb182f6396706dc6cc1896dd02b1c889d644c081b0cdec38747573db88a7d7", + "sha256:fa0e294046de09acd6146be0ed6727d1f42ded4ce3ea1e9a19c11b6774eea27c", + "sha256:fb54f7c6bafaa808f27166569b1511fc42701a7713858dddc08afdde9746849e", + "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539" ], "markers": "python_version >= '3.6'", - "version": "==5.3.1" + "version": "==5.4.0" }, "mako": { "hashes": [ - "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1", - "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac" + "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", + "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59" ], "markers": "python_version >= '3.8'", - "version": "==1.3.9" + "version": "==1.3.10" }, "markupsafe": { "hashes": [ @@ -836,11 +814,11 @@ }, "marshmallow": { "hashes": [ - "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", - "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6" + "sha256:3b6e80aac299a7935cfb97ed01d1854fb90b5079430969af92118ea1b12a8d55", + "sha256:e7b0528337e9990fd64950f8a6b3a1baabed09ad17a0dfb844d701151f92d203" ], "markers": "python_version >= '3.9'", - "version": "==3.26.1" + "version": "==4.0.0" }, "oic": { "hashes": [ @@ -860,18 +838,18 @@ }, "packaging": { "hashes": [ - "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", - "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], "markers": "python_version >= '3.8'", - "version": "==24.2" + "version": "==25.0" }, "phonenumberslite": { "hashes": [ - "sha256:85e9d75b8de1a5eeb716dc99b3937743cbd5265182dc5c1132204aa491ae0724", - "sha256:cda36bc06b5bd7c042d913408f9ca37c5e6c872f851393f15ad1e9a243e5c753" + "sha256:bb2944a5cd6477597214bf09613b65cda1bf6e454e298fa7426fe8ba318471d9", + "sha256:c2840efd67bca0e515570b615ab8de551167bb5989f065d2e1f1131991618ecc" ], - "version": "==9.0.1" + "version": "==9.0.8" }, "psycopg2-binary": { "hashes": [ @@ -958,161 +936,172 @@ }, "pycryptodomex": { "hashes": [ - "sha256:140b27caa68a36d0501b05eb247bd33afa5f854c1ee04140e38af63c750d4e39", - "sha256:1a8b0c5ba061ace4bcd03496d42702c3927003db805b8ec619ea6506080b381d", - "sha256:1ab0d89d1761959b608952c7b347b0e76a32d1a5bb278afbaa10a7f3eaef9a0a", - "sha256:259664c4803a1fa260d5afb322972813c5fe30ea8b43e54b03b7e3a27b30856b", - "sha256:276be1ed006e8fd01bba00d9bd9b60a0151e478033e86ea1cb37447bbc057edc", - "sha256:2cac9ed5c343bb3d0075db6e797e6112514764d08d667c74cb89b931aac9dddd", - "sha256:41673e5cc39a8524557a0472077635d981172182c9fe39ce0b5f5c19381ffaff", - "sha256:5ac608a6dce9418d4f300fab7ba2f7d499a96b462f2b9b5c90d8d994cd36dcad", - "sha256:5bf3ce9211d2a9877b00b8e524593e2209e370a287b3d5e61a8c45f5198487e2", - "sha256:5e64164f816f5e43fd69f8ed98eb28f98157faf68208cd19c44ed9d8e72d33e8", - "sha256:5ebc09b7d8964654aaf8a4f5ac325f2b0cc038af9bea12efff0cd4a5bb19aa42", - "sha256:644834b1836bb8e1d304afaf794d5ae98a1d637bd6e140c9be7dd192b5374811", - "sha256:684cb57812cd243217c3d1e01a720c5844b30f0b7b64bb1a49679f7e1e8a54ac", - "sha256:7127d9de3c7ce20339e06bcd4f16f1a1a77f1471bcf04e3b704306dde101b719", - "sha256:72c506aba3318505dbeecf821ed7b9a9f86f422ed085e2d79c4fba0ae669920a", - "sha256:7a24f681365ec9757ccd69b85868bbd7216ba451d0f86f6ea0eed75eeb6975db", - "sha256:7cd39f7a110c1ab97ce9ee3459b8bc615920344dc00e56d1b709628965fba3f2", - "sha256:813e57da5ceb4b549bab96fa548781d9a63f49f1d68fdb148eeac846238056b7", - "sha256:a1da61bacc22f93a91cbe690e3eb2022a03ab4123690ab16c46abb693a9df63d", - "sha256:aef4590263b9f2f6283469e998574d0bd45c14fb262241c27055b82727426157", - "sha256:b3746dedf74787da43e4a2f85bd78f5ec14d2469eb299ddce22518b3891f16ea", - "sha256:bfe4fe3233ef3e58028a3ad8f28473653b78c6d56e088ea04fe7550c63d4d16b", - "sha256:c8cffb03f5dee1026e3f892f7cffd79926a538c67c34f8b07c90c0bd5c834e27", - "sha256:d7beeacb5394765aa8dabed135389a11ee322d3ee16160d178adc7f8ee3e1f65", - "sha256:e4eaaf6163ff13788c1f8f615ad60cdc69efac6d3bf7b310b21e8cfe5f46c801", - "sha256:eac39e237d65981554c2d4c6668192dc7051ad61ab5fc383ed0ba049e4007ca2", - "sha256:ee75067b35c93cc18b38af47b7c0664998d8815174cfc66dd00ea1e244eb27e6", - "sha256:f005de31efad6f9acefc417296c641f13b720be7dbfec90edeaca601c0fab048", - "sha256:ff46212fda7ee86ec2f4a64016c994e8ad80f11ef748131753adb67e9b722ebd" + "sha256:02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51", + "sha256:06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6", + "sha256:14c37aaece158d0ace436f76a7bb19093db3b4deade9797abfc39ec6cd6cc2fe", + "sha256:189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c", + "sha256:1c3a65ad441746b250d781910d26b7ed0a396733c6f2dbc3327bd7051ec8a541", + "sha256:1c6d919fc8429e5cb228ba8c0d4d03d202a560b421c14867a65f6042990adc8e", + "sha256:267a3038f87a8565bd834317dbf053a02055915acf353bf42ededb9edaf72010", + "sha256:27e13c80ac9a0a1d050ef0a7e0a18cc04c8850101ec891815b6c5a0375e8a245", + "sha256:43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587", + "sha256:47f6d318fe864d02d5e59a20a18834819596c4ed1d3c917801b22b92b3ffa648", + "sha256:4e79f1aaff5a3a374e92eb462fa9e598585452135012e2945f96874ca6eeb1ff", + "sha256:4f2596e643d4365e14d0879dc5aafe6355616c61c2176009270f3048f6d9a61f", + "sha256:52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9", + "sha256:55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd", + "sha256:58b851b9effd0d072d4ca2e4542bf2a4abcf13c82a29fd2c93ce27ee2a2e9462", + "sha256:6b8962204c47464d5c1c4038abeadd4514a133b28748bcd9fa5b6d62e3cec6fa", + "sha256:6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003", + "sha256:71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da", + "sha256:7b37e08e3871efe2187bc1fd9320cc81d87caf19816c648f24443483005ff886", + "sha256:7de1e40a41a5d7f1ac42b6569b10bcdded34339950945948529067d8426d2785", + "sha256:8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744", + "sha256:91979028227543010d7b2ba2471cf1d1e398b3f183cb105ac584df0c36dac28d", + "sha256:a33986a0066860f7fcf7c7bd2bc804fa90e434183645595ae7b33d01f3c91ed8", + "sha256:a9d446e844f08299236780f2efa9898c818fe7e02f17263866b8550c7d5fb328", + "sha256:add243d204e125f189819db65eed55e6b4713f70a7e9576c043178656529cec7", + "sha256:b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545", + "sha256:bc65bdd9fc8de7a35a74cab1c898cab391a4add33a8fe740bda00f5976ca4708", + "sha256:bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c", + "sha256:bffc92138d75664b6d543984db7893a628559b9e78658563b0395e2a5fb47ed9", + "sha256:c25e30a20e1b426e1f0fa00131c516f16e474204eee1139d1603e132acffc314", + "sha256:c7947ab8d589e3178da3d7cdeabe14f841b391e17046954f2fbcd941705762b5", + "sha256:c84b239a1f4ec62e9c789aafe0543f0594f0acd90c8d9e15bcece3efe55eca66", + "sha256:c885da45e70139464f082018ac527fdaad26f1657a99ee13eecdce0f0ca24ab4", + "sha256:d9825410197a97685d6a1fa2a86196430b01877d64458a20e95d4fd00d739a08", + "sha256:da4fa650cef02db88c2b98acc5434461e027dce0ae8c22dd5a69013eaf510006", + "sha256:df027262368334552db2c0ce39706b3fb32022d1dce34673d0f9422df004b96a", + "sha256:ebfff755c360d674306e5891c564a274a47953562b42fb74a5c25b8fc1fb1cb5", + "sha256:eca54f4bb349d45afc17e3011ed4264ef1cc9e266699874cdd1349c504e64798", + "sha256:f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c", + "sha256:fdfac7cda115bca3a5abb2f9e43bc2fb66c2b65ab074913643803ca7083a79ea", + "sha256:febec69c0291efd056c65691b6d9a339f8b4bc43c6635b8699471248fe897fea" ], "index": "pypi", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==3.22.0" + "version": "==3.23.0" }, "pydantic": { "hashes": [ - "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", - "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236" + "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", + "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b" ], - "markers": "python_version >= '3.8'", - "version": "==2.10.6" + "markers": "python_version >= '3.9'", + "version": "==2.11.7" }, "pydantic-core": { "hashes": [ - "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278", - "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", - "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", - "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f", - "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", - "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", - "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54", - "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630", - "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", - "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", - "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", - "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", - "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", - "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", - "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", - "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", - "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", - "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd", - "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", - "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", - "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", - "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", - "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", - "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", - "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", - "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", - "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", - "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", - "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", - "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", - "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", - "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf", - "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", - "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", - "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76", - "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362", - "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", - "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", - "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320", - "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118", - "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96", - "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", - "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046", - "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", - "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", - "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", - "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", - "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67", - "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", - "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", - "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35", - "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", - "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", - "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b", - "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", - "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", - "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", - "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145", - "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", - "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", - "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", - "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", - "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", - "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", - "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5", - "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", - "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", - "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", - "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", - "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da", - "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", - "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", - "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993", - "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656", - "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4", - "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", - "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb", - "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d", - "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", - "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e", - "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", - "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc", - "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a", - "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9", - "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506", - "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b", - "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1", - "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", - "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", - "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", - "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", - "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", - "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", - "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", - "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308", - "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2", - "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228", - "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b", - "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", - "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad" + "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", + "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", + "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", + "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", + "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4", + "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", + "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", + "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", + "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", + "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b", + "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", + "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", + "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", + "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", + "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", + "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", + "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", + "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27", + "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", + "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", + "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", + "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", + "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", + "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039", + "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca", + "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", + "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", + "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6", + "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782", + "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", + "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", + "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", + "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", + "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7", + "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", + "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", + "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", + "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", + "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", + "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", + "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", + "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", + "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", + "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", + "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954", + "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", + "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", + "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", + "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64", + "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", + "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9", + "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101", + "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", + "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", + "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3", + "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", + "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", + "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", + "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d", + "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", + "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e", + "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", + "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", + "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", + "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d", + "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", + "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", + "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", + "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", + "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", + "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", + "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a", + "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", + "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", + "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb", + "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", + "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", + "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d", + "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", + "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", + "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", + "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535", + "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", + "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", + "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", + "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", + "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", + "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", + "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", + "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9", + "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", + "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3", + "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", + "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", + "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", + "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", + "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", + "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", + "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d" ], - "markers": "python_version >= '3.8'", - "version": "==2.27.2" + "markers": "python_version >= '3.9'", + "version": "==2.33.2" }, "pydantic-settings": { "hashes": [ - "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", - "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585" + "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", + "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796" ], - "markers": "python_version >= '3.8'", - "version": "==2.8.1" + "markers": "python_version >= '3.9'", + "version": "==2.10.1" }, "pyjwkest": { "hashes": [ @@ -1131,11 +1120,11 @@ }, "python-dotenv": { "hashes": [ - "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", - "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" + "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", + "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab" ], - "markers": "python_version >= '3.8'", - "version": "==1.0.1" + "markers": "python_version >= '3.9'", + "version": "==1.1.1" }, "pyzipper": { "hashes": [ @@ -1146,30 +1135,131 @@ "markers": "python_version >= '3.4'", "version": "==0.3.6" }, + "rapidfuzz": { + "hashes": [ + "sha256:09e908064d3684c541d312bd4c7b05acb99a2c764f6231bd507d4b4b65226c23", + "sha256:0da54aa8547b3c2c188db3d1c7eb4d1bb6dd80baa8cdaeaec3d1da3346ec9caa", + "sha256:0e1d08cb884805a543f2de1f6744069495ef527e279e05370dd7c83416af83f8", + "sha256:0fd05336db4d0b8348d7eaaf6fa3c517b11a56abaa5e89470ce1714e73e4aca7", + "sha256:11b125d8edd67e767b2295eac6eb9afe0b1cdc82ea3d4b9257da4b8e06077798", + "sha256:11b47b40650e06147dee5e51a9c9ad73bb7b86968b6f7d30e503b9f8dd1292db", + "sha256:1343d745fbf4688e412d8f398c6e6d6f269db99a54456873f232ba2e7aeb4939", + "sha256:1a79a2f07786a2070669b4b8e45bd96a01c788e7a3c218f531f3947878e0f956", + "sha256:1ba007f4d35a45ee68656b2eb83b8715e11d0f90e5b9f02d615a8a321ff00c27", + "sha256:1dc82b6ed01acb536b94a43996a94471a218f4d89f3fdd9185ab496de4b2a981", + "sha256:1f219f1e3c3194d7a7de222f54450ce12bc907862ff9a8962d83061c1f923c86", + "sha256:200030dfc0a1d5d6ac18e993c5097c870c97c41574e67f227300a1fb74457b1d", + "sha256:202a87760f5145140d56153b193a797ae9338f7939eb16652dd7ff96f8faf64c", + "sha256:25343ccc589a4579fbde832e6a1e27258bfdd7f2eb0f28cb836d6694ab8591fc", + "sha256:2d18228a2390375cf45726ce1af9d36ff3dc1f11dce9775eae1f1b13ac6ec50f", + "sha256:2fd0975e015b05c79a97f38883a11236f5a24cca83aa992bd2558ceaa5652b26", + "sha256:30fd1451f87ccb6c2f9d18f6caa483116bbb57b5a55d04d3ddbd7b86f5b14998", + "sha256:3abe6a4e8eb4cfc4cda04dd650a2dc6d2934cbdeda5def7e6fd1c20f6e7d2a0b", + "sha256:3b6f913ee4618ddb6d6f3e387b76e8ec2fc5efee313a128809fbd44e65c2bbb2", + "sha256:3f32f15bacd1838c929b35c84b43618481e1b3d7a61b5ed2db0291b70ae88b53", + "sha256:435071fd07a085ecbf4d28702a66fd2e676a03369ee497cc38bcb69a46bc77e2", + "sha256:45dd4628dd9c21acc5c97627dad0bb791764feea81436fb6e0a06eef4c6dceaa", + "sha256:461fd13250a2adf8e90ca9a0e1e166515cbcaa5e9c3b1f37545cbbeff9e77f6b", + "sha256:4671ee300d1818d7bdfd8fa0608580d7778ba701817216f0c17fb29e6b972514", + "sha256:48719f7dcf62dfb181063b60ee2d0a39d327fa8ad81b05e3e510680c44e1c078", + "sha256:4a1a6a906ba62f2556372282b1ef37b26bca67e3d2ea957277cfcefc6275cca7", + "sha256:4d9d7f84c8e992a8dbe5a3fdbea73d733da39bf464e62c912ac3ceba9c0cff93", + "sha256:5158da7f2ec02a930be13bac53bb5903527c073c90ee37804090614cab83c29e", + "sha256:5280be8fd7e2bee5822e254fe0a5763aa0ad57054b85a32a3d9970e9b09bbcbf", + "sha256:5435fcac94c9ecf0504bf88a8a60c55482c32e18e108d6079a0089c47f3f8cf6", + "sha256:558bf526bcd777de32b7885790a95a9548ffdcce68f704a81207be4a286c1095", + "sha256:573ad267eb9b3f6e9b04febce5de55d8538a87c56c64bf8fd2599a48dc9d8b77", + "sha256:57c390336cb50d5d3bfb0cfe1467478a15733703af61f6dffb14b1cd312a6fae", + "sha256:5d4e13593d298c50c4f94ce453f757b4b398af3fa0fd2fde693c3e51195b7f69", + "sha256:5dc71ef23845bb6b62d194c39a97bb30ff171389c9812d83030c1199f319098c", + "sha256:624a108122039af89ddda1a2b7ab2a11abe60c1521956f142f5d11bcd42ef138", + "sha256:65cc97c2fc2c2fe23586599686f3b1ceeedeca8e598cfcc1b7e56dc8ca7e2aa7", + "sha256:694eb531889f71022b2be86f625a4209c4049e74be9ca836919b9e395d5e33b3", + "sha256:6af42f2ede8b596a6aaf6d49fdee3066ca578f4856b85ab5c1e2145de367a12d", + "sha256:6c0efa73afbc5b265aca0d8a467ae2a3f40d6854cbe1481cb442a62b7bf23c99", + "sha256:6e2065f68fb1d0bf65adc289c1bdc45ba7e464e406b319d67bb54441a1b9da9e", + "sha256:7ac21489de962a4e2fc1e8f0b0da4aa1adc6ab9512fd845563fecb4b4c52093a", + "sha256:7d7cec4242d30dd521ef91c0df872e14449d1dffc2a6990ede33943b0dae56c3", + "sha256:85c9a131a44a95f9cac2eb6e65531db014e09d89c4f18c7b1fa54979cb9ff1f3", + "sha256:8c99b76b93f7b495eee7dcb0d6a38fb3ce91e72e99d9f78faa5664a881cb2b7d", + "sha256:9093cdeb926deb32a4887ebe6910f57fbcdbc9fbfa52252c10b56ef2efb0289f", + "sha256:9256218ac8f1a957806ec2fb9a6ddfc6c32ea937c0429e88cf16362a20ed8602", + "sha256:9327a4577f65fc3fb712e79f78233815b8a1c94433d0c2c9f6bc5953018b3565", + "sha256:93a755266856599be4ab6346273f192acde3102d7aa0735e2f48b456397a041f", + "sha256:98b8107ff14f5af0243f27d236bcc6e1ef8e7e3b3c25df114e91e3a99572da73", + "sha256:98e0bfa602e1942d542de077baf15d658bd9d5dcfe9b762aff791724c1c38b70", + "sha256:9a7c6232be5f809cd39da30ee5d24e6cadd919831e6020ec6c2391f4c3bc9264", + "sha256:9f338e71c45b69a482de8b11bf4a029993230760120c8c6e7c9b71760b6825a1", + "sha256:9f5fe634c9482ec5d4a6692afb8c45d370ae86755e5f57aa6c50bfe4ca2bdd87", + "sha256:a50856f49a4016ef56edd10caabdaf3608993f9faf1e05c3c7f4beeac46bd12a", + "sha256:a6dd36d4916cf57ddb05286ed40b09d034ca5d4bca85c17be0cb6a21290597d9", + "sha256:a9ad1f37894e3ffb76bbab76256e8a8b789657183870be11aa64e306bb5228fd", + "sha256:aafc42a1dc5e1beeba52cd83baa41372228d6d8266f6d803c16dbabbcc156255", + "sha256:adb40ca8ddfcd4edd07b0713a860be32bdf632687f656963bcbce84cea04b8d8", + "sha256:ae4574cb66cf1e85d32bb7e9ec45af5409c5b3970b7ceb8dea90168024127566", + "sha256:b1b065f370d54551dcc785c6f9eeb5bd517ae14c983d2784c064b3aa525896df", + "sha256:b5104b62711565e0ff6deab2a8f5dbf1fbe333c5155abe26d2cfd6f1849b6c87", + "sha256:b7b3eda607a019169f7187328a8d1648fb9a90265087f6903d7ee3a8eee01805", + "sha256:b7f4c65facdb94f44be759bbd9b6dda1fa54d0d6169cdf1a209a5ab97d311a75", + "sha256:b836f486dba0aceb2551e838ff3f514a38ee72b015364f739e526d720fdb823a", + "sha256:bef86df6d59667d9655905b02770a0c776d2853971c0773767d5ef8077acd624", + "sha256:c2b3dd5d206a12deca16870acc0d6e5036abeb70e3cad6549c294eff15591527", + "sha256:c33f9c841630b2bb7e69a3fb5c84a854075bb812c47620978bddc591f764da3d", + "sha256:c523620d14ebd03a8d473c89e05fa1ae152821920c3ff78b839218ff69e19ca3", + "sha256:cc269e74cad6043cb8a46d0ce580031ab642b5930562c2bb79aa7fbf9c858d26", + "sha256:cc64da907114d7a18b5e589057e3acaf2fec723d31c49e13fedf043592a3f6a7", + "sha256:ccbd0e7ea1a216315f63ffdc7cd09c55f57851afc8fe59a74184cb7316c0598b", + "sha256:cdb33ee9f8a8e4742c6b268fa6bd739024f34651a06b26913381b1413ebe7590", + "sha256:cfcccc08f671646ccb1e413c773bb92e7bba789e3a1796fd49d23c12539fe2e4", + "sha256:d21f188f6fe4fbf422e647ae9d5a68671d00218e187f91859c963d0738ccd88c", + "sha256:d25fdbce6459ccbbbf23b4b044f56fbd1158b97ac50994eaae2a1c0baae78301", + "sha256:d2eaf3839e52cbcc0accbe9817a67b4b0fcf70aaeb229cfddc1c28061f9ce5d8", + "sha256:d395a5cad0c09c7f096433e5fd4224d83b53298d53499945a9b0e5a971a84f3a", + "sha256:d7a217310429b43be95b3b8ad7f8fc41aba341109dc91e978cd7c703f928c58f", + "sha256:d8cf5f7cd6e4d5eb272baf6a54e182b2c237548d048e2882258336533f3f02b7", + "sha256:df8e8c21e67afb9d7fbe18f42c6111fe155e801ab103c81109a61312927cc611", + "sha256:e05752418b24bbd411841b256344c26f57da1148c5509e34ea39c7eb5099ab72", + "sha256:e1bdd2e6d0c5f9706ef7595773a81ca2b40f3b33fd7f9840b726fb00c6c4eb2e", + "sha256:e297c09972698c95649e89121e3550cee761ca3640cd005e24aaa2619175464e", + "sha256:e62779c6371bd2b21dbd1fdce89eaec2d93fd98179d36f61130b489f62294a92", + "sha256:e8ddb58961401da7d6f55f185512c0d6bd24f529a637078d41dd8ffa5a49c107", + "sha256:e9d824de871daa6e443b39ff495a884931970d567eb0dfa213d234337343835f", + "sha256:ed6f416bda1c9133000009d84d9409823eb2358df0950231cc936e4bf784eb97", + "sha256:ef0f5f03f61b0e5a57b1df7beafd83df993fd5811a09871bad6038d08e526d0d", + "sha256:f4797f821dc5d7c2b6fc818b89f8a3f37bcc900dd9e4369e6ebf1e525efce5db", + "sha256:f70f646751b6aa9d05be1fb40372f006cc89d6aad54e9d79ae97bd1f5fce5203", + "sha256:fd742c03885db1fce798a1cd87a20f47f144ccf26d75d52feb6f2bae3d57af05", + "sha256:fe5790a36d33a5d0a6a1f802aa42ecae282bf29ac6f7506d8e12510847b82a45", + "sha256:fedd316c165beed6307bf754dee54d3faca2c47e1f3bcbd67595001dfa11e969" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==3.13.0" + }, "requests": { "hashes": [ - "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", - "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", + "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==2.32.3" + "version": "==2.32.4" }, "s3transfer": { "hashes": [ - "sha256:559f161658e1cf0a911f45940552c696735f5c74e64362e515f333ebed87d679", - "sha256:ac265fa68318763a03bf2dc4f39d5cbd6a9e178d81cc9483ad27da33637e320d" + "sha256:0148ef34d6dd964d0d8cf4311b2b21c474693e57c2e069ec708ce043d2b527be", + "sha256:f5e6db74eb7776a37208001113ea7aa97695368242b364d73e91c981ac522177" ], - "markers": "python_version >= '3.8'", - "version": "==0.11.4" + "markers": "python_version >= '3.9'", + "version": "==0.13.0" }, "setuptools": { "hashes": [ - "sha256:583b361c8da8de57403743e756609670de6fb2345920e36dc5c2d914c319c945", - "sha256:67122e78221da5cf550ddd04cf8742c8fe12094483749a792d56cd669d6cf58c" + "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", + "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c" ], "markers": "python_version >= '3.9'", - "version": "==77.0.3" + "version": "==80.9.0" }, "six": { "hashes": [ @@ -1197,37 +1287,45 @@ }, "tblib": { "hashes": [ - "sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129", - "sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6" + "sha256:06404c2c9f07f66fee2d7d6ad43accc46f9c3361714d9b8426e7f47e595cd652", + "sha256:670bb4582578134b3d81a84afa1b016128b429f3d48e6cbbaecc9d15675e984e" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==3.0.0" + "markers": "python_version >= '3.9'", + "version": "==3.1.0" }, "typing-extensions": { "hashes": [ - "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", - "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" + "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", + "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==4.12.2" + "markers": "python_version >= '3.9'", + "version": "==4.14.0" + }, + "typing-inspection": { + "hashes": [ + "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", + "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28" + ], + "markers": "python_version >= '3.9'", + "version": "==0.4.1" }, "tzdata": { "hashes": [ - "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", - "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639" + "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", + "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9" ], "markers": "python_version >= '2'", - "version": "==2025.1" + "version": "==2025.2" }, "urllib3": { "hashes": [ - "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", - "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" + "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", + "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" ], "markers": "python_version >= '3.9'", - "version": "==2.3.0" + "version": "==2.5.0" }, "whitenoise": { "hashes": [ @@ -1301,20 +1399,20 @@ }, "bandit": { "hashes": [ - "sha256:28f04dc0d258e1dd0f99dee8eefa13d1cb5e3fde1a5ab0c523971f97b289bcd8", - "sha256:f5847beb654d309422985c36644649924e0ea4425c76dec2e89110b87506193a" + "sha256:cb2e57524e99e33ced48833c6cc9c12ac78ae970bb6a450a83c4b506ecc1e2f9", + "sha256:db812e9c39b8868c0fed5278b77fffbbaba828b4891bc80e34b9c50373201cfd" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==1.8.3" + "version": "==1.8.5" }, "beautifulsoup4": { "hashes": [ - "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b", - "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16" + "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", + "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195" ], "markers": "python_full_version >= '3.7.0'", - "version": "==4.13.3" + "version": "==4.13.4" }, "black": { "hashes": [ @@ -1355,12 +1453,12 @@ }, "boto3": { "hashes": [ - "sha256:1545c943f36db41853cdfdb6ff09c4eda9220dd95bd2fae76fc73091603525d1", - "sha256:9b272268794172b0b8bb9fb1f3c470c3b6c0ffb92fbd4882465cc740e40fbdcd" + "sha256:2e3411bb43285caad1c8e1a3186d025ba65a6342e26bad493f6b8feb3d1a1680", + "sha256:9b0ff0b34c9cf7328546c532c20b081f09055ff485f4d57c19146c36877048c5" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.37.18" + "markers": "python_version >= '3.9'", + "version": "==1.38.43" }, "boto3-mocking": { "hashes": [ @@ -1373,36 +1471,36 @@ }, "boto3-stubs": { "hashes": [ - "sha256:30ecf8dfaa848469f55279e41248ff46b1ecf5e2ee7b3b292e3ec39332e2f90e", - "sha256:db866104d3b9bea63c4769255759ad5c61bc5e5591cea0b692df4c08082a841e" + "sha256:2dc863a0bae3a9f31de6cdaff083be0f5f8850d4a846a017ef6079b620294f12", + "sha256:6fe495830b63ffe3bbc8082e96890eee1e9a93ec5c3c1bd9b9b531ec4d90c1a7" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.37.18" + "version": "==1.38.43" }, "botocore": { "hashes": [ - "sha256:99e8eefd5df6347ead15df07ce55f4e62a51ea7b54de1127522a08597923b726", - "sha256:a8b97d217d82b3c4f6bcc906e264df7ebb51e2c6a62b3548a97cd173fb8759a1" + "sha256:2ee60ac0b08e80e9be2aa2841d42e438d5bc4f82549560a682837655097a3db7", + "sha256:c453c5c16c157c5427058bb3cc2c5ad35ee2e43336f0ccbfcc6092c5635505c6" ], - "markers": "python_version >= '3.8'", - "version": "==1.37.18" + "markers": "python_version >= '3.9'", + "version": "==1.38.43" }, "botocore-stubs": { "hashes": [ - "sha256:b9c3a1e8fb57fb70b49aa5380cabefab32ec028d8a1d8f5ac83dd836c5b429a8", - "sha256:c6cb18979a86db311a365448b67e4a492a530c3f4fb313432d41deaee6268b95" + "sha256:291d7bf39a316c00a8a55b7255489b02c0cea1a343482e7784e8d1e235bae995", + "sha256:2efb8bdf36504aff596c670d875d8f7dd15205277c15c4cea54afdba8200c266" ], "markers": "python_version >= '3.8'", - "version": "==1.37.17" + "version": "==1.38.30" }, "click": { "hashes": [ - "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", - "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" + "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", + "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" ], - "markers": "python_version >= '3.7'", - "version": "==8.1.8" + "markers": "python_version >= '3.10'", + "version": "==8.2.1" }, "debugpy": { "hashes": [ @@ -1448,53 +1546,53 @@ }, "django-debug-toolbar": { "hashes": [ - "sha256:8a3b9da4aeab8d384a366e20304bd939a451f0242523c5b7b402248ad474eed2", - "sha256:c0591e338ee9603bdfce5aebf8d18ca7341fdbb69595e2b0b34869be5857180e" + "sha256:15627f4c2836a9099d795e271e38e8cf5204ccd79d5dbcd748f8a6c284dcd195", + "sha256:9e7f0145e1a1b7d78fcc3b53798686170a5b472d9cf085d88121ff823e900821" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==5.1.0" + "version": "==5.2.0" }, "django-model2puml": { "hashes": [ - "sha256:c823366d5ddc7cc52d855b62ce3b2b0acaa54dcaa0f372b9c5f2679d9a341f54" + "sha256:c7a986bb0e7ab22fd4dd18d6ff161c84e8c0a163dc783f17a9ca803ea5a747db" ], "index": "pypi", - "version": "==0.6.0" + "version": "==0.6.1" }, "django-stubs": { "hashes": [ - "sha256:716758ced158b439213062e52de6df3cff7c586f9f9ad7ab59210efbea5dfe78", - "sha256:8c230bc5bebee6da282ba8a27ad1503c84a0c4cd2f46e63d149e76d2a63e639a" + "sha256:c0e170d70329c27e737a5b80c5518fb6161d0c4792321d11a4a93dcda120f4ef", + "sha256:e58260958e58f7b6a8da6bba56d6b31d16c0414079a4aa6baa01c668bd08d39d" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==5.1.3" + "markers": "python_version >= '3.10'", + "version": "==5.2.1" }, "django-stubs-ext": { "hashes": [ - "sha256:3e60f82337f0d40a362f349bf15539144b96e4ceb4dbd0239be1cd71f6a74ad0", - "sha256:64561fbc53e963cc1eed2c8eb27e18b8e48dcb90771205180fe29fc8a59e55fd" + "sha256:98fb0646f1a1ef07708eec5f6f7d27523f12c0c8714abae8db981571ff957588", + "sha256:fc0582cb3289306c43ce4a0a15af86922ce1dbec3c19eab80980ee70c04e0392" ], - "markers": "python_version >= '3.8'", - "version": "==5.1.3" + "markers": "python_version >= '3.10'", + "version": "==5.2.1" }, "django-webtest": { "hashes": [ - "sha256:5012c30665e7a6e585a1544eda75045d07d5b3f5ccccd4d0fe144c4555884095", - "sha256:de5c988c20eef7abbb3d0508494d9e576af08087d0fb6109b1d54f15ef4d78fa" + "sha256:1903d35f429cbff54bd89d41ddc42376529d56e3c35bfe7cb83827b17fbf962d", + "sha256:97b2db843588128332326e4fc0a37bd825cf4d68f1ab0188c8cc00a9710e8279" ], "index": "pypi", - "version": "==1.9.12" + "version": "==1.9.13" }, "flake8": { "hashes": [ - "sha256:1cbc62e65536f65e6d754dfe6f1bada7f5cf392d6f5db3c2b85892466c3e7c1a", - "sha256:c586ffd0b41540951ae41af572e6790dbd49fc12b3aa2541685d253d9bd504bd" + "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", + "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872" ], "index": "pypi", - "markers": "python_full_version >= '3.8.1'", - "version": "==7.1.2" + "markers": "python_version >= '3.9'", + "version": "==7.3.0" }, "jmespath": { "hashes": [ @@ -1504,6 +1602,14 @@ "markers": "python_version >= '3.7'", "version": "==1.0.1" }, + "legacy-cgi": { + "hashes": [ + "sha256:4c119d6cb8e9d8b6ad7cc0ddad880552c62df4029622835d06dfd18f438a8154", + "sha256:6df2ea5ae14c71ef6f097f8b6372b44f6685283dc018535a75c924564183cdab" + ], + "markers": "python_version >= '3.8'", + "version": "==2.6.3" + }, "markdown-it-py": { "hashes": [ "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", @@ -1530,50 +1636,50 @@ }, "mypy": { "hashes": [ - "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", - "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", - "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", - "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2", - "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", - "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b", - "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", - "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", - "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", - "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", - "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", - "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828", - "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba", - "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", - "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", - "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b", - "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", - "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", - "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13", - "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", - "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", - "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", - "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", - "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b", - "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", - "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559", - "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3", - "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", - "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", - "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980", - "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078", - "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5" + "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", + "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", + "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c", + "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", + "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", + "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6", + "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383", + "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", + "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", + "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", + "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", + "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574", + "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", + "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", + "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", + "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40", + "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da", + "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", + "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", + "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", + "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069", + "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72", + "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", + "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", + "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a", + "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", + "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", + "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", + "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", + "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea", + "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b", + "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==1.15.0" + "version": "==1.16.1" }, "mypy-extensions": { "hashes": [ - "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", - "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", + "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" ], - "markers": "python_version >= '3.5'", - "version": "==1.0.0" + "markers": "python_version >= '3.8'", + "version": "==1.1.0" }, "nplusone": { "hashes": [ @@ -1585,11 +1691,11 @@ }, "packaging": { "hashes": [ - "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", - "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], "markers": "python_version >= '3.8'", - "version": "==24.2" + "version": "==25.0" }, "pathspec": { "hashes": [ @@ -1609,35 +1715,35 @@ }, "platformdirs": { "hashes": [ - "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", - "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351" + "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", + "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" ], "markers": "python_version >= '3.9'", - "version": "==4.3.7" + "version": "==4.3.8" }, "pycodestyle": { "hashes": [ - "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3", - "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521" + "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", + "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d" ], - "markers": "python_version >= '3.8'", - "version": "==2.12.1" + "markers": "python_version >= '3.9'", + "version": "==2.14.0" }, "pyflakes": { "hashes": [ - "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", - "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a" + "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", + "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f" ], - "markers": "python_version >= '3.8'", - "version": "==3.2.0" + "markers": "python_version >= '3.9'", + "version": "==3.4.0" }, "pygments": { "hashes": [ - "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", - "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c" + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" ], "markers": "python_version >= '3.8'", - "version": "==2.19.1" + "version": "==2.19.2" }, "python-dateutil": { "hashes": [ @@ -1708,27 +1814,27 @@ }, "rich": { "hashes": [ - "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", - "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90" + "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", + "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725" ], "markers": "python_full_version >= '3.8.0'", - "version": "==13.9.4" + "version": "==14.0.0" }, "s3transfer": { "hashes": [ - "sha256:559f161658e1cf0a911f45940552c696735f5c74e64362e515f333ebed87d679", - "sha256:ac265fa68318763a03bf2dc4f39d5cbd6a9e178d81cc9483ad27da33637e320d" + "sha256:0148ef34d6dd964d0d8cf4311b2b21c474693e57c2e069ec708ce043d2b527be", + "sha256:f5e6db74eb7776a37208001113ea7aa97695368242b364d73e91c981ac522177" ], - "markers": "python_version >= '3.8'", - "version": "==0.11.4" + "markers": "python_version >= '3.9'", + "version": "==0.13.0" }, "setuptools": { "hashes": [ - "sha256:583b361c8da8de57403743e756609670de6fb2345920e36dc5c2d914c319c945", - "sha256:67122e78221da5cf550ddd04cf8742c8fe12094483749a792d56cd669d6cf58c" + "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", + "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c" ], "markers": "python_version >= '3.9'", - "version": "==77.0.3" + "version": "==80.9.0" }, "six": { "hashes": [ @@ -1740,11 +1846,11 @@ }, "soupsieve": { "hashes": [ - "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", - "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9" + "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", + "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a" ], "markers": "python_version >= '3.8'", - "version": "==2.6" + "version": "==2.7" }, "sqlparse": { "hashes": [ @@ -1803,62 +1909,62 @@ }, "types-awscrt": { "hashes": [ - "sha256:345ab84a4f75b26bfb816b249657855824a4f2d1ce5b58268c549f81fce6eccc", - "sha256:5826baf69ad5d29c76be49fc7df00222281fa31b14f99e9fb4492d71ec98fea5" + "sha256:49a045f25bbd5ad2865f314512afced933aed35ddbafc252e2268efa8a787e4e", + "sha256:acd04f57119eb15626ab0ba9157fc24672421de56e7bd7b9f61681fedee44e91" ], "markers": "python_version >= '3.8'", - "version": "==0.24.2" + "version": "==0.27.2" }, "types-cachetools": { "hashes": [ - "sha256:b888ab5c1a48116f7799cd5004b18474cd82b5463acb5ffb2db2fc9c7b053bc0", - "sha256:efb2ed8bf27a4b9d3ed70d33849f536362603a90b8090a328acf0cd42fda82e2" + "sha256:1de8f0fe4bdcb187a48d2026c1e3672830f67943ad2bf3486abe031b632f1252", + "sha256:baf06f234cac3aeb44c07893447ba03ecdb6c0742ba2607e28a35d38e6821b02" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==5.5.0.20240820" + "markers": "python_version >= '3.9'", + "version": "==6.0.0.20250525" }, "types-pyyaml": { "hashes": [ - "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c", - "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6" + "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530", + "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba" ], - "markers": "python_version >= '3.8'", - "version": "==6.0.12.20241230" + "markers": "python_version >= '3.9'", + "version": "==6.0.12.20250516" }, "types-requests": { "hashes": [ - "sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1", - "sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b" + "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826", + "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==2.32.0.20250306" + "version": "==2.32.4.20250611" }, "types-s3transfer": { "hashes": [ - "sha256:05fde593c84270f19fd053f0b1e08f5a057d7c5f036b9884e68fb8cd3041ac30", - "sha256:2a76d92c07d4a3cb469e5343b2e7560e0b8078b2e03696a65407b8c44c861b61" + "sha256:203dadcb9865c2f68fb44bc0440e1dc05b79197ba4a641c0976c26c9af75ef52", + "sha256:79c8375cbf48a64bff7654c02df1ec4b20d74f8c5672fc13e382f593ca5565b3" ], "markers": "python_version >= '3.8'", - "version": "==0.11.4" + "version": "==0.13.0" }, "typing-extensions": { "hashes": [ - "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", - "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" + "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", + "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==4.12.2" + "markers": "python_version >= '3.9'", + "version": "==4.14.0" }, "urllib3": { "hashes": [ - "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", - "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" + "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", + "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" ], "markers": "python_version >= '3.9'", - "version": "==2.3.0" + "version": "==2.5.0" }, "waitress": { "hashes": [ @@ -1878,11 +1984,11 @@ }, "webtest": { "hashes": [ - "sha256:5b3d8c69ac9057f17750ed5b45320a411423c2b4196bec6450961be98b03d8c1", - "sha256:94778d19a37e5abd7388dad4d93874410ecced53a1739a8e5ff2dbcba1cfc0c4" + "sha256:0c4a5a3dcf745a78c7905b803d6a520a2cf241c1f00ccdf4351f52916d555543", + "sha256:4256fd5242448f56c575bcb9afe275e305a6f0723c4b01438dbdd4dd5344944b" ], "markers": "python_version >= '3.9'", - "version": "==3.0.4" + "version": "==3.0.6" } } } diff --git a/src/djangooidc/views.py b/src/djangooidc/views.py index 815df4ecf..2adf0f2ba 100644 --- a/src/djangooidc/views.py +++ b/src/djangooidc/views.py @@ -1,4 +1,5 @@ # coding: utf-8 +# flake8: noqa: F824 import logging diff --git a/src/docker-compose.yml b/src/docker-compose.yml index 9d945c4b6..6b8b8107b 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -125,6 +125,7 @@ services: owasp: image: ghcr.io/zaproxy/zaproxy:stable + user: "root" command: zap-baseline.py -t http://app:8080 -c zap.conf -I -r zap_report.html volumes: - .:/zap/wrk/ diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 1e16871da..db7d15862 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -162,6 +162,7 @@ INSTALLED_APPS = [ "import_export", # Waffle feature flags "waffle", + "csp", ] # Middleware are routines for processing web requests. @@ -178,6 +179,8 @@ MIDDLEWARE = [ "whitenoise.middleware.WhiteNoiseMiddleware", # provide security enhancements to the request/response cycle "django.middleware.security.SecurityMiddleware", + # django-csp: enable use of Content-Security-Policy header + "csp.middleware.CSPMiddleware", # store and retrieve arbitrary data on a per-site-visitor basis "django.contrib.sessions.middleware.SessionMiddleware", # add a few conveniences for perfectionists, see documentation @@ -193,8 +196,6 @@ MIDDLEWARE = [ "django.contrib.messages.middleware.MessageMiddleware", # provide clickjacking protection via the X-Frame-Options header "django.middleware.clickjacking.XFrameOptionsMiddleware", - # django-csp: enable use of Content-Security-Policy header - "csp.middleware.CSPMiddleware", # django-auditlog: obtain the request User for use in logging "auditlog.middleware.AuditlogMiddleware", # Used for waffle feature flags @@ -360,33 +361,35 @@ WAFFLE_FLAG_MODEL = "registrar.WaffleFlag" # Content-Security-Policy configuration # this can be restrictive because we have few external scripts -allowed_sources = ("'self'",) -CSP_DEFAULT_SRC = allowed_sources # Most things fall back to default-src, but the following do not and should be # explicitly set -CSP_FRAME_ANCESTORS = allowed_sources -CSP_FORM_ACTION = allowed_sources - # Google analytics requires that we relax our otherwise # strict CSP by allowing scripts to run from their domain # and inline with a nonce, as well as allowing connections back to their domain. # Note: If needed, we can embed chart.js instead of using the CDN -CSP_DEFAULT_SRC = ("'self'",) -CSP_STYLE_SRC = [ - "'self'", - "https://www.ssa.gov/accessibility/andi/andi.css", -] -CSP_SCRIPT_SRC_ELEM = [ - "'self'", - "https://www.googletagmanager.com/", - "https://cdn.jsdelivr.net/npm/chart.js", - "https://www.ssa.gov", - "https://ajax.googleapis.com", -] -CSP_CONNECT_SRC = ["'self'", "https://www.google-analytics.com/", "https://www.ssa.gov/accessibility/andi/andi.js"] +# Content-Security-Policy configuration for django-csp 4.0+ New format required +CONTENT_SECURITY_POLICY = { + "DIRECTIVES": { + "connect-src": [ + "'self'", + "https://www.google-analytics.com/", + "https://www.ssa.gov/accessibility/andi/andi.js", + ], + "default-src": ("'self'",), + "form-action": ("'self'",), + "frame-ancestors": ("'self'",), + "img-src": ["'self'", "https://www.ssa.gov/accessibility/andi/icons/"], + "script-src-elem": [ + "'self'", + "https://www.googletagmanager.com/", + "https://cdn.jsdelivr.net/npm/chart.js", + "https://www.ssa.gov", + "https://ajax.googleapis.com", + ], + "style-src": ["'self'", "https://www.ssa.gov/accessibility/andi/andi.css"], + } +} CSP_INCLUDE_NONCE_IN = ["script-src-elem", "style-src"] -CSP_IMG_SRC = ["'self'", "https://www.ssa.gov/accessibility/andi/icons/"] - # Cross-Origin Resource Sharing (CORS) configuration # Sets clients that allow access control to manage.get.gov # TODO: remove :8080 to see if we can have all localhost access diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index d4315e005..a51b7433c 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -505,7 +505,7 @@ class DomainOrgNameAddressForm(forms.ModelForm): state_territory = forms.ChoiceField( label="State, territory, or military post", required=True, - choices=DomainInformation.StateTerritoryChoices.choices, + choices=DomainInformation.StateTerritoryChoices.choices, # type: ignore[misc] error_messages={ "required": ("Select the state, territory, or military post where your organization is located.") }, @@ -606,7 +606,7 @@ class DomainOrgNameAddressForm(forms.ModelForm): except field.queryset.model.DoesNotExist: pass # Handle the case where the object does not exist - elif hasattr(new_value, "id"): + elif hasattr(new_value, "id") and new_value is not None: # If new_value is a model instance, compare by ID. new_value = new_value.id diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py index 43a391ae0..8c11d68fa 100644 --- a/src/registrar/forms/portfolio.py +++ b/src/registrar/forms/portfolio.py @@ -46,7 +46,7 @@ class PortfolioOrgAddressForm(forms.ModelForm): state_territory = forms.ChoiceField( label="State, territory, or military post", required=True, - choices=DomainInformation.StateTerritoryChoices.choices, + choices=DomainInformation.StateTerritoryChoices.choices, # type: ignore[misc] error_messages={ "required": ("Select the state, territory, or military post where your organization is located.") }, diff --git a/src/registrar/management/commands/create_federal_portfolio.py b/src/registrar/management/commands/create_federal_portfolio.py index cfc13c17f..b441b40e4 100644 --- a/src/registrar/management/commands/create_federal_portfolio.py +++ b/src/registrar/management/commands/create_federal_portfolio.py @@ -1,4 +1,23 @@ -"""Loads files from /tmp into our sandboxes""" +""" +This command creates and organizes federal agency portfolios by: + +1. Creates a Portfolio record for the specified agencies +2. Uses fuzzy string matching to find domain requests and domain information records + that belong to the agency (handles name variations like "Department of State" vs "State Dept" vs "DOS") +3. Automatically creates Suborganization records from the different sub-units/departments found within + the discovered domains/requests (e.g., "IT Department", "Communications Office") +4. Associates / Links domains and requests to their proper portfolio and suborganization hierarchy + +Usage Examples: + # Create portfolio for specific agency + ./manage.py create_federal_portfolio --agency_name "Department of State" --parse_requests --parse_domains + + # Create portfolios for entire branch + ./manage.py create_federal_portfolio --branch "executive" --parse_requests --parse_domains + + # Dry run to see what would change + ./manage.py create_federal_portfolio --agency_name "Department of Defense" --parse_requests --dry_run +""" import argparse import logging @@ -14,7 +33,7 @@ from registrar.models.utility.generic_helper import count_capitals, normalize_st from django.db.models import F, Q from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices - +from registrar.management.commands.utility.fuzzy_string_matcher import create_federal_agency_matcher logger = logging.getLogger(__name__) @@ -72,6 +91,34 @@ class Command(BaseCommand): self.domain_request_changes = self.ChangeTracker(model_class=DomainRequest) self.user_portfolio_perm_changes = self.ChangeTracker(model_class=UserPortfolioPermission) self.portfolio_invitation_changes = self.ChangeTracker(model_class=PortfolioInvitation) + self.fuzzy_matcher = None + self.fuzzy_threshold = 85 + self.dry_run = False + + def _create_fuzzy_organization_filter(self, federal_agency, all_org_names=None): + """ + Create a Q filter that includes both direct federal agency matches + and fuzzy organization name matches. + """ + # Direct federal agency relationship (existing logic) + base_filter = Q(federal_agency=federal_agency) + + # Fuzzy organization name matching + if all_org_names and self.fuzzy_matcher: + # The fuzzy matcher returns a MatchResult object, not a set + match_result = self.fuzzy_matcher.find_matches(federal_agency.agency, all_org_names) + + # Extract the matched_strings from the MatchResult + matched_org_names = match_result.matched_strings + + # Create Q objects for organization name matching + org_name_filters = Q() + for name in matched_org_names: + org_name_filters |= Q(organization_name__iexact=name) + + return base_filter | org_name_filters + + return base_filter def add_arguments(self, parser): """Add command line arguments to create federal portfolios. @@ -88,6 +135,8 @@ class Command(BaseCommand): Optional: --skip_existing_portfolios: Does not perform substeps on a portfolio if it already exists. + --dry_run: Show what would be changed without making any database modifications + --fuzzy_threshold: Similarity threshold for fuzzy matching (default: 85) --debug: Increases log verbosity """ group = parser.add_mutually_exclusive_group(required=True) @@ -118,7 +167,18 @@ class Command(BaseCommand): parser.add_argument( "--skip_existing_portfolios", action=argparse.BooleanOptionalAction, - help="Only parses newly created portfolios, skippubg existing ones.", + help="Only parses newly created portfolios, skipping existing ones.", + ) + parser.add_argument( + "--dry_run", + action=argparse.BooleanOptionalAction, + help="Show what would be changed without making any database modifications.", + ) + parser.add_argument( + "--fuzzy_threshold", + type=int, + default=85, + help="Similarity threshold for fuzzy matching (0-100, default: 85).", ) parser.add_argument( "--debug", @@ -133,7 +193,10 @@ class Command(BaseCommand): parse_domains = options.get("parse_domains") parse_managers = options.get("parse_managers") skip_existing_portfolios = options.get("skip_existing_portfolios") + dry_run = options.get("dry_run") debug = options.get("debug") + fuzzy_threshold = options.get("fuzzy_threshold", 85) + self.dry_run = dry_run # Parse script params if not (parse_requests or parse_domains or parse_managers): @@ -141,6 +204,15 @@ class Command(BaseCommand): "You must specify at least one of --parse_requests, --parse_domains, or --parse_managers." ) + # Show dry run + if dry_run: + logger.info(f"{TerminalColors.BOLD}{TerminalColors.OKBLUE}") + logger.info("=" * 60) + logger.info(" DRY RUN MODE") + logger.info(" NO DATABASE CHANGES WILL BE MADE") + logger.info("=" * 60) + logger.info(f"{TerminalColors.ENDC}") + # Get agencies federal_agency_filter = {"agency__iexact": agency_name} if agency_name else {"federal_type": branch} agencies = FederalAgency.objects.filter(agency__isnull=False, **federal_agency_filter).distinct() @@ -154,6 +226,8 @@ class Command(BaseCommand): else: raise CommandError(f"Cannot find '{branch}' federal agencies in our database.") + self.fuzzy_matcher = create_federal_agency_matcher(threshold=fuzzy_threshold) + # Store all portfolios and agencies in a dict to avoid extra db calls existing_portfolios = Portfolio.objects.filter( organization_name__in=agencies.values_list("agency", flat=True), organization_name__isnull=False @@ -181,19 +255,25 @@ class Command(BaseCommand): senior_official=federal_agency.so_federal_agency.first(), ) self.portfolio_changes.create.append(portfolio) - logger.info(f"{TerminalColors.OKGREEN}Created portfolio '{portfolio}'.{TerminalColors.ENDC}") + self._log_action("CREATE", f"portfolio '{portfolio}'") elif skip_existing_portfolios: message = f"Portfolio '{portfolio}' already exists. Skipped." logger.info(f"{TerminalColors.YELLOW}{message}{TerminalColors.ENDC}") self.portfolio_changes.skip.append(portfolio) # Create portfolios - self.portfolio_changes.bulk_create() + if not self.dry_run: + self.portfolio_changes.bulk_create() - # After create, get the list of all portfolios to use - portfolios_to_use = set(self.portfolio_changes.create) - if not skip_existing_portfolios: - portfolios_to_use.update(set(existing_portfolios)) + if self.dry_run: + portfolios_to_use = list(self.portfolio_changes.create) + if not skip_existing_portfolios: + portfolios_to_use.extend(list(existing_portfolios)) + else: + # After create, get the list of all portfolios to use + portfolios_to_use = set(self.portfolio_changes.create) + if not skip_existing_portfolios: + portfolios_to_use.update(set(existing_portfolios)) portfolios_to_use_dict = {normalize_string(p.organization_name): p for p in portfolios_to_use} @@ -201,14 +281,13 @@ class Command(BaseCommand): created_suborgs = self.create_suborganizations(portfolios_to_use_dict, agencies_dict) if created_suborgs: self.suborganization_changes.create.extend(created_suborgs.values()) - self.suborganization_changes.bulk_create() + if not self.dry_run: + self.suborganization_changes.bulk_create() # == Handle domains and requests == # for portfolio_org_name, portfolio in portfolios_to_use_dict.items(): federal_agency = agencies_dict.get(portfolio_org_name) - suborgs = {} - for suborg in portfolio.portfolio_suborganizations.all(): - suborgs[suborg.name] = suborg + suborgs = self._get_suborgs_for_portfolio(portfolio, created_suborgs) if parse_domains: updated_domains = self.update_domains(portfolio, federal_agency, suborgs, debug) @@ -219,30 +298,31 @@ class Command(BaseCommand): self.domain_request_changes.update.extend(updated_domain_requests) # Update DomainInformation - try: - self.domain_info_changes.bulk_update(["portfolio", "sub_organization"]) - except Exception as err: - logger.error(f"{TerminalColors.FAIL}Could not bulk update domain infos.{TerminalColors.ENDC}") - logger.error(err, exc_info=True) + if not self.dry_run: + try: + self.domain_info_changes.bulk_update(["portfolio", "sub_organization"]) + except Exception as err: + logger.error(f"{TerminalColors.FAIL}Could not bulk update domain infos.{TerminalColors.ENDC}") + logger.error(err, exc_info=True) - # Update DomainRequest - try: - self.domain_request_changes.bulk_update( - [ - "portfolio", - "sub_organization", - "requested_suborganization", - "suborganization_city", - "suborganization_state_territory", - "federal_agency", - ] - ) - except Exception as err: - logger.error(f"{TerminalColors.FAIL}Could not bulk update domain requests.{TerminalColors.ENDC}") - logger.error(err, exc_info=True) + # Update DomainRequest + try: + self.domain_request_changes.bulk_update( + [ + "portfolio", + "sub_organization", + "requested_suborganization", + "suborganization_city", + "suborganization_state_territory", + "federal_agency", + ] + ) + except Exception as err: + logger.error(f"{TerminalColors.FAIL}Could not bulk update domain requests.{TerminalColors.ENDC}") + logger.error(err, exc_info=True) # == Handle managers (no bulk_create) == # - if parse_managers: + if parse_managers and not self.dry_run: domain_infos = DomainInformation.objects.filter(portfolio__in=portfolios_to_use) domains = Domain.objects.filter(domain_info__in=domain_infos) @@ -256,22 +336,29 @@ class Command(BaseCommand): self.print_final_run_summary(parse_domains, parse_requests, parse_managers, debug) def print_final_run_summary(self, parse_domains, parse_requests, parse_managers, debug): + action_prefix = "WOULD BE " if self.dry_run else "" + self.portfolio_changes.print_script_run_summary( - no_changes_message="||============= No portfolios changed. =============||", - log_header="============= PORTFOLIOS =============", - skipped_header="----- SOME PORTFOLIOS WERENT CREATED (BUT OTHER RECORDS ARE STILL PROCESSED) -----", + no_changes_message=(f"||============= No portfolios {action_prefix.lower()}changed. =============||"), + log_header=f"============= PORTFOLIOS {action_prefix}=============", + skipped_header=( + f"----- SOME PORTFOLIOS {action_prefix}WERENT CREATED " f"(BUT OTHER RECORDS ARE STILL PROCESSED) -----" + ), detailed_prompt_title=( - "PORTFOLIOS: Do you wish to see the full list of failed, skipped and updated records?" + f"PORTFOLIOS: Do you wish to see the full list of " + f"{action_prefix.lower()}failed, skipped and updated records?" ), display_as_str=True, debug=debug, ) + self.suborganization_changes.print_script_run_summary( - no_changes_message="||============= No suborganizations changed. =============||", - log_header="============= SUBORGANIZATIONS =============", - skipped_header="----- SUBORGANIZATIONS SKIPPED (SAME NAME AS PORTFOLIO NAME) -----", + no_changes_message=(f"||============= No suborganizations {action_prefix.lower()}changed. =============||"), + log_header=f"============= SUBORGANIZATIONS {action_prefix}=============", + skipped_header=(f"----- SUBORGANIZATIONS {action_prefix}SKIPPED (SAME NAME AS PORTFOLIO NAME) -----"), detailed_prompt_title=( - "SUBORGANIZATIONS: Do you wish to see the full list of failed, skipped and updated records?" + f"SUBORGANIZATIONS: Do you wish to see the full list of " + f"{action_prefix.lower()}failed, skipped and updated records?" ), display_as_str=True, debug=debug, @@ -279,10 +366,11 @@ class Command(BaseCommand): if parse_domains: self.domain_info_changes.print_script_run_summary( - no_changes_message="||============= No domains changed. =============||", - log_header="============= DOMAINS =============", + no_changes_message=(f"||============= No domains {action_prefix.lower()}changed. =============||"), + log_header=f"============= DOMAINS {action_prefix}=============", detailed_prompt_title=( - "DOMAINS: Do you wish to see the full list of failed, skipped and updated records?" + f"DOMAINS: Do you wish to see the full list of " + f"{action_prefix.lower()}failed, skipped and updated records?" ), display_as_str=True, debug=debug, @@ -290,10 +378,13 @@ class Command(BaseCommand): if parse_requests: self.domain_request_changes.print_script_run_summary( - no_changes_message="||============= No domain requests changed. =============||", - log_header="============= DOMAIN REQUESTS =============", + no_changes_message=( + f"||============= No domain requests {action_prefix.lower()}changed. =============||" + ), + log_header=f"============= DOMAIN REQUESTS {action_prefix}=============", detailed_prompt_title=( - "DOMAIN REQUESTS: Do you wish to see the full list of failed, skipped and updated records?" + f"DOMAIN REQUESTS: Do you wish to see the full list of " + f"{action_prefix.lower()}failed, skipped and updated records?" ), display_as_str=True, debug=debug, @@ -301,102 +392,240 @@ class Command(BaseCommand): if parse_managers: self.user_portfolio_perm_changes.print_script_run_summary( - no_changes_message="||============= No managers changed. =============||", - log_header="============= MANAGERS =============", - skipped_header="----- MANAGERS SKIPPED (ALREADY EXISTED) -----", + no_changes_message=(f"||============= No managers {action_prefix.lower()}changed. =============||"), + log_header=f"============= MANAGERS {action_prefix}=============", + skipped_header=f"----- MANAGERS {action_prefix}SKIPPED (ALREADY EXISTED) -----", detailed_prompt_title=( - "MANAGERS: Do you wish to see the full list of failed, skipped and updated records?" + f"MANAGERS: Do you wish to see the full list of " + f"{action_prefix.lower()}failed, skipped and updated records?" ), display_as_str=True, debug=debug, ) + self.portfolio_invitation_changes.print_script_run_summary( - no_changes_message="||============= No manager invitations changed. =============||", - log_header="============= MANAGER INVITATIONS =============", - skipped_header="----- INVITATIONS SKIPPED (ALREADY EXISTED) -----", + no_changes_message=( + f"||============= No manager invitations {action_prefix.lower()}changed. =============||" + ), + log_header=f"============= MANAGER INVITATIONS {action_prefix}=============", + skipped_header=f"----- INVITATIONS {action_prefix}SKIPPED (ALREADY EXISTED) -----", detailed_prompt_title=( - "MANAGER INVITATIONS: Do you wish to see the full list of failed, skipped and updated records?" + f"MANAGER INVITATIONS: Do you wish to see the full list of " + f"{action_prefix.lower()}failed, skipped and updated records?" ), display_as_str=True, debug=debug, ) + # Add dry run summary at the end + if self.dry_run: + self._print_dry_run_summary() + + def _print_dry_run_summary(self): + """Print a summary of what would be changed in dry run mode.""" + logger.info(f"\n{TerminalColors.BOLD}{TerminalColors.OKBLUE}") + logger.info("=" * 60) + logger.info(" DRY RUN SUMMARY") + logger.info("=" * 60) + logger.info(f"{TerminalColors.ENDC}") + + total_changes = ( + len(self.portfolio_changes.create) + + len(self.suborganization_changes.create) + + len(self.domain_info_changes.update) + + len(self.domain_request_changes.update) + + len(self.user_portfolio_perm_changes.create) + + len(self.portfolio_invitation_changes.create) + ) + + logger.info(f"Total records that would be modified: {total_changes}") + logger.info(f" • Portfolios created: {len(self.portfolio_changes.create)}") + logger.info(f" • Suborganizations created: {len(self.suborganization_changes.create)}") + logger.info(f" • Domain infos updated: {len(self.domain_info_changes.update)}") + logger.info(f" • Domain requests updated: {len(self.domain_request_changes.update)}") + logger.info(f" • User permissions created: {len(self.user_portfolio_perm_changes.create)}") + logger.info(f" • Portfolio invitations created: {len(self.portfolio_invitation_changes.create)}") + + logger.info( + f"\n{TerminalColors.BOLD}To apply these changes, run the command without --dry_run{TerminalColors.ENDC}" + ) + def create_suborganizations(self, portfolio_dict, agency_dict): """Create Suborganizations tied to the given portfolio based on DomainInformation objects""" created_suborgs = {} - portfolios = portfolio_dict.values() + # Get filtered domains and requests + domains_dict, requests_dict = self._get_filtered_domains_and_requests(agency_dict) + + # Process each portfolio + for portfolio_name, portfolio in portfolio_dict.items(): + existing_suborgs = self._get_existing_suborgs_for_portfolio(portfolio) + portfolio_created_suborgs = self._get_portfolio_created_suborgs(created_suborgs, portfolio) + + # Create suborganizations for this portfolio + self._create_suborgs_for_portfolio( + portfolio_name, + portfolio, + domains_dict, + requests_dict, + existing_suborgs, + portfolio_created_suborgs, + created_suborgs, + ) + + return created_suborgs + + def _get_filtered_domains_and_requests(self, agency_dict): + """Get domains and requests filtered by agencies, grouped by normalized organization name.""" agencies = agency_dict.values() - domains = DomainInformation.objects.filter( - # Org name must not be null, and must not be the portfolio name - Q( - organization_name__isnull=False, - ) - & ~Q(organization_name__iexact=F("portfolio__organization_name")), - # Only get relevant data to the agency/portfolio we are targeting - Q(federal_agency__in=agencies) | Q(portfolio__in=portfolios), + # Get all organization names for matching + all_org_names = self._get_all_organization_names() + + # Build filters for domains and requests + domain_filters, request_filters = self._build_agency_filters(agencies, all_org_names) + + # Get filtered querysets + domains = self._get_filtered_domains(domain_filters) + requests = self._get_filtered_requests(request_filters) + + # Group by normalized organization name + domains_dict = self._group_by_normalized_org_name(domains, "organization_name") + requests_dict = self._group_by_normalized_org_name(requests, "organization_name") + + return domains_dict, requests_dict + + def _get_all_organization_names(self): + """Get all unique organization names from domains and requests.""" + domain_names = list( + DomainInformation.objects.filter(organization_name__isnull=False) + .values_list("organization_name", flat=True) + .distinct() ) - requests = DomainRequest.objects.filter( - # Org name must not be null, and must not be the portfolio name - Q( - organization_name__isnull=False, - ) - & ~Q(organization_name__iexact=F("portfolio__organization_name")), - # Only get relevant data to the agency/portfolio we are targeting - Q(federal_agency__in=agencies) | Q(portfolio__in=portfolios), + request_names = list( + DomainRequest.objects.filter(organization_name__isnull=False) + .values_list("organization_name", flat=True) + .distinct() + ) + return [normalize_string(name) for name in domain_names + request_names] + + def _build_agency_filters(self, agencies, all_org_names): + """Build Q filters for domains and requests based on agencies.""" + domain_filters = Q() + request_filters = Q() + + for agency in agencies: + agency_filter = self._create_fuzzy_organization_filter(agency, all_org_names) + domain_filters |= agency_filter + request_filters |= agency_filter + + return domain_filters, request_filters + + def _get_filtered_domains(self, domain_filters): + """Get filtered domain information objects.""" + return DomainInformation.objects.filter( + Q(organization_name__isnull=False) & ~Q(organization_name__iexact=F("portfolio__organization_name")), + domain_filters, ) - # First: get all existing suborgs - # NOTE: .all() is a heavy query, but unavoidable as we need to check for duplicate names. - # This is not quite as heavy as just using a for loop and .get_or_create, but worth noting. - # Change this if you can find a way to avoid doing this. - # This won't scale great for 10k+ records. - existing_suborgs = Suborganization.objects.all() - suborg_dict = {normalize_string(org.name): org for org in existing_suborgs} + def _get_filtered_requests(self, request_filters): + """Get filtered domain request objects.""" + return DomainRequest.objects.filter( + Q(organization_name__isnull=False) & ~Q(organization_name__iexact=F("portfolio__organization_name")), + request_filters, + ) - # Second: Group domains and requests by normalized organization name. - domains_dict = {} - requests_dict = {} - for domain in domains: - normalized_name = normalize_string(domain.organization_name) - domains_dict.setdefault(normalized_name, []).append(domain) + def _group_by_normalized_org_name(self, queryset, org_name_field): + """Group queryset objects by normalized organization name.""" + grouped_dict = {} + for obj in queryset: + org_name = getattr(obj, org_name_field) + normalized_name = normalize_string(org_name) + grouped_dict.setdefault(normalized_name, []).append(obj) + return grouped_dict - for request in requests: - normalized_name = normalize_string(request.organization_name) - requests_dict.setdefault(normalized_name, []).append(request) + def _get_existing_suborgs_for_portfolio(self, portfolio): + """Get existing suborganizations for a portfolio.""" + if not portfolio.pk: + return {} - # Third: Parse through each group of domains that have the same organization names, - # then create *one* suborg record from it. - # Normalize all suborg names so we don't add duplicate data unintentionally. - for portfolio_name, portfolio in portfolio_dict.items(): - # For a given agency, find all domains that list suborg info for it. - for norm_org_name, domains in domains_dict.items(): - # Don't add the record if the suborg name would equal the portfolio name - if norm_org_name == portfolio_name: - continue + existing_suborgs = portfolio.portfolio_suborganizations.all() + return {normalize_string(org.name): org for org in existing_suborgs} - new_suborg_name = None - if len(domains) == 1: - new_suborg_name = normalize_string(domains[0].organization_name, lowercase=False) - elif len(domains) > 1: - # Pick the best record for a suborg name (fewest spaces, most leading capitals) - best_record = max( - domains, - key=lambda rank: ( - -domain.organization_name.count(" "), - count_capitals(domain.organization_name, leading_only=True), - ), - ) - new_suborg_name = normalize_string(best_record.organization_name, lowercase=False) + def _get_portfolio_created_suborgs(self, created_suborgs, portfolio): + """Get suborganizations created in this batch for the given portfolio.""" + portfolio_created_suborgs = {} + for comp_key, suborg in created_suborgs.items(): + if suborg.portfolio == portfolio and ":" in comp_key: + norm_name = comp_key.split(":", 1)[1] + portfolio_created_suborgs[norm_name] = suborg + return portfolio_created_suborgs - # If the suborg already exists, don't add it again. - if norm_org_name not in suborg_dict and norm_org_name not in created_suborgs: - requests = requests_dict.get(norm_org_name) - suborg = Suborganization(name=new_suborg_name, portfolio=portfolio) - self.set_suborganization_location(suborg, domains, requests) - created_suborgs[norm_org_name] = suborg - return created_suborgs + def _create_suborgs_for_portfolio( + self, + portfolio_name, + portfolio, + domains_dict, + requests_dict, + existing_suborgs, + portfolio_created_suborgs, + created_suborgs, + ): + """Create suborganizations for a specific portfolio.""" + for norm_org_name, domains in domains_dict.items(): + # Skip if suborg name would equal portfolio name + if norm_org_name == portfolio_name: + continue + + # Skip if suborg already exists + if self._suborg_already_exists(norm_org_name, existing_suborgs, portfolio_created_suborgs): + continue + + # Create new suborganization + suborg = self._create_new_suborganization(norm_org_name, domains, requests_dict, portfolio) + + # Add to created suborgs with composite key + portfolio_identifier = portfolio.pk if portfolio.pk else id(portfolio) + composite_key = f"{portfolio_identifier}:{norm_org_name}" + created_suborgs[composite_key] = suborg + + self._log_action("CREATE", f"suborganization '{suborg}' for portfolio '{portfolio}'") + + def _suborg_already_exists(self, norm_org_name, existing_suborgs, portfolio_created_suborgs): + """Check if suborganization already exists in portfolio.""" + if norm_org_name in existing_suborgs: + existing_suborg = existing_suborgs[norm_org_name] + self._log_action( + "SKIP", f"suborganization '{existing_suborg}' already exists in portfolio '{existing_suborg.portfolio}'" + ) + return True + + return norm_org_name in portfolio_created_suborgs + + def _create_new_suborganization(self, norm_org_name, domains, requests_dict, portfolio): + """Create a new suborganization object.""" + suborg_name = self._determine_best_suborg_name(domains) + requests = requests_dict.get(norm_org_name) + + suborg = Suborganization(name=suborg_name, portfolio=portfolio) + self.set_suborganization_location(suborg, domains, requests) + + return suborg + + def _determine_best_suborg_name(self, domains): + """Determine the best name for a suborganization from domain records.""" + if len(domains) == 1: + return normalize_string(domains[0].organization_name, lowercase=False) + + # Pick the best record (fewest spaces, most leading capitals) + best_record = max( + domains, + key=lambda domain: ( + -domain.organization_name.count(" "), + count_capitals(domain.organization_name, leading_only=True), + ), + ) + return normalize_string(best_record.organization_name, lowercase=False) def set_suborganization_location(self, suborg, domains, requests): """Updates a single suborganization's location data if valid. @@ -476,11 +705,46 @@ class Command(BaseCommand): Returns a queryset of DomainInformation objects, or None if nothing changed. """ updated_domains = set() - domain_infos = federal_agency.domaininformation_set.all() + # Get all domain organization names + all_domain_org_names = list(DomainInformation.objects.values_list("organization_name", flat=True).distinct()) + # Use fuzzy matching to find domain information records that belong to this agency + # This creates a filter that matches domains in two ways: + # 1. Direct relationship: domains already linked to this federal agency + # 2. Fuzzy name matching: domains with organization names that are similar + # to this agency's name (handles abbreviations, variations, etc.) + # + # e.g., if federal_agency is "Department of Defense", this will find: + # - Domains already linked to DoD (direct relationship) + # - Domains with org names like "DoD", "Defense Dept", "US Dept of Defense" (fuzzy matching) + # - This helps capture domains that should belong to this agency but weren't + # properly linked due to name variations in the organization_name field + domain_filter = self._create_fuzzy_organization_filter( + federal_agency, [normalize_string(name) for name in all_domain_org_names if name] + ) + domain_infos = DomainInformation.objects.filter(domain_filter) + + if debug: + logger.info( + f"Fuzzy matching found {domain_infos.count()} domain information records for '{federal_agency.agency}'" + ) + for domain_info in domain_infos: - org_name = normalize_string(domain_info.organization_name, lowercase=False) + org_name = normalize_string(domain_info.organization_name) + new_suborg = suborgs.get(org_name, None) + + # ADD DRY RUN CHANGE TRACKING: + changes = [] + if domain_info.portfolio != portfolio: + changes.append(f"portfolio: {domain_info.portfolio} → {portfolio}") + if domain_info.sub_organization != new_suborg: + changes.append(f"sub_organization: {domain_info.sub_organization} → {new_suborg}") + + # Log changes in dry run mode + self._log_changes(f"domain '{domain_info.domain}'", changes) + + # Apply changes (these will still be tracked but not saved in dry run) domain_info.portfolio = portfolio - domain_info.sub_organization = suborgs.get(org_name, None) + domain_info.sub_organization = new_suborg updated_domains.add(domain_info) if not updated_domains and debug: @@ -489,13 +753,7 @@ class Command(BaseCommand): return updated_domains - def update_requests( - self, - portfolio, - federal_agency, - suborgs, - debug, - ): + def update_requests(self, portfolio, federal_agency, suborgs, debug): """ Associate portfolio with domain requests for a federal agency. Updates all relevant domain request records. @@ -505,28 +763,29 @@ class Command(BaseCommand): DomainRequest.DomainRequestStatus.INELIGIBLE, DomainRequest.DomainRequestStatus.REJECTED, ] - domain_requests = federal_agency.domainrequest_set.exclude(status__in=invalid_states) - # Add portfolio, sub_org, requested_suborg, suborg_city, and suborg_state_territory. - # For started domain requests, set the federal agency to None if not on a portfolio. + # Get all request organization names for fuzzy matching + all_request_org_names = list( + DomainRequest.objects.exclude(status__in=invalid_states) + .values_list("organization_name", flat=True) + .distinct() + ) + + # Use fuzzy matching to find domain requests that belong to this agency + request_filter = self._create_fuzzy_organization_filter( + federal_agency, [normalize_string(name) for name in all_request_org_names if name] + ) + domain_requests = DomainRequest.objects.filter(request_filter).exclude(status__in=invalid_states) + + if debug: + logger.info(f"Fuzzy matching found {domain_requests.count()} domain requests for '{federal_agency.agency}'") + + # Process each domain request for domain_request in domain_requests: if domain_request.status != DomainRequest.DomainRequestStatus.STARTED: - org_name = normalize_string(domain_request.organization_name, lowercase=False) - domain_request.portfolio = portfolio - domain_request.sub_organization = suborgs.get(org_name, None) - if domain_request.sub_organization is None: - domain_request.requested_suborganization = normalize_string( - domain_request.organization_name, lowercase=False - ) - domain_request.suborganization_city = normalize_string(domain_request.city, lowercase=False) - domain_request.suborganization_state_territory = domain_request.state_territory + self._update_active_request(domain_request, portfolio, suborgs) else: - # Clear the federal agency for started domain requests - agency_name = normalize_string(domain_request.federal_agency.agency) - portfolio_name = normalize_string(portfolio.organization_name) - if agency_name == portfolio_name: - domain_request.federal_agency = None - logger.info(f"Set federal agency on started domain request '{domain_request}' to None.") + self._handle_started_request(domain_request, portfolio) updated_domain_requests.add(domain_request) if not updated_domain_requests and debug: @@ -535,6 +794,47 @@ class Command(BaseCommand): return updated_domain_requests + def _update_active_request(self, domain_request, portfolio, suborgs): + """Update an active (non-started) domain request.""" + org_name = normalize_string(domain_request.organization_name) + new_suborg = suborgs.get(org_name, None) + + # Track changes for dry run + changes = [] + if domain_request.portfolio != portfolio: + changes.append(f"portfolio: {domain_request.portfolio} → {portfolio}") + if domain_request.sub_organization != new_suborg: + changes.append(f"sub_organization: {domain_request.sub_organization} → {new_suborg}") + + # Log changes in dry run mode + self._log_changes(f"request '{domain_request}'", changes) + + # Apply changes + domain_request.portfolio = portfolio + domain_request.sub_organization = new_suborg + + if domain_request.sub_organization is None: + domain_request.requested_suborganization = normalize_string( + domain_request.organization_name, lowercase=False + ) + domain_request.suborganization_city = normalize_string(domain_request.city, lowercase=False) + domain_request.suborganization_state_territory = domain_request.state_territory + + def _handle_started_request(self, domain_request, portfolio): + """Handle started domain requests by clearing federal agency if needed.""" + if not domain_request.federal_agency: + return + + agency_name = normalize_string(domain_request.federal_agency.agency) + portfolio_name = normalize_string(portfolio.organization_name) + + if agency_name == portfolio_name: + if self.dry_run: + logger.info(f"WOULD SET federal agency on started domain request '{domain_request}' to None.") + else: + domain_request.federal_agency = None + logger.info(f"Set federal agency on started domain request '{domain_request}' to None.") + def create_user_portfolio_permissions(self, domains): user_domain_roles = UserDomainRole.objects.select_related( "user", "domain", "domain__domain_info", "domain__domain_info__portfolio" @@ -571,3 +871,47 @@ class Command(BaseCommand): self.portfolio_invitation_changes.create.append(invitation) else: self.portfolio_invitation_changes.skip.append(invitation) + + def _log_action(self, action_type, obj, message=None): + """ + Log an action that would be performed, with dry run support. + + Args: + action_type: Type of action ('CREATE', 'UPDATE', 'DELETE') + obj: Object being acted upon + message: Optional custom message + """ + action_text = f"WOULD {action_type}" if self.dry_run else action_type.title() + obj_repr = message or str(obj) + + color = TerminalColors.OKGREEN + if action_type == "UPDATE": + color = TerminalColors.YELLOW + elif action_type == "DELETE": + color = TerminalColors.FAIL + + logger.info(f"{color}{action_text} {obj_repr}{TerminalColors.ENDC}") + + def _log_changes(self, obj, changes): + """Log what changes would be made to an object in dry run mode.""" + if self.dry_run and changes: + logger.info(f" WOULD UPDATE {obj}: {', '.join(changes)}") + + def _get_suborgs_for_portfolio(self, portfolio, created_suborgs): + """Get all suborganizations for a portfolio""" + suborgs = {} + + # Always add just-created suborganizations + if created_suborgs: + for composite_key, suborg in created_suborgs.items(): + if suborg.portfolio == portfolio: + suborgs[normalize_string(suborg.name)] = suborg + + # In normal execution, also add existing suborganizations from the database + if not self.dry_run: + for suborg in portfolio.portfolio_suborganizations.all(): + normalized_name = normalize_string(suborg.name) + if normalized_name not in suborgs: # Don't overwrite just-created ones + suborgs[normalized_name] = suborg + + return suborgs diff --git a/src/registrar/management/commands/utility/fuzzy_string_matcher.py b/src/registrar/management/commands/utility/fuzzy_string_matcher.py new file mode 100644 index 000000000..d45a6063a --- /dev/null +++ b/src/registrar/management/commands/utility/fuzzy_string_matcher.py @@ -0,0 +1,409 @@ +""" +Generic fuzzy string matching utility for any string comparison needs + +This util provides fuzzy string matching. It handles common variations +in naming conventions, such as: +- Abbreviations (e.g. "Department of" vs "Dept of") +- Punctuation (e.g. "U.S." vs "US") +- Word order (e.g. "John Smith" vs "Smith, John") +- Case insensitivity +- Common misspellings and typos +- Variants for federal agency names +It can be configured with different matching strategies and thresholds +to suit specific use cases, and supports detailed match reporting. +It also supports batch processing of multiple target strings against a pool of candidates. +This utility is designed to be flexible and extensible for various fuzzy matching needs. +""" + +import logging +from typing import Set, List, Dict, Optional, Callable, Tuple +from dataclasses import dataclass, field + +from rapidfuzz import fuzz, process +from registrar.models.utility.generic_helper import normalize_string + +logger = logging.getLogger(__name__) + + +@dataclass +class MatchingStrategy: + """Configuration for a single fuzzy matching strategy.""" + + scorer: Callable + threshold: int + name: str + weight: float = 1.0 # For weighted scoring if needed + + +@dataclass +class MatchResult: + """Result of a fuzzy matching operation.""" + + matched_strings: Set[str] + match_details: List[Tuple[str, float, str]] = field(default_factory=list) + variants_used: Set[str] = field(default_factory=set) + + def get_best_matches(self, limit: int = 10) -> List[Tuple[str, float, str]]: + """Get the top N matches sorted by score.""" + return sorted(self.match_details, key=lambda x: x[1], reverse=True)[:limit] + + +class StringVariantGenerator: + """Base class for generating string variants.""" + + def generate_variants(self, input_string: str) -> Set[str]: + """Generate variants of the input string.""" + raise NotImplementedError("Subclasses must implement generate_variants") + + +class FederalAgencyVariantGenerator(StringVariantGenerator): + """Generates variants specific to federal agency names.""" + + # Common abbreviation mappings for federal agencies + ABBREVIATION_MAPPINGS = [ + ("Department of", "Dept of", "Dept. of"), + ("Administration", "Admin"), + ("Agency", "Agcy"), + ("United States", "US", "U.S."), + ("Federal", "Fed"), + ("National", "Nat'l", "Natl"), + ] + + def generate_variants(self, agency_name: str) -> Set[str]: + """Generate federal agency name variants.""" + variants = {normalize_string(agency_name)} + + variants.update(self._get_us_prefix_variants(agency_name)) + variants.update(self._get_the_prefix_variants(agency_name)) + variants.update(self._get_abbreviation_variants(agency_name)) + variants.update(self._get_punctuation_variants(agency_name)) + + return variants + + def _get_us_prefix_variants(self, agency_name: str) -> Set[str]: + """Generate U.S./US prefix variations.""" + variants = set() + + if agency_name.startswith("U.S. "): + variants.add(normalize_string(agency_name[4:])) + variants.add(normalize_string("US " + agency_name[4:])) + variants.add(normalize_string("United States " + agency_name[4:])) + elif agency_name.startswith("US "): + variants.add(normalize_string(agency_name[3:])) + variants.add(normalize_string("U.S. " + agency_name[3:])) + variants.add(normalize_string("United States " + agency_name[3:])) + elif agency_name.startswith("United States "): + variants.add(normalize_string(agency_name[14:])) + variants.add(normalize_string("U.S. " + agency_name[14:])) + variants.add(normalize_string("US " + agency_name[14:])) + else: + variants.add(normalize_string("U.S. " + agency_name)) + variants.add(normalize_string("US " + agency_name)) + variants.add(normalize_string("United States " + agency_name)) + + return variants + + def _get_the_prefix_variants(self, agency_name: str) -> Set[str]: + """Generate 'The' prefix variations.""" + variants = set() + + if agency_name.startswith("The "): + variants.add(normalize_string(agency_name[4:])) + else: + variants.add(normalize_string("The " + agency_name)) + + return variants + + def _get_abbreviation_variants(self, agency_name: str) -> Set[str]: + """Generate common abbreviation variants.""" + variants = set() + + for full_form, *abbreviations in self.ABBREVIATION_MAPPINGS: + if full_form in agency_name: + for abbrev in abbreviations: + variants.add(normalize_string(agency_name.replace(full_form, abbrev))) + else: + # Try reverse mapping (abbrev -> full form) + for abbrev in abbreviations: + if abbrev in agency_name: + variants.add(normalize_string(agency_name.replace(abbrev, full_form))) + + return variants + + def _get_punctuation_variants(self, agency_name: str) -> Set[str]: + """Generate punctuation variations.""" + variants = set() + + # Remove all punctuation + no_punct = normalize_string(agency_name.replace(".", "").replace(",", "").replace("-", " ")) + variants.add(no_punct) + + # Common punctuation replacements + variants.add(normalize_string(agency_name.replace("&", "and"))) + variants.add(normalize_string(agency_name.replace(" and ", " & "))) + + return variants + + +class GenericFuzzyMatcher: + """ + Generic fuzzy string matcher that can be configured for different use cases. + + This class provides flexible fuzzy matching with: + - Configurable matching strategies + - Pluggable variant generators + - Detailed match reporting + - Threshold customization per strategy + """ + + # Default matching strategies + DEFAULT_STRATEGIES = [ + MatchingStrategy(fuzz.token_sort_ratio, 85, "token_sort"), + MatchingStrategy(fuzz.token_set_ratio, 85, "token_set"), + MatchingStrategy(fuzz.partial_ratio, 90, "partial"), + MatchingStrategy(fuzz.ratio, 90, "exact"), + ] + + def __init__( + self, + strategies: Optional[List[MatchingStrategy]] = None, + variant_generator: Optional[StringVariantGenerator] = None, + global_threshold: int = 85, + ): + """ + Initialize the generic fuzzy matcher. + + Args: + strategies: List of matching strategies to use + variant_generator: Generator for string variants + global_threshold: Default threshold for strategies that don't specify one + """ + self.strategies = strategies or self.DEFAULT_STRATEGIES + self.variant_generator = variant_generator + self.global_threshold = global_threshold + + def find_matches( + self, + target_string: str, + candidate_strings: List[str], + include_variants: bool = True, + report_details: bool = False, + ) -> MatchResult: + """ + Find strings that closely match the target string. + + Args: + target_string: The string to match against + candidate_strings: List of strings to search through + include_variants: Whether to include generated variants in matching + report_details: Whether to include detailed match information + + Returns: + MatchResult containing matched strings and optional details + """ + if not target_string or not candidate_strings: + return MatchResult(matched_strings=set()) + + target_variants, variants_used = self._prepare_target_variants(target_string, include_variants) + + matched_strings: Set[str] = set() + all_match_details: List[Tuple[str, float, str]] = [] + + # Exact string matching + self._perform_exact_matching( + target_variants, candidate_strings, matched_strings, all_match_details, report_details + ) + + # Fuzzy matching + self._perform_fuzzy_matching( + target_variants, candidate_strings, matched_strings, all_match_details, report_details + ) + + return MatchResult( + matched_strings=matched_strings, + match_details=all_match_details if report_details else [], + variants_used=variants_used, + ) + + def _prepare_target_variants(self, target_string: str, include_variants: bool) -> Tuple[Set[str], Set[str]]: + """Prepare target string variants for matching.""" + normalized_target = normalize_string(target_string) + target_variants = {normalized_target} + variants_used = {normalized_target} + + if include_variants and self.variant_generator: + generated_variants = self.variant_generator.generate_variants(target_string) + target_variants.update(generated_variants) + variants_used = target_variants.copy() + + return target_variants, variants_used + + def _perform_exact_matching( + self, + target_variants: Set[str], + candidate_strings: List[str], + matched_strings: Set[str], + all_match_details: List[Tuple[str, float, str]], + report_details: bool, + ) -> None: + """Perform exact string matching against target variants.""" + normalized_candidates = [normalize_string(candidate) for candidate in candidate_strings] + + for i, normalized_candidate in enumerate(normalized_candidates): + if normalized_candidate in target_variants: + matched_strings.add(candidate_strings[i]) + if report_details: + all_match_details.append((candidate_strings[i], 100.0, "exact_string_match")) + + def _perform_fuzzy_matching( + self, + target_variants: Set[str], + candidate_strings: List[str], + matched_strings: Set[str], + all_match_details: List[Tuple[str, float, str]], + report_details: bool, + ) -> None: + """Perform fuzzy matching using configured strategies.""" + for target_variant in target_variants: + for strategy in self.strategies: + self._apply_matching_strategy( + target_variant, candidate_strings, strategy, matched_strings, all_match_details, report_details + ) + + def _apply_matching_strategy( + self, + target_variant: str, + candidate_strings: List[str], + strategy: MatchingStrategy, + matched_strings: Set[str], + all_match_details: List[Tuple[str, float, str]], + report_details: bool, + ) -> None: + """Apply a single matching strategy to find matches.""" + try: + threshold = getattr(strategy, "threshold", self.global_threshold) + matches = process.extract( + target_variant, + candidate_strings, + scorer=strategy.scorer, + score_cutoff=threshold, + limit=None, + ) + + for match_string, score, _ in matches: + # Only add if not already found by exact matching + if match_string not in matched_strings: + matched_strings.add(match_string) + + if report_details: + self._add_match_detail(all_match_details, match_string, score, strategy.name) + + except Exception as e: + logger.warning(f"Error in fuzzy matching with strategy {strategy.name}: {e}") + + def _add_match_detail( + self, + all_match_details: List[Tuple[str, float, str]], + match_string: str, + score: float, + strategy_name: str, + ) -> None: + """Add match detail if it doesn't already exist.""" + existing_detail = next( + (detail for detail in all_match_details if detail[0] == match_string and detail[2] == strategy_name), + None, + ) + if not existing_detail: + all_match_details.append((match_string, score, strategy_name)) + + def find_best_match( + self, target_string: str, candidate_strings: List[str], include_variants: bool = True + ) -> Optional[Tuple[str, float]]: + """ + Find the single best match for the target string. + + Returns: + Tuple of (best_match_string, score) or None if no matches found + """ + result = self.find_matches(target_string, candidate_strings, include_variants, report_details=True) + + if not result.match_details: + return None + + best_match = max(result.match_details, key=lambda x: x[1]) + return (best_match[0], best_match[1]) + + def batch_find_matches( + self, target_strings: List[str], candidate_strings: List[str], include_variants: bool = True + ) -> Dict[str, MatchResult]: + """ + Find matches for multiple target strings efficiently. + + Returns: + Dictionary mapping each target string to its MatchResult + """ + results = {} + for target in target_strings: + results[target] = self.find_matches(target, candidate_strings, include_variants, report_details=True) + return results + + +class FuzzyMatchingTestRunner: + """Utility for testing and reporting fuzzy matching results.""" + + def __init__(self, matcher: GenericFuzzyMatcher): + self.matcher = matcher + + def generate_test_report( + self, target_strings: List[str], candidate_strings: List[str], max_display: int = 10 + ) -> str: + """ + Generate a comprehensive test report for fuzzy matching. + + Args: + target_strings: Strings to match against + candidate_strings: Pool of candidates to search + max_display: Maximum matches to display per target + + Returns: + Formatted report string + """ + report_lines = [] + report_lines.append("=" * 70) + report_lines.append(" FUZZY MATCHING TEST REPORT") + report_lines.append("=" * 70) + + for target in target_strings: + result = self.matcher.find_matches(target, candidate_strings, include_variants=True, report_details=True) + + report_lines.append(f"\nTarget: '{target}'") + report_lines.append("-" * 50) + + if result.variants_used: + report_lines.append(f"Variants tested: {len(result.variants_used)}") + sample_variants = list(result.variants_used)[:5] + report_lines.append(f"Sample variants: {sample_variants}") + + best_matches = result.get_best_matches(max_display) + report_lines.append(f"\nTop matches found: {len(best_matches)}") + + for match_string, score, strategy in best_matches: + report_lines.append(f" • {match_string} (score: {score:.1f}, strategy: {strategy})") + + if len(result.matched_strings) > max_display: + remaining = len(result.matched_strings) - max_display + report_lines.append(f" ... and {remaining} more matches") + + return "\n".join(report_lines) + + +# Factory functions for common use cases +def create_federal_agency_matcher(threshold: int = 85) -> GenericFuzzyMatcher: + """Create a fuzzy matcher optimized for federal agency names.""" + # Use default strategies but override their thresholds + return GenericFuzzyMatcher(variant_generator=FederalAgencyVariantGenerator(), global_threshold=threshold) + + +def create_basic_string_matcher(threshold: int = 85) -> GenericFuzzyMatcher: + """Create a basic fuzzy matcher without variant generation.""" + return GenericFuzzyMatcher(global_threshold=threshold) diff --git a/src/registrar/models/domain_information.py b/src/registrar/models/domain_information.py index 3839e5290..410e42f28 100644 --- a/src/registrar/models/domain_information.py +++ b/src/registrar/models/domain_information.py @@ -88,7 +88,7 @@ class DomainInformation(TimeStampedModel): # ##### data fields from the initial form ##### generic_org_type = models.CharField( max_length=255, - choices=OrganizationChoices.choices, + choices=OrganizationChoices.choices, # type: ignore[misc] null=True, blank=True, help_text="Type of organization", @@ -155,7 +155,7 @@ class DomainInformation(TimeStampedModel): ) state_territory = models.CharField( max_length=2, - choices=StateTerritoryChoices.choices, + choices=StateTerritoryChoices.choices, # type: ignore[misc] null=True, blank=True, verbose_name="state, territory, or military post", diff --git a/src/registrar/models/portfolio.py b/src/registrar/models/portfolio.py index 656efcff5..7ef9673ce 100644 --- a/src/registrar/models/portfolio.py +++ b/src/registrar/models/portfolio.py @@ -42,7 +42,7 @@ class Portfolio(TimeStampedModel): organization_type = models.CharField( max_length=255, - choices=OrganizationChoices.choices, + choices=OrganizationChoices.choices, # type: ignore[misc] null=True, blank=True, ) @@ -88,7 +88,7 @@ class Portfolio(TimeStampedModel): # (imports enums from domain_request.py) state_territory = models.CharField( max_length=2, - choices=StateTerritoryChoices.choices, + choices=StateTerritoryChoices.choices, # type: ignore[misc] null=True, blank=True, verbose_name="state, territory, or military post", diff --git a/src/registrar/models/transition_domain.py b/src/registrar/models/transition_domain.py index 0b0cffcec..2fe4e43e2 100644 --- a/src/registrar/models/transition_domain.py +++ b/src/registrar/models/transition_domain.py @@ -33,7 +33,7 @@ class TransitionDomain(TimeStampedModel): null=False, blank=True, default=StatusChoices.READY, - choices=StatusChoices.choices, + choices=StatusChoices.choices, # type: ignore[misc] verbose_name="status", help_text="domain status during the transfer", ) diff --git a/src/registrar/tests/test_fuzz_string_matcher.py b/src/registrar/tests/test_fuzz_string_matcher.py new file mode 100644 index 000000000..c067d566a --- /dev/null +++ b/src/registrar/tests/test_fuzz_string_matcher.py @@ -0,0 +1,233 @@ +from django.test import TestCase +from registrar.models import User, FederalAgency +from registrar.management.commands.utility.fuzzy_string_matcher import ( + create_federal_agency_matcher, + create_basic_string_matcher, + MatchResult, + FederalAgencyVariantGenerator, + GenericFuzzyMatcher, + MatchingStrategy, +) +from rapidfuzz import fuzz + + +class TestFuzzyStringMatcher(TestCase): + + def setUp(self): + self.user = User.objects.create(username="testuser") + self.federal_agency = FederalAgency.objects.create(agency="Test Federal Agency") + + def tearDown(self): + FederalAgency.objects.all().delete() + User.objects.all().delete() + + def test_federal_agency_matcher_creation(self): + """Test creating a federal agency matcher with different thresholds""" + matcher = create_federal_agency_matcher(threshold=85) + + self.assertIsInstance(matcher, GenericFuzzyMatcher) + self.assertIsInstance(matcher.variant_generator, FederalAgencyVariantGenerator) + self.assertEqual(matcher.global_threshold, 85) + + def test_basic_string_matcher_creation(self): + """Test creating a basic string matcher without variants""" + matcher = create_basic_string_matcher(threshold=75) + + self.assertIsInstance(matcher, GenericFuzzyMatcher) + self.assertIsNone(matcher.variant_generator) + self.assertEqual(matcher.global_threshold, 75) + + def test_federal_agency_exact_match(self): + """Test exact matching for federal agencies""" + matcher = create_federal_agency_matcher(threshold=85) + + candidates = [ + "Department of Defense", + "Department of Agriculture", + "Federal Bureau of Investigation", + "Central Intelligence Agency", + ] + + result = matcher.find_matches("Department of Defense", candidates) + + self.assertIsInstance(result, MatchResult) + self.assertIn("Department of Defense", result.matched_strings) + self.assertGreater(len(result.matched_strings), 0) + + def test_federal_agency_abbreviation_matching(self): + """Test that federal agency abbreviations are matched correctly""" + matcher = create_federal_agency_matcher(threshold=80) + + candidates = ["Department of Defense", "Dept of Defense", "DoD", "Department of Agriculture"] + + # Should match both full name and abbreviations + result = matcher.find_matches("Department of Defense", candidates) + + # Should find multiple matches due to variant generation + self.assertGreater(len(result.matched_strings), 1) + self.assertIn("Department of Defense", result.matched_strings) + + def test_federal_agency_us_prefix_variants(self): + """Test U.S. prefix variant generation""" + generator = FederalAgencyVariantGenerator() + + variants = generator.generate_variants("U.S. Department of Defense") + + # Should include variants without U.S. prefix + variant_strings = [v.lower() for v in variants] + self.assertTrue(any("department of defense" in v for v in variant_strings)) + self.assertTrue(any("us department of defense" in v for v in variant_strings)) + + def test_match_result_functionality(self): + """Test MatchResult class functionality""" + matcher = create_federal_agency_matcher(threshold=80) + + candidates = ["Department of Defense", "Dept of Defense", "Defense Department", "Department of Agriculture"] + + result = matcher.find_matches("Department of Defense", candidates, report_details=True) + + # Test MatchResult methods + self.assertIsInstance(result.matched_strings, set) + self.assertIsInstance(result.match_details, list) + self.assertIsInstance(result.variants_used, set) + + # Test get_best_matches + best_matches = result.get_best_matches(limit=2) + self.assertLessEqual(len(best_matches), 2) + + # Each match detail should be a 3-tuple + for match_string, score, strategy_name in result.match_details: + self.assertIsInstance(match_string, str) + self.assertIsInstance(score, (int, float)) + self.assertIsInstance(strategy_name, str) + + def test_find_best_match(self): + """Test finding the single best match""" + matcher = create_federal_agency_matcher(threshold=80) + + candidates = ["Department of Defense", "Department of Agriculture", "Dept of Defense"] + + best_match = matcher.find_best_match("Department of Defense", candidates) + + self.assertIsNotNone(best_match) + match_string, score = best_match + self.assertEqual(match_string, "Department of Defense") + self.assertGreater(score, 95) # Should be very high for exact match + + def test_batch_matching(self): + """Test batch processing of multiple targets""" + matcher = create_federal_agency_matcher(threshold=80) + + targets = ["Department of Defense", "FBI", "CIA"] + candidates = [ + "Department of Defense", + "Federal Bureau of Investigation", + "Central Intelligence Agency", + "Department of Agriculture", + ] + + results = matcher.batch_find_matches(targets, candidates) + + self.assertEqual(len(results), 3) + for target in targets: + self.assertIn(target, results) + self.assertIsInstance(results[target], MatchResult) + + def test_no_matches_scenario(self): + """Test behavior when no matches are found""" + matcher = create_federal_agency_matcher(threshold=95) # Very high threshold + + candidates = ["Completely Different Agency"] + + result = matcher.find_matches("Department of Defense", candidates) + + self.assertEqual(len(result.matched_strings), 0) + self.assertEqual(len(result.match_details), 0) + + def test_matching_with_variants_disabled(self): + """Test matching with variant generation disabled""" + matcher = create_federal_agency_matcher(threshold=85) + + candidates = ["Department of Defense", "Dept of Defense"] + + # With variants disabled, should only match exact or very similar strings + result = matcher.find_matches("DoD", candidates, include_variants=False) + + # Might not find matches since variants are disabled + self.assertIsInstance(result, MatchResult) + + def test_custom_matching_strategies(self): + """Test creating matcher with custom strategies""" + custom_strategies = [ + MatchingStrategy(fuzz.ratio, 90, "exact_ratio"), + MatchingStrategy(fuzz.partial_ratio, 85, "partial_ratio"), + ] + + matcher = GenericFuzzyMatcher( + strategies=custom_strategies, variant_generator=FederalAgencyVariantGenerator(), global_threshold=80 + ) + + candidates = ["Department of Defense", "Dept of Defense"] + result = matcher.find_matches("Department of Defense", candidates, report_details=True) + + # Check that our custom strategies were used + strategy_names = [detail[2] for detail in result.match_details] + self.assertTrue(any("exact_ratio" in name for name in strategy_names)) + + def test_rapidfuzz_integration(self): + """Test that rapidfuzz integration works correctly (this was the original bug)""" + from rapidfuzz import process, fuzz + + query = "Test Federal Agency" + choices = ["Test Federal Agency", "Another Agency", "Test Federal Agency Subunit"] + + # This should return 3-tuples and not cause ValueError + matches = process.extract(query, choices, scorer=fuzz.token_sort_ratio, score_cutoff=85, limit=None) + + # Verify the format + self.assertIsInstance(matches, list) + if matches: + first_match = matches[0] + self.assertEqual(len(first_match), 3) + + # Should be able to unpack as 3-tuple + match_string, score, index = first_match + self.assertIsInstance(match_string, str) + self.assertIsInstance(score, (int, float)) + self.assertIsInstance(index, int) + + def test_create_federal_portfolio_integration(self): + """Test the exact scenario used in create_federal_portfolio command""" + matcher = create_federal_agency_matcher(threshold=85) + + # Simulate real data from create_federal_portfolio + target_agency_name = "Test Federal Agency" + all_org_names = ["Test Federal Agency", "Testorg", "Test Federal Agency Division", "Another Organization"] + + result = matcher.find_matches(target_agency_name, all_org_names) + + self.assertIsInstance(result, MatchResult) + self.assertIn("Test Federal Agency", result.matched_strings) + self.assertGreater(len(result.matched_strings), 0) + + def test_empty_input_handling(self): + """Test handling of empty inputs""" + matcher = create_federal_agency_matcher(threshold=85) + + # Empty candidates list + result = matcher.find_matches("Test Agency", []) + self.assertEqual(len(result.matched_strings), 0) + + # Empty target string + result = matcher.find_matches("", ["Test Agency"]) + self.assertIsInstance(result, MatchResult) + + def test_special_characters_handling(self): + """Test handling of special characters and punctuation""" + matcher = create_federal_agency_matcher(threshold=80) + + candidates = ["U.S. Department of Defense", "Department of Veterans Affairs", "Health & Human Services"] + + # Should handle punctuation variants + result = matcher.find_matches("US Department of Defense", candidates) + self.assertGreater(len(result.matched_strings), 0) diff --git a/src/requirements.txt b/src/requirements.txt index adcd8292b..72683ab92 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -67,3 +67,4 @@ urllib3==2.3.0; python_version >= '3.9' whitenoise==6.9.0; python_version >= '3.9' zope.event==5.0; python_version >= '3.7' zope.interface==7.2; python_version >= '3.8' +rapidfuzz==3.4.0; python_version >= '3.8' \ No newline at end of file diff --git a/src/zap.conf b/src/zap.conf index a0a60bdc7..eccdfb7c5 100644 --- a/src/zap.conf +++ b/src/zap.conf @@ -76,6 +76,7 @@ 10038 OUTOFSCOPE http://app:8080/suborganization/ 10038 OUTOFSCOPE http://app:8080/transfer/ 10038 OUTOFSCOPE http://app:8080/prototype-dns +10038 OUTOFSCOPE http://app:8080/.*404.* # This URL always returns 404, so include it as well. 10038 OUTOFSCOPE http://app:8080/todo # OIDC isn't configured in the test environment and DEBUG=True so this gives a 500 without CSP headers