mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-15 17:17:02 +02:00
Merge branch 'main' into nl/1064-Change-domain-application-to-domain-requests
This commit is contained in:
commit
c2679ff9b5
23 changed files with 805 additions and 1058 deletions
46
.github/workflows/createcachetable.yaml
vendored
Normal file
46
.github/workflows/createcachetable.yaml
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# This workflow can be run from the CLI for any environment
|
||||||
|
# gh workflow run createcachetable.yaml -f environment=ENVIRONMENT
|
||||||
|
# OR
|
||||||
|
# cf run-task getgov-ENVIRONMENT --command 'python manage.py createcachetable' --name createcachetable
|
||||||
|
|
||||||
|
name: Create cache table
|
||||||
|
run-name: Create cache table for ${{ github.event.inputs.environment }}
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
environment:
|
||||||
|
type: choice
|
||||||
|
description: Which environment should we create cache table for?
|
||||||
|
options:
|
||||||
|
- stable
|
||||||
|
- staging
|
||||||
|
- development
|
||||||
|
- backup
|
||||||
|
- ky
|
||||||
|
- es
|
||||||
|
- nl
|
||||||
|
- rh
|
||||||
|
- za
|
||||||
|
- gd
|
||||||
|
- rb
|
||||||
|
- ko
|
||||||
|
- ab
|
||||||
|
- rjm
|
||||||
|
- dk
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
createcachetable:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CF_USERNAME: CF_${{ github.event.inputs.environment }}_USERNAME
|
||||||
|
CF_PASSWORD: CF_${{ github.event.inputs.environment }}_PASSWORD
|
||||||
|
steps:
|
||||||
|
- name: Create cache table for ${{ github.event.inputs.environment }}
|
||||||
|
uses: cloud-gov/cg-cli-tools@main
|
||||||
|
with:
|
||||||
|
cf_username: ${{ secrets[env.CF_USERNAME] }}
|
||||||
|
cf_password: ${{ secrets[env.CF_PASSWORD] }}
|
||||||
|
cf_org: cisa-dotgov
|
||||||
|
cf_space: ${{ github.event.inputs.environment }}
|
||||||
|
cf_command: "run-task getgov-${{ github.event.inputs.environment }} --command 'python manage.py createcachetable' --name createcachetable"
|
74
docs/architecture/decisions/0025-caching.md
Normal file
74
docs/architecture/decisions/0025-caching.md
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# 24. Production Release Cadence
|
||||||
|
|
||||||
|
Date: 2024-14-02
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
In Review
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
We experienced problems with our Cloudfront caching infrastructure early in our November launch. In response, we turned off caching across the application. We would like to utilize caching again without incurring the same issues.
|
||||||
|
|
||||||
|
Details:
|
||||||
|
Originally, Cloudfront was utilized to provide caching capabilities in our application. All incoming HTTP requests first go through a Cloudfront endpoint, which has a caching infrastructure enabled by default. Cloudfront then decides whether to pass each request to our running Django app inside cloud.gov or if it will respond to with cached data. The big problem with this feature is Cloudfront's caching has a default timeout of 24-hours, which we cannot control. This led to issues on our November launch; Incidents reported include the following...
|
||||||
|
- Users couldn't utilize login.gov properly and had to wait a day before they would be able to login. This was traced back to the 24-hour cache timeout.
|
||||||
|
- Changes made by admins would not be reflected in the app (due to the cached data not updating)
|
||||||
|
|
||||||
|
To resolve these issues, we added "no cache" headers throughout our application. Currently, every single HTTP response that comes from Django says "Cache control: no cache" in the headers, which instructs Cloudfront not to cache the associated data. This effectively removes Cloudfront caching for us.
|
||||||
|
|
||||||
|
Although we could leave our architecture as-is, we decided to investigate options for improving our use of caching (instead of just disabling it completely).
|
||||||
|
|
||||||
|
## Considered Options
|
||||||
|
|
||||||
|
**Option 1:** Cache static resources using Whitenoise
|
||||||
|
|
||||||
|
Caching static resources should pose little risk to our application's functionality. Currently, every static resource from /public/... is hitting our Django application inside of Cloud.gov. We already use a Django plugin called whitenoise that can do hash-based linking to static assets so that they can be cached forever by Cloudfront. (If the content changes, then the hash changes, then it results in a different filename.)
|
||||||
|
|
||||||
|
See ticket [#1371](https://github.com/cisagov/manage.get.gov/issues/1371) for more information.
|
||||||
|
|
||||||
|
**Option 2:** Leave things as-is (we had some discussion on whether or not caching static pages will make enough of a difference to be worth the effort)
|
||||||
|
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
We decided on Option 2 - leave things as-is (for now).
|
||||||
|
|
||||||
|
Preliminary analysis suggest that implementing caching on static pages will result in negligible improvements to our application load time. A quick look at Kibana logs suggests most of these resources take less than 10ms to load...
|
||||||
|

|
||||||
|
|
||||||
|
If we look at average load times in Kibana (here is [the Kibana page with preloaded query](https://logs.fr.cloud.gov/app/visualize#/create?_a=(filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'logs-app*',key:'@cf.app',negate:!f,params:(query:getgov-stable),type:phrase),query:(match_phrase:('@cf.app':getgov-stable)))),linked:!f,query:(language:lucene,query:''),uiState:(),vis:(aggs:!((enabled:!t,id:'1',params:(customLabel:'Average%20Response%20Time%20in%20ms',field:rtr.response_time_ms),schema:metric,type:avg),(enabled:!t,id:'2',params:(drop_partials:!f,extended_bounds:(),field:'@timestamp',interval:d,min_doc_count:1,scaleMetricValues:!f,timeRange:(from:now-20d,to:now),useNormalizedEsInterval:!t),schema:segment,type:date_histogram)),params:(addLegend:!t,addTimeMarker:!f,addTooltip:!t,categoryAxes:!((id:CategoryAxis-1,labels:(filter:!t,show:!t,truncate:100),position:bottom,scale:(type:linear),show:!t,style:(),title:(),type:category)),grid:(categoryLines:!f),labels:(show:!f),legendPosition:right,seriesParams:!((data:(id:'1',label:'Average%20Response%20Time%20in%20ms'),drawLinesBetweenPoints:!t,lineWidth:2,mode:stacked,show:!t,showCircles:!t,type:histogram,valueAxis:ValueAxis-1)),thresholdLine:(color:%23E7664C,show:!f,style:full,value:10,width:1),times:!(),type:histogram,valueAxes:!((id:ValueAxis-1,labels:(filter:!f,rotate:0,show:!t,truncate:100),name:LeftAxis-1,position:left,scale:(mode:normal,type:linear),show:!t,style:(),title:(text:'Average%20Response%20Time%20in%20ms'),type:value))),title:'',type:histogram))&_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-2w,to:now))&indexPattern=logs-app*&type=histogram)), it looks like we are doing great for load times in stable (using the rtr.response_time_ms metric), staying under 200ms (in the last 4 weeks) and usually hovering around 40-80ms. Some google searching suggests that "an ideal page load time is between 0-2 seconds, but 3 seconds is also considered to be an acceptable score. Anything above 3 seconds increases the likelihood of visitors leaving your site." (Quote shamelessly copied from Sematex)
|
||||||
|

|
||||||
|
|
||||||
|
NOTE: While we considered implementing caching in a sandbox (See footnote) in order to examine risks and benefits of OPTION 1 in more detail, this incurred more overhead than expected (mainly due to poor documentation). Therefore, we decided it was not worth the investment.
|
||||||
|
|
||||||
|
Therefore, implementing caching using Whitenoise is not currently worth it for the following reasons;
|
||||||
|
- Minimal gains: We would only be caching static files which would not result in a large performance boost
|
||||||
|
- Risks: Incurs risk of unforeseen loading issues (we can’t entirely rule out that we won’t run into issues like we did in our November launch incident). Although we don’t think static files should pose a problem, due diligence would call us to monitor for any unforeseen issues that might arise, which adds cost to this project that doesn’t seem proportional to the gains.
|
||||||
|
- Maintenance: We would have to provide custom settings in cloudfront (coordinated through Cameron) for any sandboxes and other environments where caching is enabled. If we move down the route of utilizing CDN, it would be good for every environment to have this service enabled so our dev environments reflect stable settings. This could possibly introduce some overhead and maintenance issues. (Although further investigation might reveal these to be negligible.)
|
||||||
|
|
||||||
|
Overall, it is recommended that we SHELVE this caching endeavor for a future scenario where we have exhausted other (likely more lucrative) options for performance improvements. If we then still need to make improvements to our load times, perhaps we can revisit this and examine caching not only static files, but other resources as well (with caution).
|
||||||
|
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
We will forgo (negligible) load-time improvements by leaving caching off.
|
||||||
|
|
||||||
|
## (Footnote - How to implement caching)
|
||||||
|
Here are notes for implementing caching using whitenoise should we decide to pick this up again in the future;
|
||||||
|
|
||||||
|
1 - Add caching capability to a sandbox using the following steps (or [following documentation for command line](https://cloud.gov/docs/services/external-domain-service/))
|
||||||
|
- Log-in to the cloud.gov website
|
||||||
|
- [Navigate to "Services"](https://dashboard.fr.cloud.gov/services). Click "Add Service"...
|
||||||
|
- Choose "Marketplace Service"
|
||||||
|
- For the fields, select Cloud Foundry, Organization = "cisa-dotgov", Space = "[your sandbox. eg. "nl"]". Click "Next"
|
||||||
|
- For the Service, select "External Domain". Click "Next"
|
||||||
|
- For the Plan, select "domain-with-cdn" (here is [documentation on CDN](https://cloud.gov/docs/management/custom-domains/))
|
||||||
|
- If you choose to bind the app, a JSON string will be required (we believe this should do it: {"domains": "example.gov"}, where "example.gov" is replaced with the domain name you want to use for this application)
|
||||||
|
Before you can continue, work with Cameron to setup the DNS in AWS (use the following documentation linked below):
|
||||||
|
https://cloud.gov/docs/services/external-domain-service/
|
||||||
|
- Once the DNS is setup, you *should* be able to continue. We did not test this.
|
||||||
|
|
||||||
|
2- Enable caching in the code with Whitenoise (see [documentation on Whitenoise Caching](https://whitenoise.readthedocs.io/en/latest/djangohtml#add-compression-and-caching-support))
|
||||||
|
|
||||||
|
3- Take performance measurements before/after caching is enabled to determine cost-benefits of implementing caching. (NOTE: [lighthouse](https://developer.chrome.com/blog/lighthouse-load-performance) might be useful for this step)
|
BIN
docs/architecture/doc-images/caching-average-load-times.png
Normal file
BIN
docs/architecture/doc-images/caching-average-load-times.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 434 KiB |
BIN
docs/architecture/doc-images/caching-rtr-logs.png
Normal file
BIN
docs/architecture/doc-images/caching-rtr-logs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 200 KiB |
|
@ -90,6 +90,9 @@ cd src/
|
||||||
cd ..
|
cd ..
|
||||||
cf push getgov-$1 -f ops/manifests/manifest-$1.yaml
|
cf push getgov-$1 -f ops/manifests/manifest-$1.yaml
|
||||||
|
|
||||||
|
echo "Creating cache table..."
|
||||||
|
cf run-task getgov-$1 --command 'python manage.py createcachetable' --name createcachetable
|
||||||
|
|
||||||
read -p "Please provide the email of the space developer: " -r
|
read -p "Please provide the email of the space developer: " -r
|
||||||
cf set-space-role $REPLY cisa-dotgov $1 SpaceDeveloper
|
cf set-space-role $REPLY cisa-dotgov $1 SpaceDeveloper
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ django-login-required-middleware = "*"
|
||||||
greenlet = "*"
|
greenlet = "*"
|
||||||
gevent = "*"
|
gevent = "*"
|
||||||
fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"}
|
fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"}
|
||||||
geventconnpool = {git = "https://github.com/rasky/geventconnpool.git", ref = "1bbb93a714a331a069adf27265fe582d9ba7ecd4"}
|
tblib = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
django-debug-toolbar = "*"
|
django-debug-toolbar = "*"
|
||||||
|
|
583
src/Pipfile.lock
generated
583
src/Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "a672aeb8951fd850e90ad87c6f03cf71e2fc2b387d56fd3942361cb0b45bb449"
|
"sha256": "b5d93b1b9ccafc37019276a222957544bab3f1f46b5dab8a0f2ffc2e5c9e1678"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {},
|
"requires": {},
|
||||||
|
@ -32,29 +32,29 @@
|
||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:65acfe7f1cf2a9b7df3d4edb87c8022e02685825bd1957e7bb678cc0d09f5e5f",
|
"sha256:8b3f5cc7fbedcbb22271c328039df8a6ab343001e746e0cdb24774c426cadcf8",
|
||||||
"sha256:73f5ec89cb3ddb3ed577317889fd2f2df783f66b6502a9a4239979607e33bf74"
|
"sha256:f201b6a416f809283d554c652211eecec9fe3a52ed4063dab3f3e7aea7571d9c"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.37"
|
"version": "==1.34.54"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2a5bf33aacd2d970afd3d492e179e06ea98a5469030d5cfe7a2ad9995f7bb2ef",
|
"sha256:4061ff4be3efcf53547ebadf2c94d419dfc8be7beec24e9fa1819599ffd936fa",
|
||||||
"sha256:3c46ddb1679e6ef45ca78b48665398636bda532a07cd476e4b500697d13d9a99"
|
"sha256:bf215d93e9d5544c593962780d194e74c6ee40b883d0b885e62ef35fc0ec01e5"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.37"
|
"version": "==1.34.54"
|
||||||
},
|
},
|
||||||
"cachetools": {
|
"cachetools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2",
|
"sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945",
|
||||||
"sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"
|
"sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==5.3.2"
|
"version": "==5.3.3"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -228,41 +228,41 @@
|
||||||
},
|
},
|
||||||
"cryptography": {
|
"cryptography": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380",
|
"sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee",
|
||||||
"sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589",
|
"sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576",
|
||||||
"sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea",
|
"sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d",
|
||||||
"sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65",
|
"sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30",
|
||||||
"sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a",
|
"sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413",
|
||||||
"sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3",
|
"sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb",
|
||||||
"sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008",
|
"sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da",
|
||||||
"sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1",
|
"sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4",
|
||||||
"sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2",
|
"sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd",
|
||||||
"sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635",
|
"sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc",
|
||||||
"sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2",
|
"sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8",
|
||||||
"sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90",
|
"sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1",
|
||||||
"sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee",
|
"sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc",
|
||||||
"sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a",
|
"sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e",
|
||||||
"sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242",
|
"sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8",
|
||||||
"sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12",
|
"sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940",
|
||||||
"sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2",
|
"sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400",
|
||||||
"sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d",
|
"sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7",
|
||||||
"sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be",
|
"sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16",
|
||||||
"sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee",
|
"sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278",
|
||||||
"sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6",
|
"sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74",
|
||||||
"sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529",
|
"sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec",
|
||||||
"sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929",
|
"sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1",
|
||||||
"sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1",
|
"sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2",
|
||||||
"sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6",
|
"sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c",
|
||||||
"sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a",
|
"sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922",
|
||||||
"sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446",
|
"sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a",
|
||||||
"sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9",
|
"sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6",
|
||||||
"sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888",
|
"sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1",
|
||||||
"sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4",
|
"sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e",
|
||||||
"sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33",
|
"sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac",
|
||||||
"sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f"
|
"sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==42.0.2"
|
"version": "==42.0.5"
|
||||||
},
|
},
|
||||||
"defusedxml": {
|
"defusedxml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -330,11 +330,11 @@
|
||||||
},
|
},
|
||||||
"django-csp": {
|
"django-csp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:01443a07723f9a479d498bd7bb63571aaa771e690f64bde515db6cdb76e8041a",
|
"sha256:19b2978b03fcd73517d7d67acbc04fbbcaec0facc3e83baa502965892d1e0719",
|
||||||
"sha256:01eda02ad3f10261c74131cdc0b5a6a62b7c7ad4fd017fbefb7a14776e0a9727"
|
"sha256:ef0f1a9f7d8da68ae6e169c02e9ac661c0ecf04db70e0d1d85640512a68471c0"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.7"
|
"version": "==3.8"
|
||||||
},
|
},
|
||||||
"django-fsm": {
|
"django-fsm": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -384,12 +384,12 @@
|
||||||
},
|
},
|
||||||
"faker": {
|
"faker": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:60e89e5c0b584e285a7db05eceba35011a241954afdab2853cb246c8a56700a2",
|
"sha256:117ce1a2805c1bc5ca753b3dc6f9d567732893b2294b827d3164261ee8f20267",
|
||||||
"sha256:b7f76bb1b2ac4cdc54442d955e36e477c387000f31ce46887fb9722a041be60b"
|
"sha256:458d93580de34403a8dec1e8d5e6be2fee96c4deca63b95d71df7a6a80a690de"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==23.1.0"
|
"version": "==23.3.0"
|
||||||
},
|
},
|
||||||
"fred-epplib": {
|
"fred-epplib": {
|
||||||
"git": "https://github.com/cisagov/epplib.git",
|
"git": "https://github.com/cisagov/epplib.git",
|
||||||
|
@ -404,61 +404,59 @@
|
||||||
},
|
},
|
||||||
"future": {
|
"future": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"
|
"sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216",
|
||||||
|
"sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==0.18.3"
|
"version": "==1.0.0"
|
||||||
},
|
},
|
||||||
"gevent": {
|
"gevent": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:272cffdf535978d59c38ed837916dfd2b5d193be1e9e5dcc60a5f4d5025dd98a",
|
"sha256:03aa5879acd6b7076f6a2a307410fb1e0d288b84b03cdfd8c74db8b4bc882fc5",
|
||||||
"sha256:2c7b5c9912378e5f5ccf180d1fdb1e83f42b71823483066eddbe10ef1a2fcaa2",
|
"sha256:117e5837bc74a1673605fb53f8bfe22feb6e5afa411f524c835b2ddf768db0de",
|
||||||
"sha256:36a549d632c14684bcbbd3014a6ce2666c5f2a500f34d58d32df6c9ea38b6535",
|
"sha256:141a2b24ad14f7b9576965c0c84927fc85f824a9bb19f6ec1e61e845d87c9cd8",
|
||||||
"sha256:4368f341a5f51611411ec3fc62426f52ac3d6d42eaee9ed0f9eebe715c80184e",
|
"sha256:14532a67f7cb29fb055a0e9b39f16b88ed22c66b96641df8c04bdc38c26b9ea5",
|
||||||
"sha256:43daf68496c03a35287b8b617f9f91e0e7c0d042aebcc060cadc3f049aadd653",
|
"sha256:1dffb395e500613e0452b9503153f8f7ba587c67dd4a85fc7cd7aa7430cb02cc",
|
||||||
"sha256:455e5ee8103f722b503fa45dedb04f3ffdec978c1524647f8ba72b4f08490af1",
|
"sha256:2955eea9c44c842c626feebf4459c42ce168685aa99594e049d03bedf53c2800",
|
||||||
"sha256:45792c45d60f6ce3d19651d7fde0bc13e01b56bb4db60d3f32ab7d9ec467374c",
|
"sha256:2ae3a25ecce0a5b0cd0808ab716bfca180230112bb4bc89b46ae0061d62d4afe",
|
||||||
"sha256:4e24c2af9638d6c989caffc691a039d7c7022a31c0363da367c0d32ceb4a0648",
|
"sha256:2e9ac06f225b696cdedbb22f9e805e2dd87bf82e8fa5e17756f94e88a9d37cf7",
|
||||||
"sha256:52b4abf28e837f1865a9bdeef58ff6afd07d1d888b70b6804557e7908032e599",
|
"sha256:368a277bd9278ddb0fde308e6a43f544222d76ed0c4166e0d9f6b036586819d9",
|
||||||
"sha256:52e9f12cd1cda96603ce6b113d934f1aafb873e2c13182cf8e86d2c5c41982ea",
|
"sha256:3adfb96637f44010be8abd1b5e73b5070f851b817a0b182e601202f20fa06533",
|
||||||
"sha256:5f3c781c84794926d853d6fb58554dc0dcc800ba25c41d42f6959c344b4db5a6",
|
"sha256:3d5325ccfadfd3dcf72ff88a92fb8fc0b56cacc7225f0f4b6dcf186c1a6eeabc",
|
||||||
"sha256:62d121344f7465e3739989ad6b91f53a6ca9110518231553fe5846dbe1b4518f",
|
"sha256:432fc76f680acf7cf188c2ee0f5d3ab73b63c1f03114c7cd8a34cebbe5aa2056",
|
||||||
"sha256:65883ac026731ac112184680d1f0f1e39fa6f4389fd1fc0bf46cc1388e2599f9",
|
"sha256:44098038d5e2749b0784aabb27f1fcbb3f43edebedf64d0af0d26955611be8d6",
|
||||||
"sha256:707904027d7130ff3e59ea387dddceedb133cc742b00b3ffe696d567147a9c9e",
|
"sha256:5a1df555431f5cd5cc189a6ee3544d24f8c52f2529134685f1e878c4972ab026",
|
||||||
"sha256:72c002235390d46f94938a96920d8856d4ffd9ddf62a303a0d7c118894097e34",
|
"sha256:6c47ae7d1174617b3509f5d884935e788f325eb8f1a7efc95d295c68d83cce40",
|
||||||
"sha256:7532c17bc6c1cbac265e751b95000961715adef35a25d2b0b1813aa7263fb397",
|
"sha256:6f947a9abc1a129858391b3d9334c45041c08a0f23d14333d5b844b6e5c17a07",
|
||||||
"sha256:78eebaf5e73ff91d34df48f4e35581ab4c84e22dd5338ef32714264063c57507",
|
"sha256:782a771424fe74bc7e75c228a1da671578c2ba4ddb2ca09b8f959abdf787331e",
|
||||||
"sha256:7c1abc6f25f475adc33e5fc2dbcc26a732608ac5375d0d306228738a9ae14d3b",
|
"sha256:7899a38d0ae7e817e99adb217f586d0a4620e315e4de577444ebeeed2c5729be",
|
||||||
"sha256:7c28e38dcde327c217fdafb9d5d17d3e772f636f35df15ffae2d933a5587addd",
|
"sha256:7b00f8c9065de3ad226f7979154a7b27f3b9151c8055c162332369262fc025d8",
|
||||||
"sha256:7ccf0fd378257cb77d91c116e15c99e533374a8153632c48a3ecae7f7f4f09fe",
|
"sha256:8f4b8e777d39013595a7740b4463e61b1cfe5f462f1b609b28fbc1e4c4ff01e5",
|
||||||
"sha256:921dda1c0b84e3d3b1778efa362d61ed29e2b215b90f81d498eb4d8eafcd0b7a",
|
"sha256:90cbac1ec05b305a1b90ede61ef73126afdeb5a804ae04480d6da12c56378df1",
|
||||||
"sha256:a2898b7048771917d85a1d548fd378e8a7b2ca963db8e17c6d90c76b495e0e2b",
|
"sha256:918cdf8751b24986f915d743225ad6b702f83e1106e08a63b736e3a4c6ead789",
|
||||||
"sha256:a3c5e9b1f766a7a64833334a18539a362fb563f6c4682f9634dea72cbe24f771",
|
"sha256:9202f22ef811053077d01f43cc02b4aaf4472792f9fd0f5081b0b05c926cca19",
|
||||||
"sha256:ada07076b380918829250201df1d016bdafb3acf352f35e5693b59dceee8dd2e",
|
"sha256:94138682e68ec197db42ad7442d3cf9b328069c3ad8e4e5022e6b5cd3e7ffae5",
|
||||||
"sha256:b101086f109168b23fa3586fccd1133494bdb97f86920a24dc0b23984dc30b69",
|
"sha256:968581d1717bbcf170758580f5f97a2925854943c45a19be4d47299507db2eb7",
|
||||||
"sha256:bf456bd6b992eb0e1e869e2fd0caf817f0253e55ca7977fd0e72d0336a8c1c6a",
|
"sha256:9d8d0642c63d453179058abc4143e30718b19a85cbf58c2744c9a63f06a1d388",
|
||||||
"sha256:bf7af500da05363e66f122896012acb6e101a552682f2352b618e541c941a011",
|
"sha256:a7ceb59986456ce851160867ce4929edaffbd2f069ae25717150199f8e1548b8",
|
||||||
"sha256:c3e5d2fa532e4d3450595244de8ccf51f5721a05088813c1abd93ad274fe15e7",
|
"sha256:b9913c45d1be52d7a5db0c63977eebb51f68a2d5e6fd922d1d9b5e5fd758cc98",
|
||||||
"sha256:c84d34256c243b0a53d4335ef0bc76c735873986d478c53073861a92566a8d71",
|
"sha256:bde283313daf0b34a8d1bab30325f5cb0f4e11b5869dbe5bc61f8fe09a8f66f3",
|
||||||
"sha256:d163d59f1be5a4c4efcdd13c2177baaf24aadf721fdf2e1af9ee54a998d160f5",
|
"sha256:bf5b9c72b884c6f0c4ed26ef204ee1f768b9437330422492c319470954bc4cc7",
|
||||||
"sha256:d57737860bfc332b9b5aa438963986afe90f49645f6e053140cfa0fa1bdae1ae",
|
"sha256:ca80b121bbec76d7794fcb45e65a7eca660a76cc1a104ed439cdbd7df5f0b060",
|
||||||
"sha256:dbb22a9bbd6a13e925815ce70b940d1578dbe5d4013f20d23e8a11eddf8d14a7",
|
"sha256:cdf66977a976d6a3cfb006afdf825d1482f84f7b81179db33941f2fc9673bb1d",
|
||||||
"sha256:dcb8612787a7f4626aa881ff15ff25439561a429f5b303048f0fca8a1c781c39",
|
"sha256:d4faf846ed132fd7ebfbbf4fde588a62d21faa0faa06e6f468b7faa6f436b661",
|
||||||
"sha256:dd6c32ab977ecf7c7b8c2611ed95fa4aaebd69b74bf08f4b4960ad516861517d",
|
"sha256:d7f87c2c02e03d99b95cfa6f7a776409083a9e4d468912e18c7680437b29222c",
|
||||||
"sha256:de350fde10efa87ea60d742901e1053eb2127ebd8b59a7d3b90597eb4e586599",
|
"sha256:dd23df885318391856415e20acfd51a985cba6919f0be78ed89f5db9ff3a31cb",
|
||||||
"sha256:e1ead6863e596a8cc2a03e26a7a0981f84b6b3e956101135ff6d02df4d9a6b07",
|
"sha256:f5de3c676e57177b38857f6e3cdfbe8f38d1cd754b63200c0615eaa31f514b4f",
|
||||||
"sha256:ed7a048d3e526a5c1d55c44cb3bc06cfdc1947d06d45006cc4cf60dedc628904",
|
"sha256:f5e8e8d60e18d5f7fd49983f0c4696deeddaf6e608fbab33397671e2fcc6cc91",
|
||||||
"sha256:f632487c87866094546a74eefbca2c74c1d03638b715b6feb12e80120960185a",
|
"sha256:f7cac622e11b4253ac4536a654fe221249065d9a69feb6cdcd4d9af3503602e0",
|
||||||
"sha256:fae8d5b5b8fa2a8f63b39f5447168b02db10c888a3e387ed7af2bd1b8612e543",
|
"sha256:f8a04cf0c5b7139bc6368b461257d4a757ea2fe89b3773e494d235b7dd51119f",
|
||||||
"sha256:fde6402c5432b835fbb7698f1c7f2809c8d6b2bd9d047ac1f5a7c1d5aa569303"
|
"sha256:f8bb35ce57a63c9a6896c71a285818a3922d8ca05d150fd1fe49a7f57287b836",
|
||||||
|
"sha256:fbfdce91239fe306772faab57597186710d5699213f4df099d1612da7320d682"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==23.9.1"
|
"version": "==24.2.1"
|
||||||
},
|
|
||||||
"geventconnpool": {
|
|
||||||
"git": "https://github.com/rasky/geventconnpool.git",
|
|
||||||
"ref": "1bbb93a714a331a069adf27265fe582d9ba7ecd4"
|
|
||||||
},
|
},
|
||||||
"greenlet": {
|
"greenlet": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -710,11 +708,11 @@
|
||||||
},
|
},
|
||||||
"marshmallow": {
|
"marshmallow": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd",
|
"sha256:20f53be28c6e374a711a16165fb22a8dc6003e3f7cda1285e3ca777b9193885b",
|
||||||
"sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"
|
"sha256:e7997f83571c7fd476042c2c188e4ee8a78900ca5e74bd9c8097afa56624e9bd"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==3.20.2"
|
"version": "==3.21.0"
|
||||||
},
|
},
|
||||||
"oic": {
|
"oic": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -742,10 +740,10 @@
|
||||||
},
|
},
|
||||||
"phonenumberslite": {
|
"phonenumberslite": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2b04a53401d01ab42564c1abc762fc9808ad398e71dacfa3b38d4321e112ecb3",
|
"sha256:137d53d5d78dca30bc2becf81a3e2ac74deb8f0997e9bbe44de515ece4bd92bd",
|
||||||
"sha256:74e3ee63dfa2bb562ce2e6ce74ce76ae74a2f81472005b80343235fb43426db4"
|
"sha256:e1f4359bff90c86d1b52db0e726d3334df00cc7d9c9c2ef66561d5f7a774d4ba"
|
||||||
],
|
],
|
||||||
"version": "==8.13.29"
|
"version": "==8.13.31"
|
||||||
},
|
},
|
||||||
"psycopg2-binary": {
|
"psycopg2-binary": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -874,104 +872,104 @@
|
||||||
},
|
},
|
||||||
"pydantic": {
|
"pydantic": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f",
|
"sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a",
|
||||||
"sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"
|
"sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.6.1"
|
"version": "==2.6.3"
|
||||||
},
|
},
|
||||||
"pydantic-core": {
|
"pydantic-core": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379",
|
"sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a",
|
||||||
"sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06",
|
"sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed",
|
||||||
"sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05",
|
"sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979",
|
||||||
"sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7",
|
"sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff",
|
||||||
"sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753",
|
"sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5",
|
||||||
"sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a",
|
"sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45",
|
||||||
"sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731",
|
"sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340",
|
||||||
"sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc",
|
"sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad",
|
||||||
"sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380",
|
"sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23",
|
||||||
"sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3",
|
"sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6",
|
||||||
"sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c",
|
"sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7",
|
||||||
"sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11",
|
"sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241",
|
||||||
"sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990",
|
"sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda",
|
||||||
"sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a",
|
"sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187",
|
||||||
"sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2",
|
"sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba",
|
||||||
"sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8",
|
"sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c",
|
||||||
"sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97",
|
"sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2",
|
||||||
"sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a",
|
"sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c",
|
||||||
"sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8",
|
"sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132",
|
||||||
"sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef",
|
"sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf",
|
||||||
"sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77",
|
"sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972",
|
||||||
"sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33",
|
"sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db",
|
||||||
"sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82",
|
"sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade",
|
||||||
"sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5",
|
"sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4",
|
||||||
"sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b",
|
"sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8",
|
||||||
"sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55",
|
"sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f",
|
||||||
"sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e",
|
"sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9",
|
||||||
"sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b",
|
"sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48",
|
||||||
"sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7",
|
"sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec",
|
||||||
"sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec",
|
"sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d",
|
||||||
"sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc",
|
"sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9",
|
||||||
"sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469",
|
"sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb",
|
||||||
"sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b",
|
"sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4",
|
||||||
"sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20",
|
"sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89",
|
||||||
"sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e",
|
"sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c",
|
||||||
"sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d",
|
"sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9",
|
||||||
"sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f",
|
"sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da",
|
||||||
"sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b",
|
"sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac",
|
||||||
"sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039",
|
"sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b",
|
||||||
"sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e",
|
"sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf",
|
||||||
"sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2",
|
"sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e",
|
||||||
"sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f",
|
"sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137",
|
||||||
"sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b",
|
"sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1",
|
||||||
"sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc",
|
"sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b",
|
||||||
"sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8",
|
"sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8",
|
||||||
"sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522",
|
"sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e",
|
||||||
"sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e",
|
"sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053",
|
||||||
"sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784",
|
"sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01",
|
||||||
"sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a",
|
"sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe",
|
||||||
"sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890",
|
"sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd",
|
||||||
"sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485",
|
"sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805",
|
||||||
"sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545",
|
"sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183",
|
||||||
"sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f",
|
"sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8",
|
||||||
"sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943",
|
"sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99",
|
||||||
"sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878",
|
"sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820",
|
||||||
"sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f",
|
"sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074",
|
||||||
"sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17",
|
"sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256",
|
||||||
"sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7",
|
"sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8",
|
||||||
"sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286",
|
"sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975",
|
||||||
"sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c",
|
"sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad",
|
||||||
"sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb",
|
"sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e",
|
||||||
"sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646",
|
"sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca",
|
||||||
"sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978",
|
"sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df",
|
||||||
"sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8",
|
"sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b",
|
||||||
"sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15",
|
"sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a",
|
||||||
"sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272",
|
"sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a",
|
||||||
"sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2",
|
"sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721",
|
||||||
"sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55",
|
"sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a",
|
||||||
"sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf",
|
"sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f",
|
||||||
"sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545",
|
"sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2",
|
||||||
"sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4",
|
"sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97",
|
||||||
"sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a",
|
"sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6",
|
||||||
"sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804",
|
"sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed",
|
||||||
"sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4",
|
"sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc",
|
||||||
"sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0",
|
"sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1",
|
||||||
"sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a",
|
"sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe",
|
||||||
"sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113",
|
"sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120",
|
||||||
"sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d",
|
"sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f",
|
||||||
"sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"
|
"sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.16.2"
|
"version": "==2.16.3"
|
||||||
},
|
},
|
||||||
"pydantic-settings": {
|
"pydantic-settings": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:26b1492e0a24755626ac5e6d715e9077ab7ad4fb5f19a8b7ed7011d52f36141c",
|
"sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed",
|
||||||
"sha256:7621c0cb5d90d1140d2f0ef557bdf03573aac7035948109adf2574770b77605a"
|
"sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.1.0"
|
"version": "==2.2.1"
|
||||||
},
|
},
|
||||||
"pyjwkest": {
|
"pyjwkest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -982,11 +980,11 @@
|
||||||
},
|
},
|
||||||
"python-dateutil": {
|
"python-dateutil": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
"sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
|
||||||
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
"sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==2.8.2"
|
"version": "==2.9.0.post0"
|
||||||
},
|
},
|
||||||
"python-dotenv": {
|
"python-dotenv": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1015,11 +1013,11 @@
|
||||||
},
|
},
|
||||||
"setuptools": {
|
"setuptools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05",
|
"sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56",
|
||||||
"sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"
|
"sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==69.0.3"
|
"version": "==69.1.1"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1037,14 +1035,23 @@
|
||||||
"markers": "python_version >= '3.5'",
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==0.4.4"
|
"version": "==0.4.4"
|
||||||
},
|
},
|
||||||
"typing-extensions": {
|
"tblib": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783",
|
"sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129",
|
||||||
"sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"
|
"sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==4.9.0"
|
"version": "==3.0.0"
|
||||||
|
},
|
||||||
|
"typing-extensions": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475",
|
||||||
|
"sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==4.10.0"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1073,45 +1080,45 @@
|
||||||
},
|
},
|
||||||
"zope.interface": {
|
"zope.interface": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff",
|
"sha256:02adbab560683c4eca3789cc0ac487dcc5f5a81cc48695ec247f00803cafe2fe",
|
||||||
"sha256:13b7d0f2a67eb83c385880489dbb80145e9d344427b4262c49fbf2581677c11c",
|
"sha256:14e02a6fc1772b458ebb6be1c276528b362041217b9ca37e52ecea2cbdce9fac",
|
||||||
"sha256:1f294a15f7723fc0d3b40701ca9b446133ec713eafc1cc6afa7b3d98666ee1ac",
|
"sha256:25e0af9663eeac6b61b231b43c52293c2cb7f0c232d914bdcbfd3e3bd5c182ad",
|
||||||
"sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f",
|
"sha256:2606955a06c6852a6cff4abeca38346ed01e83f11e960caa9a821b3626a4467b",
|
||||||
"sha256:2f8d89721834524a813f37fa174bac074ec3d179858e4ad1b7efd4401f8ac45d",
|
"sha256:396f5c94654301819a7f3a702c5830f0ea7468d7b154d124ceac823e2419d000",
|
||||||
"sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309",
|
"sha256:3b240883fb43160574f8f738e6d09ddbdbf8fa3e8cea051603d9edfd947d9328",
|
||||||
"sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736",
|
"sha256:3b6c62813c63c543a06394a636978b22dffa8c5410affc9331ce6cdb5bfa8565",
|
||||||
"sha256:387545206c56b0315fbadb0431d5129c797f92dc59e276b3ce82db07ac1c6179",
|
"sha256:4ae9793f114cee5c464cc0b821ae4d36e1eba961542c6086f391a61aee167b6f",
|
||||||
"sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb",
|
"sha256:4bce517b85f5debe07b186fc7102b332676760f2e0c92b7185dd49c138734b70",
|
||||||
"sha256:57d0a8ce40ce440f96a2c77824ee94bf0d0925e6089df7366c2272ccefcb7941",
|
"sha256:4d45d2ba8195850e3e829f1f0016066a122bfa362cc9dc212527fc3d51369037",
|
||||||
"sha256:5a804abc126b33824a44a7aa94f06cd211a18bbf31898ba04bd0924fbe9d282d",
|
"sha256:4dd374927c00764fcd6fe1046bea243ebdf403fba97a937493ae4be2c8912c2b",
|
||||||
"sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92",
|
"sha256:506f5410b36e5ba494136d9fa04c548eaf1a0d9c442b0b0e7a0944db7620e0ab",
|
||||||
"sha256:6af47f10cfc54c2ba2d825220f180cc1e2d4914d783d6fc0cd93d43d7bc1c78b",
|
"sha256:59f7374769b326a217d0b2366f1c176a45a4ff21e8f7cebb3b4a3537077eff85",
|
||||||
"sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41",
|
"sha256:5ee9789a20b0081dc469f65ff6c5007e67a940d5541419ca03ef20c6213dd099",
|
||||||
"sha256:70d2cef1bf529bff41559be2de9d44d47b002f65e17f43c73ddefc92f32bf00f",
|
"sha256:6fc711acc4a1c702ca931fdbf7bf7c86f2a27d564c85c4964772dadf0e3c52f5",
|
||||||
"sha256:7ebc4d34e7620c4f0da7bf162c81978fce0ea820e4fa1e8fc40ee763839805f3",
|
"sha256:75d2ec3d9b401df759b87bc9e19d1b24db73083147089b43ae748aefa63067ef",
|
||||||
"sha256:964a7af27379ff4357dad1256d9f215047e70e93009e532d36dcb8909036033d",
|
"sha256:76e0531d86523be7a46e15d379b0e975a9db84316617c0efe4af8338dc45b80c",
|
||||||
"sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8",
|
"sha256:8af82afc5998e1f307d5e72712526dba07403c73a9e287d906a8aa2b1f2e33dd",
|
||||||
"sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3",
|
"sha256:8f5d2c39f3283e461de3655e03faf10e4742bb87387113f787a7724f32db1e48",
|
||||||
"sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1",
|
"sha256:97785604824981ec8c81850dd25c8071d5ce04717a34296eeac771231fbdd5cd",
|
||||||
"sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1",
|
"sha256:a3046e8ab29b590d723821d0785598e0b2e32b636a0272a38409be43e3ae0550",
|
||||||
"sha256:a41f87bb93b8048fe866fa9e3d0c51e27fe55149035dcf5f43da4b56732c0a40",
|
"sha256:abb0b3f2cb606981c7432f690db23506b1db5899620ad274e29dbbbdd740e797",
|
||||||
"sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d",
|
"sha256:ac7c2046d907e3b4e2605a130d162b1b783c170292a11216479bb1deb7cadebe",
|
||||||
"sha256:ad54ed57bdfa3254d23ae04a4b1ce405954969c1b0550cc2d1d2990e8b439de1",
|
"sha256:af27b3fe5b6bf9cd01b8e1c5ddea0a0d0a1b8c37dc1c7452f1e90bf817539c6d",
|
||||||
"sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605",
|
"sha256:b386b8b9d2b6a5e1e4eadd4e62335571244cb9193b7328c2b6e38b64cfda4f0e",
|
||||||
"sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7",
|
"sha256:b66335bbdbb4c004c25ae01cc4a54fd199afbc1fd164233813c6d3c2293bb7e1",
|
||||||
"sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd",
|
"sha256:d54f66c511ea01b9ef1d1a57420a93fbb9d48a08ec239f7d9c581092033156d0",
|
||||||
"sha256:c9559138690e1bd4ea6cd0954d22d1e9251e8025ce9ede5d0af0ceae4a401e43",
|
"sha256:de125151a53ecdb39df3cb3deb9951ed834dd6a110a9e795d985b10bb6db4532",
|
||||||
"sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0",
|
"sha256:de7916380abaef4bb4891740879b1afcba2045aee51799dfd6d6ca9bdc71f35f",
|
||||||
"sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b",
|
"sha256:e2fefad268ff5c5b314794e27e359e48aeb9c8bb2cbb5748a071757a56f6bb8f",
|
||||||
"sha256:e441e8b7d587af0414d25e8d05e27040d78581388eed4c54c30c0c91aad3a379",
|
"sha256:e7b2bed4eea047a949296e618552d3fed00632dc1b795ee430289bdd0e3717f3",
|
||||||
"sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a",
|
"sha256:e87698e2fea5ca2f0a99dff0a64ce8110ea857b640de536c76d92aaa2a91ff3a",
|
||||||
"sha256:ef43ee91c193f827e49599e824385ec7c7f3cd152d74cb1dfe02cb135f264d83",
|
"sha256:ede888382882f07b9e4cd942255921ffd9f2901684198b88e247c7eabd27a000",
|
||||||
"sha256:ef467d86d3cfde8b39ea1b35090208b0447caaabd38405420830f7fd85fbdd56",
|
"sha256:f444de0565db46d26c9fa931ca14f497900a295bd5eba480fc3fad25af8c763e",
|
||||||
"sha256:f89b28772fc2562ed9ad871c865f5320ef761a7fcc188a935e21fe8b31a38ca9",
|
"sha256:fa994e8937e8ccc7e87395b7b35092818905cf27c651e3ff3e7f29729f5ce3ce",
|
||||||
"sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de"
|
"sha256:febceb04ee7dd2aef08c2ff3d6f8a07de3052fc90137c507b0ede3ea80c21440"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==6.1"
|
"version": "==6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
|
@ -1142,32 +1149,32 @@
|
||||||
},
|
},
|
||||||
"black": {
|
"black": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0269dfdea12442022e88043d2910429bed717b2d04523867a85dacce535916b8",
|
"sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8",
|
||||||
"sha256:07204d078e25327aad9ed2c64790d681238686bce254c910de640c7cc4fc3aa6",
|
"sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8",
|
||||||
"sha256:08b34e85170d368c37ca7bf81cf67ac863c9d1963b2c1780c39102187ec8dd62",
|
"sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd",
|
||||||
"sha256:1a95915c98d6e32ca43809d46d932e2abc5f1f7d582ffbe65a5b4d1588af7445",
|
"sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9",
|
||||||
"sha256:2588021038bd5ada078de606f2a804cadd0a3cc6a79cb3e9bb3a8bf581325a4c",
|
"sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31",
|
||||||
"sha256:2fa6a0e965779c8f2afb286f9ef798df770ba2b6cee063c650b96adec22c056a",
|
"sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92",
|
||||||
"sha256:34afe9da5056aa123b8bfda1664bfe6fb4e9c6f311d8e4a6eb089da9a9173bf9",
|
"sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f",
|
||||||
"sha256:3897ae5a21ca132efa219c029cce5e6bfc9c3d34ed7e892113d199c0b1b444a2",
|
"sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29",
|
||||||
"sha256:40657e1b78212d582a0edecafef133cf1dd02e6677f539b669db4746150d38f6",
|
"sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4",
|
||||||
"sha256:48b5760dcbfe5cf97fd4fba23946681f3a81514c6ab8a45b50da67ac8fbc6c7b",
|
"sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693",
|
||||||
"sha256:5242ecd9e990aeb995b6d03dc3b2d112d4a78f2083e5a8e86d566340ae80fec4",
|
"sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218",
|
||||||
"sha256:5cdc2e2195212208fbcae579b931407c1fa9997584f0a415421748aeafff1168",
|
"sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a",
|
||||||
"sha256:5d7b06ea8816cbd4becfe5f70accae953c53c0e53aa98730ceccb0395520ee5d",
|
"sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23",
|
||||||
"sha256:7258c27115c1e3b5de9ac6c4f9957e3ee2c02c0b39222a24dc7aa03ba0e986f5",
|
"sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0",
|
||||||
"sha256:854c06fb86fd854140f37fb24dbf10621f5dab9e3b0c29a690ba595e3d543024",
|
"sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982",
|
||||||
"sha256:a21725862d0e855ae05da1dd25e3825ed712eaaccef6b03017fe0853a01aa45e",
|
"sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894",
|
||||||
"sha256:a83fe522d9698d8f9a101b860b1ee154c1d25f8a82ceb807d319f085b2627c5b",
|
"sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540",
|
||||||
"sha256:b3d64db762eae4a5ce04b6e3dd745dcca0fb9560eb931a5be97472e38652a161",
|
"sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430",
|
||||||
"sha256:e298d588744efda02379521a19639ebcd314fba7a49be22136204d7ed1782717",
|
"sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b",
|
||||||
"sha256:e2c8dfa14677f90d976f68e0c923947ae68fa3961d61ee30976c388adc0b02c8",
|
"sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2",
|
||||||
"sha256:ecba2a15dfb2d97105be74bbfe5128bc5e9fa8477d8c46766505c1dda5883aac",
|
"sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6",
|
||||||
"sha256:fc1ec9aa6f4d98d022101e015261c056ddebe3da6a8ccfc2c792cbe0349d48b7"
|
"sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==24.1.1"
|
"version": "==24.2.0"
|
||||||
},
|
},
|
||||||
"blinker": {
|
"blinker": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1179,12 +1186,12 @@
|
||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:65acfe7f1cf2a9b7df3d4edb87c8022e02685825bd1957e7bb678cc0d09f5e5f",
|
"sha256:8b3f5cc7fbedcbb22271c328039df8a6ab343001e746e0cdb24774c426cadcf8",
|
||||||
"sha256:73f5ec89cb3ddb3ed577317889fd2f2df783f66b6502a9a4239979607e33bf74"
|
"sha256:f201b6a416f809283d554c652211eecec9fe3a52ed4063dab3f3e7aea7571d9c"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.37"
|
"version": "==1.34.54"
|
||||||
},
|
},
|
||||||
"boto3-mocking": {
|
"boto3-mocking": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1197,28 +1204,28 @@
|
||||||
},
|
},
|
||||||
"boto3-stubs": {
|
"boto3-stubs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:97b5ca3d3145385acde5af46ca2da3fc74f433545034c36183f389e99771516e",
|
"sha256:7db5194e47f76e0010cd00b6ad9725db114d6a3fd04e52ceed3ef1181fe326bc",
|
||||||
"sha256:c6618c7126bac0337c05e161e9c428febc57d6a24d7ff62de46e67761f402c57"
|
"sha256:c7b2e8b99f4896cf1226df47d4badaaa8df7426008c96a428bf00205695669e9"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.37"
|
"version": "==1.34.54"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2a5bf33aacd2d970afd3d492e179e06ea98a5469030d5cfe7a2ad9995f7bb2ef",
|
"sha256:4061ff4be3efcf53547ebadf2c94d419dfc8be7beec24e9fa1819599ffd936fa",
|
||||||
"sha256:3c46ddb1679e6ef45ca78b48665398636bda532a07cd476e4b500697d13d9a99"
|
"sha256:bf215d93e9d5544c593962780d194e74c6ee40b883d0b885e62ef35fc0ec01e5"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.37"
|
"version": "==1.34.54"
|
||||||
},
|
},
|
||||||
"botocore-stubs": {
|
"botocore-stubs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:087cd42973edcb5527dc97eec87fa29fffecc39691249486e02045677d4a2dbe",
|
"sha256:958f0084322dc9e549f73151b686fa51b15858fb2b3a573b9f4367f073fff463",
|
||||||
"sha256:d6bcea8a6872aa46d389027dc5c022241fd0a2047a8b858aa5005e6151ed30a7"
|
"sha256:bcc35bfbd14d1261813681c40108f2ce85fdf082c15b0a04016d3c22dd93b73f"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8' and python_version < '4.0'",
|
"markers": "python_version >= '3.8' and python_version < '4.0'",
|
||||||
"version": "==1.34.37"
|
"version": "==1.34.54"
|
||||||
},
|
},
|
||||||
"click": {
|
"click": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1427,11 +1434,11 @@
|
||||||
},
|
},
|
||||||
"python-dateutil": {
|
"python-dateutil": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
"sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
|
||||||
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
"sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==2.8.2"
|
"version": "==2.9.0.post0"
|
||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1492,11 +1499,11 @@
|
||||||
},
|
},
|
||||||
"rich": {
|
"rich": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa",
|
"sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222",
|
||||||
"sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"
|
"sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.7.0'",
|
"markers": "python_full_version >= '3.7.0'",
|
||||||
"version": "==13.7.0"
|
"version": "==13.7.1"
|
||||||
},
|
},
|
||||||
"s3transfer": {
|
"s3transfer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1532,11 +1539,11 @@
|
||||||
},
|
},
|
||||||
"stevedore": {
|
"stevedore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:8cc040628f3cea5d7128f2e76cf486b2251a4e543c7b938f58d9a377f6694a2d",
|
"sha256:1c15d95766ca0569cad14cb6272d4d31dae66b011a929d7c18219c176ea1b5c9",
|
||||||
"sha256:a54534acf9b89bc7ed264807013b505bf07f74dbe4bcfa37d32bd063870b087c"
|
"sha256:46b93ca40e1114cea93d738a6c1e365396981bb6bb78c27045b7587c9473544d"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==5.1.0"
|
"version": "==5.2.0"
|
||||||
},
|
},
|
||||||
"tomli": {
|
"tomli": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1548,11 +1555,11 @@
|
||||||
},
|
},
|
||||||
"types-awscrt": {
|
"types-awscrt": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:06a859189a329ca8e66d56ceeef2391488e39b878fbd2141f115eab4d416fe22",
|
"sha256:61811bbf4de95248939f9276a434be93d2b95f6ccfe8aa94e56999e9778cfcc2",
|
||||||
"sha256:f61a120d3e98ee1387bc5ca4b93437f258cc5c2af1f55f8634ec4cee5729f178"
|
"sha256:79d5bfb01f64701b6cf442e89a37d9c4dc6dbb79a46f2f611739b2418d30ecfd"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7' and python_version < '4.0'",
|
"markers": "python_version >= '3.7' and python_version < '4.0'",
|
||||||
"version": "==0.20.3"
|
"version": "==0.20.5"
|
||||||
},
|
},
|
||||||
"types-cachetools": {
|
"types-cachetools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1580,12 +1587,12 @@
|
||||||
},
|
},
|
||||||
"types-requests": {
|
"types-requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:03a28ce1d7cd54199148e043b2079cdded22d6795d19a2c2a6791a4b2b5e2eb5",
|
"sha256:a82807ec6ddce8f00fe0e949da6d6bc1fbf1715420218a9640d695f70a9e5a9b",
|
||||||
"sha256:9592a9a4cb92d6d75d9b491a41477272b710e021011a2a3061157e2fb1f1a5d1"
|
"sha256:f1721dba8385958f504a5386240b92de4734e047a08a40751c1654d1ac3349c5"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.31.0.20240125"
|
"version": "==2.31.0.20240218"
|
||||||
},
|
},
|
||||||
"types-s3transfer": {
|
"types-s3transfer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1597,12 +1604,12 @@
|
||||||
},
|
},
|
||||||
"typing-extensions": {
|
"typing-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783",
|
"sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475",
|
||||||
"sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"
|
"sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==4.9.0"
|
"version": "==4.10.0"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
@ -66,6 +66,7 @@ services:
|
||||||
command: >
|
command: >
|
||||||
bash -c " python manage.py migrate &&
|
bash -c " python manage.py migrate &&
|
||||||
python manage.py load &&
|
python manage.py load &&
|
||||||
|
python manage.py createcachetable &&
|
||||||
python manage.py runserver 0.0.0.0:8080"
|
python manage.py runserver 0.0.0.0:8080"
|
||||||
|
|
||||||
db:
|
db:
|
||||||
|
|
|
@ -2,10 +2,6 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from time import sleep
|
|
||||||
from gevent import Timeout
|
|
||||||
from epplibwrapper.utility.pool_status import PoolStatus
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from epplib.client import Client
|
from epplib.client import Client
|
||||||
from epplib import commands
|
from epplib import commands
|
||||||
|
@ -18,8 +14,6 @@ from django.conf import settings
|
||||||
|
|
||||||
from .cert import Cert, Key
|
from .cert import Cert, Key
|
||||||
from .errors import ErrorCode, LoginError, RegistryError
|
from .errors import ErrorCode, LoginError, RegistryError
|
||||||
from .socket import Socket
|
|
||||||
from .utility.pool import EPPConnectionPool
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -43,8 +37,12 @@ class EPPLibWrapper:
|
||||||
ATTN: This should not be used directly. Use `Domain` from domain.py.
|
ATTN: This should not be used directly. Use `Domain` from domain.py.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, start_connection_pool=True) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialize settings which will be used for all connections."""
|
"""Initialize settings which will be used for all connections."""
|
||||||
|
# set _client to None initially. In the event that the __init__ fails
|
||||||
|
# before _client initializes, app should still start and be in a state
|
||||||
|
# that it can attempt _client initialization on send attempts
|
||||||
|
self._client = None # type: ignore
|
||||||
# prepare (but do not send) a Login command
|
# prepare (but do not send) a Login command
|
||||||
self._login = commands.Login(
|
self._login = commands.Login(
|
||||||
cl_id=settings.SECRET_REGISTRY_CL_ID,
|
cl_id=settings.SECRET_REGISTRY_CL_ID,
|
||||||
|
@ -54,9 +52,19 @@ class EPPLibWrapper:
|
||||||
"urn:ietf:params:xml:ns:contact-1.0",
|
"urn:ietf:params:xml:ns:contact-1.0",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
self._initialize_client()
|
||||||
|
except Exception:
|
||||||
|
logger.warning("Unable to configure epplib. Registrar cannot contact registry.")
|
||||||
|
|
||||||
|
def _initialize_client(self) -> None:
|
||||||
|
"""Initialize a client, assuming _login defined. Sets _client to initialized
|
||||||
|
client. Raises errors if initialization fails.
|
||||||
|
This method will be called at app initialization, and also during retries."""
|
||||||
# establish a client object with a TCP socket transport
|
# establish a client object with a TCP socket transport
|
||||||
self._client = Client(
|
# note that type: ignore added in several places because linter complains
|
||||||
|
# about _client initially being set to None, and None type doesn't match code
|
||||||
|
self._client = Client( # type: ignore
|
||||||
SocketTransport(
|
SocketTransport(
|
||||||
settings.SECRET_REGISTRY_HOSTNAME,
|
settings.SECRET_REGISTRY_HOSTNAME,
|
||||||
cert_file=CERT.filename,
|
cert_file=CERT.filename,
|
||||||
|
@ -64,176 +72,95 @@ class EPPLibWrapper:
|
||||||
password=settings.SECRET_REGISTRY_KEY_PASSPHRASE,
|
password=settings.SECRET_REGISTRY_KEY_PASSPHRASE,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
# use the _client object to connect
|
||||||
|
self._client.connect() # type: ignore
|
||||||
|
response = self._client.send(self._login) # type: ignore
|
||||||
|
if response.code >= 2000: # type: ignore
|
||||||
|
self._client.close() # type: ignore
|
||||||
|
raise LoginError(response.msg) # type: ignore
|
||||||
|
except TransportError as err:
|
||||||
|
message = "_initialize_client failed to execute due to a connection error."
|
||||||
|
logger.error(f"{message} Error: {err}")
|
||||||
|
raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err
|
||||||
|
except LoginError as err:
|
||||||
|
raise err
|
||||||
|
except Exception as err:
|
||||||
|
message = "_initialize_client failed to execute due to an unknown error."
|
||||||
|
logger.error(f"{message} Error: {err}")
|
||||||
|
raise RegistryError(message) from err
|
||||||
|
|
||||||
self.pool_options = {
|
def _disconnect(self) -> None:
|
||||||
# Pool size
|
"""Close the connection."""
|
||||||
"size": settings.EPP_CONNECTION_POOL_SIZE,
|
try:
|
||||||
# Which errors the pool should look out for.
|
self._client.send(commands.Logout()) # type: ignore
|
||||||
# Avoid changing this unless necessary,
|
self._client.close() # type: ignore
|
||||||
# it can and will break things.
|
except Exception:
|
||||||
"exc_classes": (TransportError,),
|
logger.warning("Connection to registry was not cleanly closed.")
|
||||||
# Occasionally pings the registry to keep the connection alive.
|
|
||||||
# Value in seconds => (keepalive / size)
|
|
||||||
"keepalive": settings.POOL_KEEP_ALIVE,
|
|
||||||
}
|
|
||||||
|
|
||||||
self._pool = None
|
|
||||||
|
|
||||||
# Tracks the status of the pool
|
|
||||||
self.pool_status = PoolStatus()
|
|
||||||
|
|
||||||
if start_connection_pool:
|
|
||||||
self.start_connection_pool()
|
|
||||||
|
|
||||||
def _send(self, command):
|
def _send(self, command):
|
||||||
"""Helper function used by `send`."""
|
"""Helper function used by `send`."""
|
||||||
cmd_type = command.__class__.__name__
|
cmd_type = command.__class__.__name__
|
||||||
|
|
||||||
# Start a timeout to check if the pool is hanging
|
|
||||||
timeout = Timeout(settings.POOL_TIMEOUT)
|
|
||||||
timeout.start()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self.pool_status.connection_success:
|
# check for the condition that the _client was not initialized properly
|
||||||
raise LoginError("Couldn't connect to the registry after three attempts")
|
# at app initialization
|
||||||
with self._pool.get() as connection:
|
if self._client is None:
|
||||||
response = connection.send(command)
|
self._initialize_client()
|
||||||
except Timeout as t:
|
response = self._client.send(command)
|
||||||
# If more than one pool exists,
|
|
||||||
# multiple timeouts can be floating around.
|
|
||||||
# We need to be specific as to which we are targeting.
|
|
||||||
if t is timeout:
|
|
||||||
# Flag that the pool is frozen,
|
|
||||||
# then restart the pool.
|
|
||||||
self.pool_status.pool_hanging = True
|
|
||||||
logger.error("Pool timed out")
|
|
||||||
self.start_connection_pool()
|
|
||||||
except (ValueError, ParsingError) as err:
|
except (ValueError, ParsingError) as err:
|
||||||
message = f"{cmd_type} failed to execute due to some syntax error."
|
message = f"{cmd_type} failed to execute due to some syntax error."
|
||||||
logger.error(f"{message} Error: {err}", exc_info=True)
|
logger.error(f"{message} Error: {err}")
|
||||||
raise RegistryError(message) from err
|
raise RegistryError(message) from err
|
||||||
except TransportError as err:
|
except TransportError as err:
|
||||||
message = f"{cmd_type} failed to execute due to a connection error."
|
message = f"{cmd_type} failed to execute due to a connection error."
|
||||||
logger.error(f"{message} Error: {err}", exc_info=True)
|
logger.error(f"{message} Error: {err}")
|
||||||
raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err
|
raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err
|
||||||
except LoginError as err:
|
except LoginError as err:
|
||||||
# For linter due to it not liking this line length
|
# For linter due to it not liking this line length
|
||||||
text = "failed to execute due to a registry login error."
|
text = "failed to execute due to a registry login error."
|
||||||
message = f"{cmd_type} {text}"
|
message = f"{cmd_type} {text}"
|
||||||
logger.error(f"{message} Error: {err}", exc_info=True)
|
logger.error(f"{message} Error: {err}")
|
||||||
raise RegistryError(message) from err
|
raise RegistryError(message) from err
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
message = f"{cmd_type} failed to execute due to an unknown error."
|
message = f"{cmd_type} failed to execute due to an unknown error."
|
||||||
logger.error(f"{message} Error: {err}", exc_info=True)
|
logger.error(f"{message} Error: {err}")
|
||||||
raise RegistryError(message) from err
|
raise RegistryError(message) from err
|
||||||
else:
|
else:
|
||||||
if response.code >= 2000:
|
if response.code >= 2000:
|
||||||
raise RegistryError(response.msg, code=response.code)
|
raise RegistryError(response.msg, code=response.code)
|
||||||
else:
|
else:
|
||||||
return response
|
return response
|
||||||
finally:
|
|
||||||
# Close the timeout no matter what happens
|
def _retry(self, command):
|
||||||
timeout.close()
|
"""Retry sending a command through EPP by re-initializing the client
|
||||||
|
and then sending the command."""
|
||||||
|
# re-initialize by disconnecting and initial
|
||||||
|
self._disconnect()
|
||||||
|
self._initialize_client()
|
||||||
|
return self._send(command)
|
||||||
|
|
||||||
def send(self, command, *, cleaned=False):
|
def send(self, command, *, cleaned=False):
|
||||||
"""Login, send the command, then close the connection. Tries 3 times."""
|
"""Login, the send the command. Retry once if an error is found"""
|
||||||
# try to prevent use of this method without appropriate safeguards
|
# try to prevent use of this method without appropriate safeguards
|
||||||
|
cmd_type = command.__class__.__name__
|
||||||
if not cleaned:
|
if not cleaned:
|
||||||
raise ValueError("Please sanitize user input before sending it.")
|
raise ValueError("Please sanitize user input before sending it.")
|
||||||
|
|
||||||
# Reopen the pool if its closed
|
|
||||||
# Only occurs when a login error is raised, after connection is successful
|
|
||||||
if not self.pool_status.pool_running:
|
|
||||||
# We want to reopen the connection pool,
|
|
||||||
# but we don't want the end user to wait while it opens.
|
|
||||||
# Raise syntax doesn't allow this, so we use a try/catch
|
|
||||||
# block.
|
|
||||||
try:
|
|
||||||
logger.error("Can't contact the Registry. Pool was not running.")
|
|
||||||
raise RegistryError("Can't contact the Registry. Pool was not running.")
|
|
||||||
except RegistryError as err:
|
|
||||||
raise err
|
|
||||||
finally:
|
|
||||||
# Code execution will halt after here.
|
|
||||||
# The end user will need to recall .send.
|
|
||||||
self.start_connection_pool()
|
|
||||||
|
|
||||||
counter = 0 # we'll try 3 times
|
|
||||||
while True:
|
|
||||||
try:
|
try:
|
||||||
return self._send(command)
|
return self._send(command)
|
||||||
except RegistryError as err:
|
except RegistryError as err:
|
||||||
if counter < 3 and (err.should_retry() or err.is_transport_error()):
|
if (
|
||||||
logger.info(f"Retrying transport error. Attempt #{counter+1} of 3.")
|
err.is_transport_error()
|
||||||
counter += 1
|
or err.is_connection_error()
|
||||||
sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms
|
or err.is_session_error()
|
||||||
else: # don't try again
|
or err.is_server_error()
|
||||||
raise err
|
or err.should_retry()
|
||||||
|
):
|
||||||
def get_pool(self):
|
message = f"{cmd_type} failed and will be retried"
|
||||||
"""Get the current pool instance"""
|
logger.info(f"{message} Error: {err}")
|
||||||
return self._pool
|
return self._retry(command)
|
||||||
|
|
||||||
def _create_pool(self, client, login, options):
|
|
||||||
"""Creates and returns new pool instance"""
|
|
||||||
logger.info("New pool was created")
|
|
||||||
return EPPConnectionPool(client, login, options)
|
|
||||||
|
|
||||||
def start_connection_pool(self, restart_pool_if_exists=True):
|
|
||||||
"""Starts a connection pool for the registry.
|
|
||||||
|
|
||||||
restart_pool_if_exists -> bool:
|
|
||||||
If an instance of the pool already exists,
|
|
||||||
then then that instance will be killed first.
|
|
||||||
It is generally recommended to keep this enabled.
|
|
||||||
"""
|
|
||||||
# Since we reuse the same creds for each pool, we can test on
|
|
||||||
# one socket, and if successful, then we know we can connect.
|
|
||||||
if not self._test_registry_connection_success():
|
|
||||||
logger.warning("start_connection_pool() -> Cannot contact the Registry")
|
|
||||||
self.pool_status.connection_success = False
|
|
||||||
else:
|
else:
|
||||||
self.pool_status.connection_success = True
|
raise err
|
||||||
|
|
||||||
# If this function is reinvoked, then ensure
|
|
||||||
# that we don't have duplicate data sitting around.
|
|
||||||
if self._pool is not None and restart_pool_if_exists:
|
|
||||||
logger.info("Connection pool restarting...")
|
|
||||||
self.kill_pool()
|
|
||||||
logger.info("Old pool killed")
|
|
||||||
|
|
||||||
self._pool = self._create_pool(self._client, self._login, self.pool_options)
|
|
||||||
|
|
||||||
self.pool_status.pool_running = True
|
|
||||||
self.pool_status.pool_hanging = False
|
|
||||||
|
|
||||||
logger.info("Connection pool started")
|
|
||||||
|
|
||||||
def kill_pool(self):
|
|
||||||
"""Kills the existing pool. Use this instead
|
|
||||||
of self._pool = None, as that doesn't clear
|
|
||||||
gevent instances."""
|
|
||||||
if self._pool is not None:
|
|
||||||
self._pool.kill_all_connections()
|
|
||||||
self._pool = None
|
|
||||||
self.pool_status.pool_running = False
|
|
||||||
return None
|
|
||||||
logger.info("kill_pool() was invoked but there was no pool to delete")
|
|
||||||
|
|
||||||
def _test_registry_connection_success(self):
|
|
||||||
"""Check that determines if our login
|
|
||||||
credentials are valid, and/or if the Registrar
|
|
||||||
can be contacted
|
|
||||||
"""
|
|
||||||
# This is closed in test_connection_success
|
|
||||||
socket = Socket(self._client, self._login)
|
|
||||||
can_login = False
|
|
||||||
|
|
||||||
# Something went wrong if this doesn't exist
|
|
||||||
if hasattr(socket, "test_connection_success"):
|
|
||||||
can_login = socket.test_connection_success()
|
|
||||||
|
|
||||||
return can_login
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -241,5 +168,4 @@ try:
|
||||||
CLIENT = EPPLibWrapper()
|
CLIENT = EPPLibWrapper()
|
||||||
logger.info("registry client initialized")
|
logger.info("registry client initialized")
|
||||||
except Exception:
|
except Exception:
|
||||||
CLIENT = None # type: ignore
|
logger.warning("Unable to configure epplib. Registrar cannot contact registry.")
|
||||||
logger.warning("Unable to configure epplib. Registrar cannot contact registry.", exc_info=True)
|
|
||||||
|
|
|
@ -1,102 +0,0 @@
|
||||||
import logging
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
try:
|
|
||||||
from epplib import commands
|
|
||||||
from epplib.client import Client
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from .errors import LoginError
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Socket:
|
|
||||||
"""Context manager which establishes a TCP connection with registry."""
|
|
||||||
|
|
||||||
def __init__(self, client: Client, login: commands.Login) -> None:
|
|
||||||
"""Save the epplib client and login details."""
|
|
||||||
self.client = client
|
|
||||||
self.login = login
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
"""Runs connect(), which opens a connection with EPPLib."""
|
|
||||||
self.connect()
|
|
||||||
|
|
||||||
def __exit__(self, *args, **kwargs):
|
|
||||||
"""Runs disconnect(), which closes a connection with EPPLib."""
|
|
||||||
self.disconnect()
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
"""Use epplib to connect."""
|
|
||||||
logger.info("Opening socket on connection pool")
|
|
||||||
self.client.connect()
|
|
||||||
response = self.client.send(self.login)
|
|
||||||
if self.is_login_error(response.code):
|
|
||||||
self.client.close()
|
|
||||||
raise LoginError(response.msg)
|
|
||||||
return self.client
|
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
"""Close the connection."""
|
|
||||||
logger.info("Closing socket on connection pool")
|
|
||||||
try:
|
|
||||||
self.client.send(commands.Logout())
|
|
||||||
self.client.close()
|
|
||||||
except Exception as err:
|
|
||||||
logger.warning("Connection to registry was not cleanly closed.")
|
|
||||||
logger.error(err)
|
|
||||||
|
|
||||||
def send(self, command):
|
|
||||||
"""Sends a command to the registry.
|
|
||||||
If the RegistryError code is >= 2000,
|
|
||||||
then this function raises a LoginError.
|
|
||||||
The calling function should handle this."""
|
|
||||||
response = self.client.send(command)
|
|
||||||
if self.is_login_error(response.code):
|
|
||||||
self.client.close()
|
|
||||||
raise LoginError(response.msg)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def is_login_error(self, code):
|
|
||||||
"""Returns the result of code >= 2000 for RegistryError.
|
|
||||||
This indicates that something weird happened on the Registry,
|
|
||||||
and that we should return a LoginError."""
|
|
||||||
return code >= 2000
|
|
||||||
|
|
||||||
def test_connection_success(self):
|
|
||||||
"""Tests if a successful connection can be made with the registry.
|
|
||||||
Tries 3 times."""
|
|
||||||
# Something went wrong if this doesn't exist
|
|
||||||
if not hasattr(self.client, "connect"):
|
|
||||||
logger.warning("self.client does not have a connect attribute")
|
|
||||||
return False
|
|
||||||
|
|
||||||
counter = 0 # we'll try 3 times
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
self.client.connect()
|
|
||||||
response = self.client.send(self.login)
|
|
||||||
except (LoginError, OSError) as err:
|
|
||||||
logger.error(err)
|
|
||||||
should_retry = True
|
|
||||||
if isinstance(err, LoginError):
|
|
||||||
should_retry = err.should_retry()
|
|
||||||
if should_retry and counter < 3:
|
|
||||||
counter += 1
|
|
||||||
sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms
|
|
||||||
else: # don't try again
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# If we encounter a login error, fail
|
|
||||||
if self.is_login_error(response.code):
|
|
||||||
logger.warning("A login error was found in test_connection_success")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Otherwise, just return true
|
|
||||||
return True
|
|
||||||
finally:
|
|
||||||
self.disconnect()
|
|
257
src/epplibwrapper/tests/test_client.py
Normal file
257
src/epplibwrapper/tests/test_client.py
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
from django.test import TestCase
|
||||||
|
from epplibwrapper.client import EPPLibWrapper
|
||||||
|
from epplibwrapper.errors import RegistryError, LoginError
|
||||||
|
from .common import less_console_noise
|
||||||
|
import logging
|
||||||
|
|
||||||
|
try:
|
||||||
|
from epplib.exceptions import TransportError
|
||||||
|
from epplib.responses import Result
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TestClient(TestCase):
|
||||||
|
"""Test the EPPlibwrapper client"""
|
||||||
|
|
||||||
|
def fake_result(self, code, msg):
|
||||||
|
"""Helper function to create a fake Result object"""
|
||||||
|
return Result(code=code, msg=msg, res_data=[], cl_tr_id="cl_tr_id", sv_tr_id="sv_tr_id")
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_initialize_client_success(self, mock_client):
|
||||||
|
"""Test when the initialize_client is successful"""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
mock_connect = MagicMock()
|
||||||
|
# Create a mock Result instance
|
||||||
|
mock_result = MagicMock(spec=Result)
|
||||||
|
mock_result.code = 200
|
||||||
|
mock_result.msg = "Success"
|
||||||
|
mock_result.res_data = ["data1", "data2"]
|
||||||
|
mock_result.cl_tr_id = "client_id"
|
||||||
|
mock_result.sv_tr_id = "server_id"
|
||||||
|
mock_send = MagicMock(return_value=mock_result)
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
|
||||||
|
# Create EPPLibWrapper instance and initialize client
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
|
||||||
|
# Assert that connect method is called once
|
||||||
|
mock_connect.assert_called_once()
|
||||||
|
# Assert that _client is not None after initialization
|
||||||
|
self.assertIsNotNone(wrapper._client)
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_initialize_client_transport_error(self, mock_client):
|
||||||
|
"""Test when the send(login) step of initialize_client raises a TransportError."""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
mock_connect = MagicMock()
|
||||||
|
mock_send = MagicMock(side_effect=TransportError("Transport error"))
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
|
||||||
|
with self.assertRaises(RegistryError):
|
||||||
|
# Create EPPLibWrapper instance and initialize client
|
||||||
|
# if functioning as expected, initial __init__ should except
|
||||||
|
# and log any Exception raised
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
# so call _initialize_client a second time directly to test
|
||||||
|
# the raised exception
|
||||||
|
wrapper._initialize_client()
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_initialize_client_login_error(self, mock_client):
|
||||||
|
"""Test when the send(login) step of initialize_client returns (2400) comamnd failed code."""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
mock_connect = MagicMock()
|
||||||
|
# Create a mock Result instance
|
||||||
|
mock_result = MagicMock(spec=Result)
|
||||||
|
mock_result.code = 2400
|
||||||
|
mock_result.msg = "Login failed"
|
||||||
|
mock_result.res_data = ["data1", "data2"]
|
||||||
|
mock_result.cl_tr_id = "client_id"
|
||||||
|
mock_result.sv_tr_id = "server_id"
|
||||||
|
mock_send = MagicMock(return_value=mock_result)
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
|
||||||
|
with self.assertRaises(LoginError):
|
||||||
|
# Create EPPLibWrapper instance and initialize client
|
||||||
|
# if functioning as expected, initial __init__ should except
|
||||||
|
# and log any Exception raised
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
# so call _initialize_client a second time directly to test
|
||||||
|
# the raised exception
|
||||||
|
wrapper._initialize_client()
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_initialize_client_unknown_exception(self, mock_client):
|
||||||
|
"""Test when the send(login) step of initialize_client raises an unexpected Exception."""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
mock_connect = MagicMock()
|
||||||
|
mock_send = MagicMock(side_effect=Exception("Unknown exception"))
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
|
||||||
|
with self.assertRaises(RegistryError):
|
||||||
|
# Create EPPLibWrapper instance and initialize client
|
||||||
|
# if functioning as expected, initial __init__ should except
|
||||||
|
# and log any Exception raised
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
# so call _initialize_client a second time directly to test
|
||||||
|
# the raised exception
|
||||||
|
wrapper._initialize_client()
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_initialize_client_fails_recovers_with_send_command(self, mock_client):
|
||||||
|
"""Test when the initialize_client fails on the connect() step. And then a subsequent
|
||||||
|
call to send() should recover and re-initialize the client and properly return
|
||||||
|
the successful send command.
|
||||||
|
Flow:
|
||||||
|
Initialization step fails at app init
|
||||||
|
Send command fails (with 2400 code) prompting retry
|
||||||
|
Client closes and re-initializes, and command is sent successfully"""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
# close() should return successfully
|
||||||
|
mock_close = MagicMock()
|
||||||
|
mock_client.return_value.close = mock_close
|
||||||
|
# Create success and failure results
|
||||||
|
command_success_result = self.fake_result(1000, "Command completed successfully")
|
||||||
|
command_failure_result = self.fake_result(2400, "Command failed")
|
||||||
|
# side_effect for the connect() calls
|
||||||
|
# first connect() should raise an Exception
|
||||||
|
# subsequent connect() calls should return success
|
||||||
|
connect_call_count = 0
|
||||||
|
|
||||||
|
def connect_side_effect(*args, **kwargs):
|
||||||
|
nonlocal connect_call_count
|
||||||
|
connect_call_count += 1
|
||||||
|
if connect_call_count == 1:
|
||||||
|
raise Exception("Connection failed")
|
||||||
|
else:
|
||||||
|
return command_success_result
|
||||||
|
|
||||||
|
mock_connect = MagicMock(side_effect=connect_side_effect)
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
# side_effect for the send() calls
|
||||||
|
# first send will be the send("InfoDomainCommand") and should fail
|
||||||
|
# subsequend send() calls should return success
|
||||||
|
send_call_count = 0
|
||||||
|
|
||||||
|
def send_side_effect(*args, **kwargs):
|
||||||
|
nonlocal send_call_count
|
||||||
|
send_call_count += 1
|
||||||
|
if send_call_count == 1:
|
||||||
|
return command_failure_result
|
||||||
|
else:
|
||||||
|
return command_success_result
|
||||||
|
|
||||||
|
mock_send = MagicMock(side_effect=send_side_effect)
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
# Create EPPLibWrapper instance and call send command
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
wrapper.send("InfoDomainCommand", cleaned=True)
|
||||||
|
# two connect() calls should be made, the initial failed connect()
|
||||||
|
# and the successful connect() during retry()
|
||||||
|
self.assertEquals(mock_connect.call_count, 2)
|
||||||
|
# close() should only be called once, during retry()
|
||||||
|
mock_close.assert_called_once()
|
||||||
|
# send called 4 times: failed send("InfoDomainCommand"), passed send(logout),
|
||||||
|
# passed send(login), passed send("InfoDomainCommand")
|
||||||
|
self.assertEquals(mock_send.call_count, 4)
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_send_command_failed_retries_and_fails_again(self, mock_client):
|
||||||
|
"""Test when the send("InfoDomainCommand) call fails with a 2400, prompting a retry
|
||||||
|
and the subsequent send("InfoDomainCommand) call also fails with a 2400, raise
|
||||||
|
a RegistryError
|
||||||
|
Flow:
|
||||||
|
Initialization succeeds
|
||||||
|
Send command fails (with 2400 code) prompting retry
|
||||||
|
Client closes and re-initializes, and command fails again with 2400"""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
# connect() and close() should succeed throughout
|
||||||
|
mock_connect = MagicMock()
|
||||||
|
mock_close = MagicMock()
|
||||||
|
# Create a mock Result instance
|
||||||
|
send_command_success_result = self.fake_result(1000, "Command completed successfully")
|
||||||
|
send_command_failure_result = self.fake_result(2400, "Command failed")
|
||||||
|
|
||||||
|
# side_effect for send command, passes for all other sends (login, logout), but
|
||||||
|
# fails for send("InfoDomainCommand")
|
||||||
|
def side_effect(*args, **kwargs):
|
||||||
|
if args[0] == "InfoDomainCommand":
|
||||||
|
return send_command_failure_result
|
||||||
|
else:
|
||||||
|
return send_command_success_result
|
||||||
|
|
||||||
|
mock_send = MagicMock(side_effect=side_effect)
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
mock_client.return_value.close = mock_close
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
|
||||||
|
with self.assertRaises(RegistryError):
|
||||||
|
# Create EPPLibWrapper instance and initialize client
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
# call send, which should throw a RegistryError (after retry)
|
||||||
|
wrapper.send("InfoDomainCommand", cleaned=True)
|
||||||
|
# connect() should be called twice, once during initialization, second time
|
||||||
|
# during retry
|
||||||
|
self.assertEquals(mock_connect.call_count, 2)
|
||||||
|
# close() is called once during retry
|
||||||
|
mock_close.assert_called_once()
|
||||||
|
# send() is called 5 times: send(login), send(command) fails, send(logout)
|
||||||
|
# send(login), send(command)
|
||||||
|
self.assertEquals(mock_send.call_count, 5)
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_send_command_failure_prompts_successful_retry(self, mock_client):
|
||||||
|
"""Test when the send("InfoDomainCommand) call fails with a 2400, prompting a retry
|
||||||
|
and the subsequent send("InfoDomainCommand) call succeeds
|
||||||
|
Flow:
|
||||||
|
Initialization succeeds
|
||||||
|
Send command fails (with 2400 code) prompting retry
|
||||||
|
Client closes and re-initializes, and command succeeds"""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
# connect() and close() should succeed throughout
|
||||||
|
mock_connect = MagicMock()
|
||||||
|
mock_close = MagicMock()
|
||||||
|
# create success and failure result messages
|
||||||
|
send_command_success_result = self.fake_result(1000, "Command completed successfully")
|
||||||
|
send_command_failure_result = self.fake_result(2400, "Command failed")
|
||||||
|
# side_effect for send call, initial send(login) succeeds during initialization, next send(command)
|
||||||
|
# fails, subsequent sends (logout, login, command) all succeed
|
||||||
|
send_call_count = 0
|
||||||
|
|
||||||
|
def side_effect(*args, **kwargs):
|
||||||
|
nonlocal send_call_count
|
||||||
|
send_call_count += 1
|
||||||
|
if send_call_count == 2:
|
||||||
|
return send_command_failure_result
|
||||||
|
else:
|
||||||
|
return send_command_success_result
|
||||||
|
|
||||||
|
mock_send = MagicMock(side_effect=side_effect)
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
mock_client.return_value.close = mock_close
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
# Create EPPLibWrapper instance and initialize client
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
wrapper.send("InfoDomainCommand", cleaned=True)
|
||||||
|
# connect() is called twice, once during initialization of app, once during retry
|
||||||
|
self.assertEquals(mock_connect.call_count, 2)
|
||||||
|
# close() is called once, during retry
|
||||||
|
mock_close.assert_called_once()
|
||||||
|
# send() is called 5 times: send(login), send(command) fail, send(logout), send(login), send(command)
|
||||||
|
self.assertEquals(mock_send.call_count, 5)
|
|
@ -1,262 +0,0 @@
|
||||||
import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
from dateutil.tz import tzlocal # type: ignore
|
|
||||||
from django.test import TestCase
|
|
||||||
from epplibwrapper.client import EPPLibWrapper
|
|
||||||
from epplibwrapper.errors import RegistryError
|
|
||||||
from epplibwrapper.socket import Socket
|
|
||||||
from epplibwrapper.utility.pool import EPPConnectionPool
|
|
||||||
from registrar.models.domain import registry
|
|
||||||
from contextlib import ExitStack
|
|
||||||
from .common import less_console_noise
|
|
||||||
import logging
|
|
||||||
|
|
||||||
try:
|
|
||||||
from epplib import commands
|
|
||||||
from epplib.client import Client
|
|
||||||
from epplib.exceptions import TransportError
|
|
||||||
from epplib.transport import SocketTransport
|
|
||||||
from epplib.models import common, info
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class TestConnectionPool(TestCase):
|
|
||||||
"""Tests for our connection pooling behaviour"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
# Mimic the settings added to settings.py
|
|
||||||
self.pool_options = {
|
|
||||||
# Current pool size
|
|
||||||
"size": 1,
|
|
||||||
# Which errors the pool should look out for
|
|
||||||
"exc_classes": (TransportError,),
|
|
||||||
# Occasionally pings the registry to keep the connection alive.
|
|
||||||
# Value in seconds => (keepalive / size)
|
|
||||||
"keepalive": 60,
|
|
||||||
}
|
|
||||||
|
|
||||||
def fake_socket(self, login, client):
|
|
||||||
# Linter reasons
|
|
||||||
pw = "none"
|
|
||||||
# Create a fake client object
|
|
||||||
fake_client = Client(
|
|
||||||
SocketTransport(
|
|
||||||
"none",
|
|
||||||
cert_file="path/to/cert_file",
|
|
||||||
key_file="path/to/key_file",
|
|
||||||
password=pw,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return Socket(fake_client, MagicMock())
|
|
||||||
|
|
||||||
def patch_success(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def fake_send(self, command, cleaned=None):
|
|
||||||
mock = MagicMock(
|
|
||||||
code=1000,
|
|
||||||
msg="Command completed successfully",
|
|
||||||
res_data=None,
|
|
||||||
cl_tr_id="xkw1uo#2023-10-17T15:29:09.559376",
|
|
||||||
sv_tr_id="5CcH4gxISuGkq8eqvr1UyQ==-35a",
|
|
||||||
extensions=[],
|
|
||||||
msg_q=None,
|
|
||||||
)
|
|
||||||
return mock
|
|
||||||
|
|
||||||
def fake_client(mock_client):
|
|
||||||
pw = "none"
|
|
||||||
client = Client(
|
|
||||||
SocketTransport(
|
|
||||||
"none",
|
|
||||||
cert_file="path/to/cert_file",
|
|
||||||
key_file="path/to/key_file",
|
|
||||||
password=pw,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return client
|
|
||||||
|
|
||||||
@patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success)
|
|
||||||
def test_pool_sends_data(self):
|
|
||||||
"""A .send is invoked on the pool successfully"""
|
|
||||||
expected_result = {
|
|
||||||
"cl_tr_id": None,
|
|
||||||
"code": 1000,
|
|
||||||
"extensions": [],
|
|
||||||
"msg": "Command completed successfully",
|
|
||||||
"msg_q": None,
|
|
||||||
"res_data": [
|
|
||||||
info.InfoDomainResultData(
|
|
||||||
roid="DF1340360-GOV",
|
|
||||||
statuses=[
|
|
||||||
common.Status(
|
|
||||||
state="serverTransferProhibited",
|
|
||||||
description=None,
|
|
||||||
lang="en",
|
|
||||||
),
|
|
||||||
common.Status(state="inactive", description=None, lang="en"),
|
|
||||||
],
|
|
||||||
cl_id="gov2023-ote",
|
|
||||||
cr_id="gov2023-ote",
|
|
||||||
cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()),
|
|
||||||
up_id="gov2023-ote",
|
|
||||||
up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()),
|
|
||||||
tr_date=None,
|
|
||||||
name="test3.gov",
|
|
||||||
registrant="TuaWnx9hnm84GCSU",
|
|
||||||
admins=[],
|
|
||||||
nsset=None,
|
|
||||||
keyset=None,
|
|
||||||
ex_date=datetime.date(2024, 8, 15),
|
|
||||||
auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
"sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Mock a response from EPP
|
|
||||||
def fake_receive(command, cleaned=None):
|
|
||||||
location = Path(__file__).parent / "utility" / "infoDomain.xml"
|
|
||||||
xml = (location).read_bytes()
|
|
||||||
return xml
|
|
||||||
|
|
||||||
def do_nothing(command):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Mock what happens inside the "with"
|
|
||||||
with ExitStack() as stack:
|
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket))
|
|
||||||
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing))
|
|
||||||
stack.enter_context(patch.object(SocketTransport, "send", self.fake_send))
|
|
||||||
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
|
||||||
with less_console_noise():
|
|
||||||
# Restart the connection pool
|
|
||||||
registry.start_connection_pool()
|
|
||||||
# Pool should be running, and be the right size
|
|
||||||
self.assertEqual(registry.pool_status.connection_success, True)
|
|
||||||
self.assertEqual(registry.pool_status.pool_running, True)
|
|
||||||
|
|
||||||
# Send a command
|
|
||||||
result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True)
|
|
||||||
|
|
||||||
# Should this ever fail, it either means that the schema has changed,
|
|
||||||
# or the pool is broken.
|
|
||||||
# If the schema has changed: Update the associated infoDomain.xml file
|
|
||||||
self.assertEqual(result.__dict__, expected_result)
|
|
||||||
|
|
||||||
# The number of open pools should match the number of requested ones.
|
|
||||||
# If it is 0, then they failed to open
|
|
||||||
self.assertEqual(len(registry._pool.conn), self.pool_options["size"])
|
|
||||||
# Kill the connection pool
|
|
||||||
registry.kill_pool()
|
|
||||||
|
|
||||||
@patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success)
|
|
||||||
def test_pool_restarts_on_send(self):
|
|
||||||
"""A .send is invoked, but the pool isn't running.
|
|
||||||
The pool should restart."""
|
|
||||||
expected_result = {
|
|
||||||
"cl_tr_id": None,
|
|
||||||
"code": 1000,
|
|
||||||
"extensions": [],
|
|
||||||
"msg": "Command completed successfully",
|
|
||||||
"msg_q": None,
|
|
||||||
"res_data": [
|
|
||||||
info.InfoDomainResultData(
|
|
||||||
roid="DF1340360-GOV",
|
|
||||||
statuses=[
|
|
||||||
common.Status(
|
|
||||||
state="serverTransferProhibited",
|
|
||||||
description=None,
|
|
||||||
lang="en",
|
|
||||||
),
|
|
||||||
common.Status(state="inactive", description=None, lang="en"),
|
|
||||||
],
|
|
||||||
cl_id="gov2023-ote",
|
|
||||||
cr_id="gov2023-ote",
|
|
||||||
cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()),
|
|
||||||
up_id="gov2023-ote",
|
|
||||||
up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()),
|
|
||||||
tr_date=None,
|
|
||||||
name="test3.gov",
|
|
||||||
registrant="TuaWnx9hnm84GCSU",
|
|
||||||
admins=[],
|
|
||||||
nsset=None,
|
|
||||||
keyset=None,
|
|
||||||
ex_date=datetime.date(2024, 8, 15),
|
|
||||||
auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
"sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Mock a response from EPP
|
|
||||||
def fake_receive(command, cleaned=None):
|
|
||||||
location = Path(__file__).parent / "utility" / "infoDomain.xml"
|
|
||||||
xml = (location).read_bytes()
|
|
||||||
return xml
|
|
||||||
|
|
||||||
def do_nothing(command):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Mock what happens inside the "with"
|
|
||||||
with ExitStack() as stack:
|
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket))
|
|
||||||
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing))
|
|
||||||
stack.enter_context(patch.object(SocketTransport, "send", self.fake_send))
|
|
||||||
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
|
||||||
with less_console_noise():
|
|
||||||
# Start the connection pool
|
|
||||||
registry.start_connection_pool()
|
|
||||||
# Kill the connection pool
|
|
||||||
registry.kill_pool()
|
|
||||||
|
|
||||||
self.assertEqual(registry.pool_status.pool_running, False)
|
|
||||||
|
|
||||||
# An exception should be raised as end user will be informed
|
|
||||||
# that they cannot connect to EPP
|
|
||||||
with self.assertRaises(RegistryError):
|
|
||||||
expected = "InfoDomain failed to execute due to a connection error."
|
|
||||||
result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True)
|
|
||||||
self.assertEqual(result, expected)
|
|
||||||
|
|
||||||
# A subsequent command should be successful, as the pool restarts
|
|
||||||
result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True)
|
|
||||||
# Should this ever fail, it either means that the schema has changed,
|
|
||||||
# or the pool is broken.
|
|
||||||
# If the schema has changed: Update the associated infoDomain.xml file
|
|
||||||
self.assertEqual(result.__dict__, expected_result)
|
|
||||||
|
|
||||||
# The number of open pools should match the number of requested ones.
|
|
||||||
# If it is 0, then they failed to open
|
|
||||||
self.assertEqual(len(registry._pool.conn), self.pool_options["size"])
|
|
||||||
# Kill the connection pool
|
|
||||||
registry.kill_pool()
|
|
||||||
|
|
||||||
@patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success)
|
|
||||||
def test_raises_connection_error(self):
|
|
||||||
"""A .send is invoked on the pool, but registry connection is lost
|
|
||||||
right as we send a command."""
|
|
||||||
|
|
||||||
with ExitStack() as stack:
|
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket))
|
|
||||||
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
|
||||||
with less_console_noise():
|
|
||||||
# Start the connection pool
|
|
||||||
registry.start_connection_pool()
|
|
||||||
|
|
||||||
# Pool should be running
|
|
||||||
self.assertEqual(registry.pool_status.connection_success, True)
|
|
||||||
self.assertEqual(registry.pool_status.pool_running, True)
|
|
||||||
|
|
||||||
# Try to send a command out - should fail
|
|
||||||
with self.assertRaises(RegistryError):
|
|
||||||
expected = "InfoDomain failed to execute due to a connection error."
|
|
||||||
result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True)
|
|
||||||
self.assertEqual(result, expected)
|
|
|
@ -1,151 +0,0 @@
|
||||||
import logging
|
|
||||||
from typing import List
|
|
||||||
import gevent
|
|
||||||
from geventconnpool import ConnectionPool
|
|
||||||
from epplibwrapper.socket import Socket
|
|
||||||
from epplibwrapper.utility.pool_error import PoolError, PoolErrorCodes
|
|
||||||
|
|
||||||
try:
|
|
||||||
from epplib.commands import Hello
|
|
||||||
from epplib.exceptions import TransportError
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from gevent.lock import BoundedSemaphore
|
|
||||||
from collections import deque
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class EPPConnectionPool(ConnectionPool):
|
|
||||||
"""A connection pool for EPPLib.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
client (Client): The client
|
|
||||||
login (commands.Login): Login creds
|
|
||||||
options (dict): Options for the ConnectionPool
|
|
||||||
base class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, client, login, options: dict):
|
|
||||||
# For storing shared credentials
|
|
||||||
self._client = client
|
|
||||||
self._login = login
|
|
||||||
|
|
||||||
# Keep track of each greenlet
|
|
||||||
self.greenlets: List[gevent.Greenlet] = []
|
|
||||||
|
|
||||||
# Define optional pool settings.
|
|
||||||
# Kept in a dict so that the parent class,
|
|
||||||
# client.py, can maintain seperation/expandability
|
|
||||||
self.size = 1
|
|
||||||
if "size" in options:
|
|
||||||
self.size = options["size"]
|
|
||||||
|
|
||||||
self.exc_classes = tuple((TransportError,))
|
|
||||||
if "exc_classes" in options:
|
|
||||||
self.exc_classes = options["exc_classes"]
|
|
||||||
|
|
||||||
self.keepalive = None
|
|
||||||
if "keepalive" in options:
|
|
||||||
self.keepalive = options["keepalive"]
|
|
||||||
|
|
||||||
# Determines the period in which new
|
|
||||||
# gevent threads are spun up.
|
|
||||||
# This time period is in seconds. So for instance, .1 would be .1 seconds.
|
|
||||||
self.spawn_frequency = 0.1
|
|
||||||
if "spawn_frequency" in options:
|
|
||||||
self.spawn_frequency = options["spawn_frequency"]
|
|
||||||
|
|
||||||
self.conn: deque = deque()
|
|
||||||
self.lock = BoundedSemaphore(self.size)
|
|
||||||
|
|
||||||
self.populate_all_connections()
|
|
||||||
|
|
||||||
def _new_connection(self):
|
|
||||||
socket = self._create_socket(self._client, self._login)
|
|
||||||
try:
|
|
||||||
connection = socket.connect()
|
|
||||||
return connection
|
|
||||||
except Exception as err:
|
|
||||||
message = f"Failed to execute due to a registry error: {err}"
|
|
||||||
logger.error(message, exc_info=True)
|
|
||||||
# We want to raise a pool error rather than a LoginError here
|
|
||||||
# because if this occurs internally, we should handle this
|
|
||||||
# differently than we otherwise would for LoginError.
|
|
||||||
raise PoolError(code=PoolErrorCodes.NEW_CONNECTION_FAILED) from err
|
|
||||||
|
|
||||||
def _keepalive(self, c):
|
|
||||||
"""Sends a command to the server to keep the connection alive."""
|
|
||||||
try:
|
|
||||||
# Sends a ping to the registry via EPPLib
|
|
||||||
c.send(Hello())
|
|
||||||
except Exception as err:
|
|
||||||
message = "Failed to keep the connection alive."
|
|
||||||
logger.error(message, exc_info=True)
|
|
||||||
raise PoolError(code=PoolErrorCodes.KEEP_ALIVE_FAILED) from err
|
|
||||||
|
|
||||||
def _keepalive_periodic(self):
|
|
||||||
"""Overriding _keepalive_periodic from geventconnpool so that PoolErrors
|
|
||||||
are properly handled, as opposed to printing to stdout"""
|
|
||||||
delay = float(self.keepalive) / self.size
|
|
||||||
while 1:
|
|
||||||
try:
|
|
||||||
with self.get() as c:
|
|
||||||
self._keepalive(c)
|
|
||||||
except PoolError as err:
|
|
||||||
logger.error(err.message, exc_info=True)
|
|
||||||
except self.exc_classes:
|
|
||||||
# Nothing to do, the pool will generate a new connection later
|
|
||||||
pass
|
|
||||||
gevent.sleep(delay)
|
|
||||||
|
|
||||||
def _create_socket(self, client, login) -> Socket:
|
|
||||||
"""Creates and returns a socket instance"""
|
|
||||||
socket = Socket(client, login)
|
|
||||||
return socket
|
|
||||||
|
|
||||||
def get_connections(self):
|
|
||||||
"""Returns the connection queue"""
|
|
||||||
return self.conn
|
|
||||||
|
|
||||||
def kill_all_connections(self):
|
|
||||||
"""Kills all active connections in the pool."""
|
|
||||||
try:
|
|
||||||
if len(self.conn) > 0 or len(self.greenlets) > 0:
|
|
||||||
logger.info("Attempting to kill connections")
|
|
||||||
gevent.killall(self.greenlets)
|
|
||||||
|
|
||||||
self.greenlets.clear()
|
|
||||||
for connection in self.conn:
|
|
||||||
connection.disconnect()
|
|
||||||
self.conn.clear()
|
|
||||||
|
|
||||||
# Clear the semaphore
|
|
||||||
self.lock = BoundedSemaphore(self.size)
|
|
||||||
logger.info("Finished killing connections")
|
|
||||||
else:
|
|
||||||
logger.info("No connections to kill.")
|
|
||||||
except Exception as err:
|
|
||||||
logger.error("Could not kill all connections.")
|
|
||||||
raise PoolError(code=PoolErrorCodes.KILL_ALL_FAILED) from err
|
|
||||||
|
|
||||||
def populate_all_connections(self):
|
|
||||||
"""Generates the connection pool.
|
|
||||||
If any connections exist, kill them first.
|
|
||||||
Based off of the __init__ definition for geventconnpool.
|
|
||||||
"""
|
|
||||||
if len(self.conn) > 0 or len(self.greenlets) > 0:
|
|
||||||
self.kill_all_connections()
|
|
||||||
|
|
||||||
# Setup the lock
|
|
||||||
for i in range(self.size):
|
|
||||||
self.lock.acquire()
|
|
||||||
|
|
||||||
# Open multiple connections
|
|
||||||
for i in range(self.size):
|
|
||||||
self.greenlets.append(gevent.spawn_later(self.spawn_frequency * i, self._addOne))
|
|
||||||
|
|
||||||
# Open a "keepalive" thread if we want to ping open connections
|
|
||||||
if self.keepalive:
|
|
||||||
self.greenlets.append(gevent.spawn(self._keepalive_periodic))
|
|
|
@ -1,46 +0,0 @@
|
||||||
from enum import IntEnum
|
|
||||||
|
|
||||||
|
|
||||||
class PoolErrorCodes(IntEnum):
|
|
||||||
"""Used in the PoolError class for
|
|
||||||
error mapping.
|
|
||||||
|
|
||||||
Overview of contact error codes:
|
|
||||||
- 2000 KILL_ALL_FAILED
|
|
||||||
- 2001 NEW_CONNECTION_FAILED
|
|
||||||
- 2002 KEEP_ALIVE_FAILED
|
|
||||||
"""
|
|
||||||
|
|
||||||
KILL_ALL_FAILED = 2000
|
|
||||||
NEW_CONNECTION_FAILED = 2001
|
|
||||||
KEEP_ALIVE_FAILED = 2002
|
|
||||||
|
|
||||||
|
|
||||||
class PoolError(Exception):
|
|
||||||
"""
|
|
||||||
Overview of contact error codes:
|
|
||||||
- 2000 KILL_ALL_FAILED
|
|
||||||
- 2001 NEW_CONNECTION_FAILED
|
|
||||||
- 2002 KEEP_ALIVE_FAILED
|
|
||||||
|
|
||||||
Note: These are separate from the error codes returned from EppLib
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Used variables due to linter requirements
|
|
||||||
kill_failed = "Could not kill all connections. Are multiple pools running?"
|
|
||||||
conn_failed = "Failed to execute due to a registry error. See previous logs to determine the cause of the error."
|
|
||||||
alive_failed = "Failed to keep the connection alive. It is likely that the registry returned a LoginError."
|
|
||||||
_error_mapping = {
|
|
||||||
PoolErrorCodes.KILL_ALL_FAILED: kill_failed,
|
|
||||||
PoolErrorCodes.NEW_CONNECTION_FAILED: conn_failed,
|
|
||||||
PoolErrorCodes.KEEP_ALIVE_FAILED: alive_failed,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, code=None, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.code = code
|
|
||||||
if self.code in self._error_mapping:
|
|
||||||
self.message = self._error_mapping.get(self.code)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.message}"
|
|
|
@ -1,12 +0,0 @@
|
||||||
class PoolStatus:
|
|
||||||
"""A list of Booleans to keep track of Pool Status.
|
|
||||||
|
|
||||||
pool_running -> bool: Tracks if the pool itself is active or not.
|
|
||||||
connection_success -> bool: Tracks if connection is possible with the registry.
|
|
||||||
pool_hanging -> pool: Tracks if the pool has exceeded its timeout period.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.pool_running = False
|
|
||||||
self.connection_success = False
|
|
||||||
self.pool_hanging = False
|
|
|
@ -188,15 +188,12 @@ WSGI_APPLICATION = "registrar.config.wsgi.application"
|
||||||
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
||||||
|
|
||||||
|
|
||||||
# Caching is disabled by default.
|
CACHES = {
|
||||||
# For a low to medium traffic site, caching causes more
|
"default": {
|
||||||
# problems than it solves. Should caching be desired,
|
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
|
||||||
# a reasonable start might be:
|
"LOCATION": "cache_table",
|
||||||
# CACHES = {
|
}
|
||||||
# "default": {
|
}
|
||||||
# "BACKEND": "django.core.cache.backends.db.DatabaseCache",
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
|
|
||||||
# Absolute path to the directory where `collectstatic`
|
# Absolute path to the directory where `collectstatic`
|
||||||
# will place static files for deployment.
|
# will place static files for deployment.
|
||||||
|
@ -601,20 +598,6 @@ SECRET_REGISTRY_KEY = secret_registry_key
|
||||||
SECRET_REGISTRY_KEY_PASSPHRASE = secret_registry_key_passphrase
|
SECRET_REGISTRY_KEY_PASSPHRASE = secret_registry_key_passphrase
|
||||||
SECRET_REGISTRY_HOSTNAME = secret_registry_hostname
|
SECRET_REGISTRY_HOSTNAME = secret_registry_hostname
|
||||||
|
|
||||||
# Use this variable to set the size of our connection pool in client.py
|
|
||||||
# WARNING: Setting this value too high could cause frequent app crashes!
|
|
||||||
# Having too many connections open could cause the sandbox to timeout,
|
|
||||||
# as the spinup time could exceed the timeout time.
|
|
||||||
EPP_CONNECTION_POOL_SIZE = 1
|
|
||||||
|
|
||||||
# Determines the interval in which we ping open connections in seconds
|
|
||||||
# Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE
|
|
||||||
POOL_KEEP_ALIVE = 60
|
|
||||||
|
|
||||||
# Determines how long we try to keep a pool alive for,
|
|
||||||
# before restarting it.
|
|
||||||
POOL_TIMEOUT = 60
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
# region: Security and Privacy----------------------------------------------###
|
# region: Security and Privacy----------------------------------------------###
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import phonenumber_field.modelfields
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("registrar", "0070_domainapplication_rejection_reason"),
|
("registrar", "0070_domainapplication_rejection_reason"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-03-05 21:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0071_alter_contact_first_name_alter_contact_last_name_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="fax",
|
||||||
|
field=models.CharField(
|
||||||
|
help_text="Contact's fax number (null ok). Must be in ITU.E164.2005 format.", null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="voice",
|
||||||
|
field=models.CharField(help_text="Contact's phone number. Must be in ITU.E164.2005 format"),
|
||||||
|
),
|
||||||
|
]
|
|
@ -8,8 +8,6 @@ from registrar.utility.enums import DefaultEmail
|
||||||
|
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
|
||||||
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def get_id():
|
def get_id():
|
||||||
"""Generate a 16 character registry ID with a low probability of collision."""
|
"""Generate a 16 character registry ID with a low probability of collision."""
|
||||||
|
@ -71,8 +69,8 @@ class PublicContact(TimeStampedModel):
|
||||||
pc = models.CharField(null=False, help_text="Contact's postal code")
|
pc = models.CharField(null=False, help_text="Contact's postal code")
|
||||||
cc = models.CharField(null=False, help_text="Contact's country code")
|
cc = models.CharField(null=False, help_text="Contact's country code")
|
||||||
email = models.EmailField(null=False, help_text="Contact's email address")
|
email = models.EmailField(null=False, help_text="Contact's email address")
|
||||||
voice = PhoneNumberField(null=False, help_text="Contact's phone number. Must be in ITU.E164.2005 format")
|
voice = models.CharField(null=False, help_text="Contact's phone number. Must be in ITU.E164.2005 format")
|
||||||
fax = PhoneNumberField(
|
fax = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
help_text="Contact's fax number (null ok). Must be in ITU.E164.2005 format.",
|
help_text="Contact's fax number (null ok). Must be in ITU.E164.2005 format.",
|
||||||
)
|
)
|
||||||
|
|
|
@ -475,7 +475,6 @@ class AuditedAdminMockData:
|
||||||
def mock_user():
|
def mock_user():
|
||||||
"""A simple user."""
|
"""A simple user."""
|
||||||
user_kwargs = dict(
|
user_kwargs = dict(
|
||||||
id=4,
|
|
||||||
first_name="Jeff",
|
first_name="Jeff",
|
||||||
last_name="Lebowski",
|
last_name="Lebowski",
|
||||||
)
|
)
|
||||||
|
|
|
@ -1542,6 +1542,8 @@ class DomainInvitationAdminTest(TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Delete all DomainInvitation objects"""
|
"""Delete all DomainInvitation objects"""
|
||||||
DomainInvitation.objects.all().delete()
|
DomainInvitation.objects.all().delete()
|
||||||
|
User.objects.all().delete()
|
||||||
|
Contact.objects.all().delete()
|
||||||
|
|
||||||
def test_get_filters(self):
|
def test_get_filters(self):
|
||||||
"""Ensures that our filters are displaying correctly"""
|
"""Ensures that our filters are displaying correctly"""
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
-i https://pypi.python.org/simple
|
-i https://pypi.python.org/simple
|
||||||
annotated-types==0.6.0; python_version >= '3.8'
|
annotated-types==0.6.0; python_version >= '3.8'
|
||||||
asgiref==3.7.2; python_version >= '3.7'
|
asgiref==3.7.2; python_version >= '3.7'
|
||||||
boto3==1.34.37; python_version >= '3.8'
|
boto3==1.34.54; python_version >= '3.8'
|
||||||
botocore==1.34.37; python_version >= '3.8'
|
botocore==1.34.54; python_version >= '3.8'
|
||||||
cachetools==5.3.2; python_version >= '3.7'
|
cachetools==5.3.3; python_version >= '3.7'
|
||||||
certifi==2024.2.2; python_version >= '3.6'
|
certifi==2024.2.2; python_version >= '3.6'
|
||||||
cfenv==0.5.3
|
cfenv==0.5.3
|
||||||
cffi==1.16.0; platform_python_implementation != 'PyPy'
|
cffi==1.16.0; platform_python_implementation != 'PyPy'
|
||||||
charset-normalizer==3.3.2; python_full_version >= '3.7.0'
|
charset-normalizer==3.3.2; python_full_version >= '3.7.0'
|
||||||
cryptography==42.0.2; python_version >= '3.7'
|
cryptography==42.0.5; python_version >= '3.7'
|
||||||
defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||||
dj-database-url==2.1.0
|
dj-database-url==2.1.0
|
||||||
dj-email-url==1.0.6
|
dj-email-url==1.0.6
|
||||||
|
@ -17,18 +17,17 @@ django-allow-cidr==0.7.1
|
||||||
django-auditlog==2.3.0; python_version >= '3.7'
|
django-auditlog==2.3.0; python_version >= '3.7'
|
||||||
django-cache-url==3.4.5
|
django-cache-url==3.4.5
|
||||||
django-cors-headers==4.3.1; python_version >= '3.8'
|
django-cors-headers==4.3.1; python_version >= '3.8'
|
||||||
django-csp==3.7
|
django-csp==3.8
|
||||||
django-fsm==2.8.1
|
django-fsm==2.8.1
|
||||||
django-login-required-middleware==0.9.0
|
django-login-required-middleware==0.9.0
|
||||||
django-phonenumber-field[phonenumberslite]==7.3.0; python_version >= '3.8'
|
django-phonenumber-field[phonenumberslite]==7.3.0; python_version >= '3.8'
|
||||||
django-widget-tweaks==1.5.0; python_version >= '3.8'
|
django-widget-tweaks==1.5.0; python_version >= '3.8'
|
||||||
environs[django]==10.3.0; python_version >= '3.8'
|
environs[django]==10.3.0; python_version >= '3.8'
|
||||||
faker==23.1.0; python_version >= '3.8'
|
faker==23.3.0; python_version >= '3.8'
|
||||||
fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c
|
fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c
|
||||||
furl==2.1.3
|
furl==2.1.3
|
||||||
future==0.18.3; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
future==1.0.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||||
gevent==23.9.1; python_version >= '3.8'
|
gevent==24.2.1; python_version >= '3.8'
|
||||||
geventconnpool@ git+https://github.com/rasky/geventconnpool.git@1bbb93a714a331a069adf27265fe582d9ba7ecd4
|
|
||||||
greenlet==3.0.3; python_version >= '3.7'
|
greenlet==3.0.3; python_version >= '3.7'
|
||||||
gunicorn==21.2.0; python_version >= '3.5'
|
gunicorn==21.2.0; python_version >= '3.5'
|
||||||
idna==3.6; python_version >= '3.5'
|
idna==3.6; python_version >= '3.5'
|
||||||
|
@ -36,27 +35,28 @@ jmespath==1.0.1; python_version >= '3.7'
|
||||||
lxml==5.1.0; python_version >= '3.6'
|
lxml==5.1.0; python_version >= '3.6'
|
||||||
mako==1.3.2; python_version >= '3.8'
|
mako==1.3.2; python_version >= '3.8'
|
||||||
markupsafe==2.1.5; python_version >= '3.7'
|
markupsafe==2.1.5; python_version >= '3.7'
|
||||||
marshmallow==3.20.2; python_version >= '3.8'
|
marshmallow==3.21.0; python_version >= '3.8'
|
||||||
oic==1.6.1; python_version ~= '3.7'
|
oic==1.6.1; python_version ~= '3.7'
|
||||||
orderedmultidict==1.0.1
|
orderedmultidict==1.0.1
|
||||||
packaging==23.2; python_version >= '3.7'
|
packaging==23.2; python_version >= '3.7'
|
||||||
phonenumberslite==8.13.29
|
phonenumberslite==8.13.31
|
||||||
psycopg2-binary==2.9.9; python_version >= '3.7'
|
psycopg2-binary==2.9.9; python_version >= '3.7'
|
||||||
pycparser==2.21
|
pycparser==2.21
|
||||||
pycryptodomex==3.20.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
pycryptodomex==3.20.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||||
pydantic==2.6.1; python_version >= '3.8'
|
pydantic==2.6.3; python_version >= '3.8'
|
||||||
pydantic-core==2.16.2; python_version >= '3.8'
|
pydantic-core==2.16.3; python_version >= '3.8'
|
||||||
pydantic-settings==2.1.0; python_version >= '3.8'
|
pydantic-settings==2.2.1; python_version >= '3.8'
|
||||||
pyjwkest==1.4.2
|
pyjwkest==1.4.2
|
||||||
python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||||
python-dotenv==1.0.1; python_version >= '3.8'
|
python-dotenv==1.0.1; python_version >= '3.8'
|
||||||
requests==2.31.0; python_version >= '3.7'
|
requests==2.31.0; python_version >= '3.7'
|
||||||
s3transfer==0.10.0; python_version >= '3.8'
|
s3transfer==0.10.0; python_version >= '3.8'
|
||||||
setuptools==69.0.3; python_version >= '3.8'
|
setuptools==69.1.1; python_version >= '3.8'
|
||||||
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||||
sqlparse==0.4.4; python_version >= '3.5'
|
sqlparse==0.4.4; python_version >= '3.5'
|
||||||
typing-extensions==4.9.0; python_version >= '3.8'
|
tblib==3.0.0; python_version >= '3.8'
|
||||||
|
typing-extensions==4.10.0; python_version >= '3.8'
|
||||||
urllib3==2.0.7; python_version >= '3.7'
|
urllib3==2.0.7; python_version >= '3.7'
|
||||||
whitenoise==6.6.0; python_version >= '3.8'
|
whitenoise==6.6.0; python_version >= '3.8'
|
||||||
zope.event==5.0; python_version >= '3.7'
|
zope.event==5.0; python_version >= '3.7'
|
||||||
zope.interface==6.1; python_version >= '3.7'
|
zope.interface==6.2; python_version >= '3.7'
|
||||||
|
|
|
@ -6,4 +6,4 @@ set -o pipefail
|
||||||
# Make sure that django's `collectstatic` has been run locally before pushing up to any environment,
|
# Make sure that django's `collectstatic` has been run locally before pushing up to any environment,
|
||||||
# so that the styles and static assets to show up correctly on any environment.
|
# so that the styles and static assets to show up correctly on any environment.
|
||||||
|
|
||||||
gunicorn --worker-class=gevent registrar.config.wsgi -t 60
|
gunicorn --workers=3 --worker-class=gevent registrar.config.wsgi -t 60
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue