mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-01 07:26:34 +02:00
Merge branch 'main' into za/1676-require-investigator-da
This commit is contained in:
commit
6f15428abd
29 changed files with 1763 additions and 1117 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 |
30
docs/operations/runbooks/downtime_incident_management.md
Normal file
30
docs/operations/runbooks/downtime_incident_management.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Downtime Incident Management Runbook
|
||||||
|
|
||||||
|
Our team has agreed upon steps for handling incidents that cause our site to go offline or become unusable for users. For this document, an incident refers to one in which manage.get.gov is offline or displaying error 400/500 HTTP errors on all pages. However, for this document to apply the cause of the problem must be a critical bug in our code or one of our providers having an outage, not to be confused with a cyber security incident. This document should not be used in response to any type of cyber security incident.
|
||||||
|
|
||||||
|
## Response management rules
|
||||||
|
|
||||||
|
The following set of rules should be followed while an incident is in progress.
|
||||||
|
|
||||||
|
- The person who first notices that the site is down is responsible for using @here and notifying in #dotgov-announce that production is down.
|
||||||
|
- This applies to any team member, including new team members and non-developers.
|
||||||
|
- If no engineer has acknowledged the announcement within 10 minutes, whoever discovered the site was down should call each developer via the Slack DM huddle feature. If there is no response, this should escalate to a phone call.
|
||||||
|
- When calling, go down the [phone call list](https://docs.google.com/document/d/1k4r-1MNCfW8EXSXa-tqJQzOvJxQv0ARvHnOjjAH0LII/edit) from top to bottom until someone answers who is available to help.
|
||||||
|
- If this incident occurs outside of regular working hours, choosing to help is on a volunteer basis, and answering a call doesn't mean an individual is truly available to assist.
|
||||||
|
- Once an engineer is online, they should immediately start a huddle in the #dotgov-redalert channel to begin troubleshooting.
|
||||||
|
- All available engineers should join the huddle once they see it.
|
||||||
|
- If downtime occurs outside of working hours, team members who are off for the day may still be pinged and called but are not required to join if unavailable to do so.
|
||||||
|
- Uncomment the [banner on get.gov](https://github.com/cisagov/get.gov/blob/0365d3d34b041cc9353497b2b5f81b6ab7fe75a9/_includes/header.html#L9), so it is transparent to users that we know about the issue on manage.get.gov.
|
||||||
|
- Designers or Developers should be able to make this change; if designers are online and can help with this task, that will allow developers to focus on fixing the bug.
|
||||||
|
|
||||||
|
## Post Incident
|
||||||
|
|
||||||
|
The following checklist should be followed after the site is back up and running.
|
||||||
|
|
||||||
|
- [ ] Message in #dotgov-announce with an @here saying the issue is resolved
|
||||||
|
- [ ] Remove the [banner on get.gov](https://github.com/cisagov/get.gov/blob/0365d3d34b041cc9353497b2b5f81b6ab7fe75a9/_includes/header.html#L9) by commenting it out.
|
||||||
|
- [ ] Write up what happened and when; if the cause is already known, write that as well. This is a draft for internal communications and not for any public facing site and can be as simple as using bullet points.
|
||||||
|
- [ ] If the cause is not known yet, developers should investigate the issue as the highest priority task.
|
||||||
|
- [ ] As close to the event as possible, such as the next day, perform a team incident retro that is an hour long. The goal of this meeting should be to inform all team members what happened and what is being done now and to collect feedback on what could have been done better. This is where the draft write up of what happened will be useful.
|
||||||
|
- [ ] After the retro and once the bug is fully identified, an engineer should assist in writing an incident report and may be as detailed as possible for future team members to refer to. That document should be places in the [Incidents folder](https://drive.google.com/drive/folders/1LPVICVpI4Xb5KGdrNkSwhX2OAJ6hYTyu).
|
||||||
|
- [ ] After creating the document above, the lead engineer make a draft of content that will go in the get.gov Incidents section. This Word document should be shared and reviewed by the product team before a developer adds it to get.gov.
|
|
@ -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.")
|
||||||
|
try:
|
||||||
# Reopen the pool if its closed
|
return self._send(command)
|
||||||
# Only occurs when a login error is raised, after connection is successful
|
except RegistryError as err:
|
||||||
if not self.pool_status.pool_running:
|
if (
|
||||||
# We want to reopen the connection pool,
|
err.is_transport_error()
|
||||||
# but we don't want the end user to wait while it opens.
|
or err.is_connection_error()
|
||||||
# Raise syntax doesn't allow this, so we use a try/catch
|
or err.is_session_error()
|
||||||
# block.
|
or err.is_server_error()
|
||||||
try:
|
or err.should_retry()
|
||||||
logger.error("Can't contact the Registry. Pool was not running.")
|
):
|
||||||
raise RegistryError("Can't contact the Registry. Pool was not running.")
|
message = f"{cmd_type} failed and will be retried"
|
||||||
except RegistryError as err:
|
logger.info(f"{message} Error: {err}")
|
||||||
|
return self._retry(command)
|
||||||
|
else:
|
||||||
raise 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:
|
|
||||||
return self._send(command)
|
|
||||||
except RegistryError as err:
|
|
||||||
if counter < 3 and (err.should_retry() or err.is_transport_error()):
|
|
||||||
logger.info(f"Retrying transport error. Attempt #{counter+1} of 3.")
|
|
||||||
counter += 1
|
|
||||||
sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms
|
|
||||||
else: # don't try again
|
|
||||||
raise err
|
|
||||||
|
|
||||||
def get_pool(self):
|
|
||||||
"""Get the current pool instance"""
|
|
||||||
return self._pool
|
|
||||||
|
|
||||||
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:
|
|
||||||
self.pool_status.connection_success = True
|
|
||||||
|
|
||||||
# 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----------------------------------------------###
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,863 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-02-28 04:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import phonenumber_field.modelfields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0070_domainapplication_rejection_reason"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="contact",
|
||||||
|
name="first_name",
|
||||||
|
field=models.CharField(blank=True, db_index=True, null=True, verbose_name="first name / given name"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="contact",
|
||||||
|
name="last_name",
|
||||||
|
field=models.CharField(blank=True, db_index=True, null=True, verbose_name="last name / family name"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="contact",
|
||||||
|
name="middle_name",
|
||||||
|
field=models.CharField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="contact",
|
||||||
|
name="title",
|
||||||
|
field=models.CharField(blank=True, null=True, verbose_name="title or role in your organization"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="address_line1",
|
||||||
|
field=models.CharField(blank=True, help_text="Street address", null=True, verbose_name="Address line 1"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="address_line2",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True, help_text="Street address line 2 (optional)", null=True, verbose_name="Address line 2"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="city",
|
||||||
|
field=models.CharField(blank=True, help_text="City", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="federal_agency",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
(
|
||||||
|
"Administrative Conference of the United States",
|
||||||
|
"Administrative Conference of the United States",
|
||||||
|
),
|
||||||
|
("Advisory Council on Historic Preservation", "Advisory Council on Historic Preservation"),
|
||||||
|
("American Battle Monuments Commission", "American Battle Monuments Commission"),
|
||||||
|
("AMTRAK", "AMTRAK"),
|
||||||
|
("Appalachian Regional Commission", "Appalachian Regional Commission"),
|
||||||
|
(
|
||||||
|
"Appraisal Subcommittee of the Federal Financial Institutions Examination Council",
|
||||||
|
"Appraisal Subcommittee of the Federal Financial Institutions Examination Council",
|
||||||
|
),
|
||||||
|
("Appraisal Subcommittee", "Appraisal Subcommittee"),
|
||||||
|
("Architect of the Capitol", "Architect of the Capitol"),
|
||||||
|
("Armed Forces Retirement Home", "Armed Forces Retirement Home"),
|
||||||
|
(
|
||||||
|
"Barry Goldwater Scholarship and Excellence in Education Foundation",
|
||||||
|
"Barry Goldwater Scholarship and Excellence in Education Foundation",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Barry Goldwater Scholarship and Excellence in Education Program",
|
||||||
|
"Barry Goldwater Scholarship and Excellence in Education Program",
|
||||||
|
),
|
||||||
|
("Central Intelligence Agency", "Central Intelligence Agency"),
|
||||||
|
("Chemical Safety Board", "Chemical Safety Board"),
|
||||||
|
("Christopher Columbus Fellowship Foundation", "Christopher Columbus Fellowship Foundation"),
|
||||||
|
("Civil Rights Cold Case Records Review Board", "Civil Rights Cold Case Records Review Board"),
|
||||||
|
(
|
||||||
|
"Commission for the Preservation of America's Heritage Abroad",
|
||||||
|
"Commission for the Preservation of America's Heritage Abroad",
|
||||||
|
),
|
||||||
|
("Commission of Fine Arts", "Commission of Fine Arts"),
|
||||||
|
(
|
||||||
|
"Committee for Purchase From People Who Are Blind or Severely Disabled",
|
||||||
|
"Committee for Purchase From People Who Are Blind or Severely Disabled",
|
||||||
|
),
|
||||||
|
("Commodity Futures Trading Commission", "Commodity Futures Trading Commission"),
|
||||||
|
("Congressional Budget Office", "Congressional Budget Office"),
|
||||||
|
("Consumer Financial Protection Bureau", "Consumer Financial Protection Bureau"),
|
||||||
|
("Consumer Product Safety Commission", "Consumer Product Safety Commission"),
|
||||||
|
("Corporation for National & Community Service", "Corporation for National & Community Service"),
|
||||||
|
(
|
||||||
|
"Corporation for National and Community Service",
|
||||||
|
"Corporation for National and Community Service",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Council of Inspectors General on Integrity and Efficiency",
|
||||||
|
"Council of Inspectors General on Integrity and Efficiency",
|
||||||
|
),
|
||||||
|
("Court Services and Offender Supervision", "Court Services and Offender Supervision"),
|
||||||
|
("Cyberspace Solarium Commission", "Cyberspace Solarium Commission"),
|
||||||
|
(
|
||||||
|
"DC Court Services and Offender Supervision Agency",
|
||||||
|
"DC Court Services and Offender Supervision Agency",
|
||||||
|
),
|
||||||
|
("DC Pre-trial Services", "DC Pre-trial Services"),
|
||||||
|
("Defense Nuclear Facilities Safety Board", "Defense Nuclear Facilities Safety Board"),
|
||||||
|
("Delta Regional Authority", "Delta Regional Authority"),
|
||||||
|
("Denali Commission", "Denali Commission"),
|
||||||
|
("Department of Agriculture", "Department of Agriculture"),
|
||||||
|
("Department of Commerce", "Department of Commerce"),
|
||||||
|
("Department of Defense", "Department of Defense"),
|
||||||
|
("Department of Education", "Department of Education"),
|
||||||
|
("Department of Energy", "Department of Energy"),
|
||||||
|
("Department of Health and Human Services", "Department of Health and Human Services"),
|
||||||
|
("Department of Homeland Security", "Department of Homeland Security"),
|
||||||
|
("Department of Housing and Urban Development", "Department of Housing and Urban Development"),
|
||||||
|
("Department of Justice", "Department of Justice"),
|
||||||
|
("Department of Labor", "Department of Labor"),
|
||||||
|
("Department of State", "Department of State"),
|
||||||
|
("Department of the Interior", "Department of the Interior"),
|
||||||
|
("Department of the Treasury", "Department of the Treasury"),
|
||||||
|
("Department of Transportation", "Department of Transportation"),
|
||||||
|
("Department of Veterans Affairs", "Department of Veterans Affairs"),
|
||||||
|
("Director of National Intelligence", "Director of National Intelligence"),
|
||||||
|
("Dwight D. Eisenhower Memorial Commission", "Dwight D. Eisenhower Memorial Commission"),
|
||||||
|
("Election Assistance Commission", "Election Assistance Commission"),
|
||||||
|
("Environmental Protection Agency", "Environmental Protection Agency"),
|
||||||
|
("Equal Employment Opportunity Commission", "Equal Employment Opportunity Commission"),
|
||||||
|
("Executive Office of the President", "Executive Office of the President"),
|
||||||
|
("Export-Import Bank of the United States", "Export-Import Bank of the United States"),
|
||||||
|
("Export/Import Bank of the U.S.", "Export/Import Bank of the U.S."),
|
||||||
|
("Farm Credit Administration", "Farm Credit Administration"),
|
||||||
|
("Farm Credit System Insurance Corporation", "Farm Credit System Insurance Corporation"),
|
||||||
|
("Federal Communications Commission", "Federal Communications Commission"),
|
||||||
|
("Federal Deposit Insurance Corporation", "Federal Deposit Insurance Corporation"),
|
||||||
|
("Federal Election Commission", "Federal Election Commission"),
|
||||||
|
("Federal Energy Regulatory Commission", "Federal Energy Regulatory Commission"),
|
||||||
|
(
|
||||||
|
"Federal Financial Institutions Examination Council",
|
||||||
|
"Federal Financial Institutions Examination Council",
|
||||||
|
),
|
||||||
|
("Federal Housing Finance Agency", "Federal Housing Finance Agency"),
|
||||||
|
("Federal Judiciary", "Federal Judiciary"),
|
||||||
|
("Federal Labor Relations Authority", "Federal Labor Relations Authority"),
|
||||||
|
("Federal Maritime Commission", "Federal Maritime Commission"),
|
||||||
|
("Federal Mediation and Conciliation Service", "Federal Mediation and Conciliation Service"),
|
||||||
|
(
|
||||||
|
"Federal Mine Safety and Health Review Commission",
|
||||||
|
"Federal Mine Safety and Health Review Commission",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Federal Permitting Improvement Steering Council",
|
||||||
|
"Federal Permitting Improvement Steering Council",
|
||||||
|
),
|
||||||
|
("Federal Reserve Board of Governors", "Federal Reserve Board of Governors"),
|
||||||
|
("Federal Reserve System", "Federal Reserve System"),
|
||||||
|
("Federal Trade Commission", "Federal Trade Commission"),
|
||||||
|
("General Services Administration", "General Services Administration"),
|
||||||
|
("gov Administration", "gov Administration"),
|
||||||
|
("Government Accountability Office", "Government Accountability Office"),
|
||||||
|
("Government Publishing Office", "Government Publishing Office"),
|
||||||
|
("Gulf Coast Ecosystem Restoration Council", "Gulf Coast Ecosystem Restoration Council"),
|
||||||
|
("Harry S Truman Scholarship Foundation", "Harry S Truman Scholarship Foundation"),
|
||||||
|
("Harry S. Truman Scholarship Foundation", "Harry S. Truman Scholarship Foundation"),
|
||||||
|
("Institute of Museum and Library Services", "Institute of Museum and Library Services"),
|
||||||
|
("Institute of Peace", "Institute of Peace"),
|
||||||
|
("Inter-American Foundation", "Inter-American Foundation"),
|
||||||
|
(
|
||||||
|
"International Boundary and Water Commission: United States and Mexico",
|
||||||
|
"International Boundary and Water Commission: United States and Mexico",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"International Boundary Commission: United States and Canada",
|
||||||
|
"International Boundary Commission: United States and Canada",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"International Joint Commission: United States and Canada",
|
||||||
|
"International Joint Commission: United States and Canada",
|
||||||
|
),
|
||||||
|
("James Madison Memorial Fellowship Foundation", "James Madison Memorial Fellowship Foundation"),
|
||||||
|
("Japan-United States Friendship Commission", "Japan-United States Friendship Commission"),
|
||||||
|
("Japan-US Friendship Commission", "Japan-US Friendship Commission"),
|
||||||
|
("John F. Kennedy Center for Performing Arts", "John F. Kennedy Center for Performing Arts"),
|
||||||
|
(
|
||||||
|
"John F. Kennedy Center for the Performing Arts",
|
||||||
|
"John F. Kennedy Center for the Performing Arts",
|
||||||
|
),
|
||||||
|
("Legal Services Corporation", "Legal Services Corporation"),
|
||||||
|
("Legislative Branch", "Legislative Branch"),
|
||||||
|
("Library of Congress", "Library of Congress"),
|
||||||
|
("Marine Mammal Commission", "Marine Mammal Commission"),
|
||||||
|
(
|
||||||
|
"Medicaid and CHIP Payment and Access Commission",
|
||||||
|
"Medicaid and CHIP Payment and Access Commission",
|
||||||
|
),
|
||||||
|
("Medical Payment Advisory Commission", "Medical Payment Advisory Commission"),
|
||||||
|
("Medicare Payment Advisory Commission", "Medicare Payment Advisory Commission"),
|
||||||
|
("Merit Systems Protection Board", "Merit Systems Protection Board"),
|
||||||
|
("Millennium Challenge Corporation", "Millennium Challenge Corporation"),
|
||||||
|
(
|
||||||
|
"Morris K. Udall and Stewart L. Udall Foundation",
|
||||||
|
"Morris K. Udall and Stewart L. Udall Foundation",
|
||||||
|
),
|
||||||
|
("National Aeronautics and Space Administration", "National Aeronautics and Space Administration"),
|
||||||
|
("National Archives and Records Administration", "National Archives and Records Administration"),
|
||||||
|
("National Capital Planning Commission", "National Capital Planning Commission"),
|
||||||
|
("National Council on Disability", "National Council on Disability"),
|
||||||
|
("National Credit Union Administration", "National Credit Union Administration"),
|
||||||
|
("National Endowment for the Arts", "National Endowment for the Arts"),
|
||||||
|
("National Endowment for the Humanities", "National Endowment for the Humanities"),
|
||||||
|
(
|
||||||
|
"National Foundation on the Arts and the Humanities",
|
||||||
|
"National Foundation on the Arts and the Humanities",
|
||||||
|
),
|
||||||
|
("National Gallery of Art", "National Gallery of Art"),
|
||||||
|
("National Indian Gaming Commission", "National Indian Gaming Commission"),
|
||||||
|
("National Labor Relations Board", "National Labor Relations Board"),
|
||||||
|
("National Mediation Board", "National Mediation Board"),
|
||||||
|
("National Science Foundation", "National Science Foundation"),
|
||||||
|
(
|
||||||
|
"National Security Commission on Artificial Intelligence",
|
||||||
|
"National Security Commission on Artificial Intelligence",
|
||||||
|
),
|
||||||
|
("National Transportation Safety Board", "National Transportation Safety Board"),
|
||||||
|
(
|
||||||
|
"Networking Information Technology Research and Development",
|
||||||
|
"Networking Information Technology Research and Development",
|
||||||
|
),
|
||||||
|
("Non-Federal Agency", "Non-Federal Agency"),
|
||||||
|
("Northern Border Regional Commission", "Northern Border Regional Commission"),
|
||||||
|
("Nuclear Regulatory Commission", "Nuclear Regulatory Commission"),
|
||||||
|
("Nuclear Safety Oversight Committee", "Nuclear Safety Oversight Committee"),
|
||||||
|
("Nuclear Waste Technical Review Board", "Nuclear Waste Technical Review Board"),
|
||||||
|
(
|
||||||
|
"Occupational Safety & Health Review Commission",
|
||||||
|
"Occupational Safety & Health Review Commission",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Occupational Safety and Health Review Commission",
|
||||||
|
"Occupational Safety and Health Review Commission",
|
||||||
|
),
|
||||||
|
("Office of Compliance", "Office of Compliance"),
|
||||||
|
("Office of Congressional Workplace Rights", "Office of Congressional Workplace Rights"),
|
||||||
|
("Office of Government Ethics", "Office of Government Ethics"),
|
||||||
|
("Office of Navajo and Hopi Indian Relocation", "Office of Navajo and Hopi Indian Relocation"),
|
||||||
|
("Office of Personnel Management", "Office of Personnel Management"),
|
||||||
|
("Open World Leadership Center", "Open World Leadership Center"),
|
||||||
|
("Overseas Private Investment Corporation", "Overseas Private Investment Corporation"),
|
||||||
|
("Peace Corps", "Peace Corps"),
|
||||||
|
("Pension Benefit Guaranty Corporation", "Pension Benefit Guaranty Corporation"),
|
||||||
|
("Postal Regulatory Commission", "Postal Regulatory Commission"),
|
||||||
|
("Presidio Trust", "Presidio Trust"),
|
||||||
|
("Privacy and Civil Liberties Oversight Board", "Privacy and Civil Liberties Oversight Board"),
|
||||||
|
("Public Buildings Reform Board", "Public Buildings Reform Board"),
|
||||||
|
(
|
||||||
|
"Public Defender Service for the District of Columbia",
|
||||||
|
"Public Defender Service for the District of Columbia",
|
||||||
|
),
|
||||||
|
("Railroad Retirement Board", "Railroad Retirement Board"),
|
||||||
|
("Securities and Exchange Commission", "Securities and Exchange Commission"),
|
||||||
|
("Selective Service System", "Selective Service System"),
|
||||||
|
("Small Business Administration", "Small Business Administration"),
|
||||||
|
("Smithsonian Institution", "Smithsonian Institution"),
|
||||||
|
("Social Security Administration", "Social Security Administration"),
|
||||||
|
("Social Security Advisory Board", "Social Security Advisory Board"),
|
||||||
|
("Southeast Crescent Regional Commission", "Southeast Crescent Regional Commission"),
|
||||||
|
("Southwest Border Regional Commission", "Southwest Border Regional Commission"),
|
||||||
|
("State Justice Institute", "State Justice Institute"),
|
||||||
|
("State, Local, and Tribal Government", "State, Local, and Tribal Government"),
|
||||||
|
("Stennis Center for Public Service", "Stennis Center for Public Service"),
|
||||||
|
("Surface Transportation Board", "Surface Transportation Board"),
|
||||||
|
("Tennessee Valley Authority", "Tennessee Valley Authority"),
|
||||||
|
("The Executive Office of the President", "The Executive Office of the President"),
|
||||||
|
("The Intelligence Community", "The Intelligence Community"),
|
||||||
|
("The Legislative Branch", "The Legislative Branch"),
|
||||||
|
("The Supreme Court", "The Supreme Court"),
|
||||||
|
(
|
||||||
|
"The United States World War One Centennial Commission",
|
||||||
|
"The United States World War One Centennial Commission",
|
||||||
|
),
|
||||||
|
("U.S. Access Board", "U.S. Access Board"),
|
||||||
|
("U.S. Agency for Global Media", "U.S. Agency for Global Media"),
|
||||||
|
("U.S. Agency for International Development", "U.S. Agency for International Development"),
|
||||||
|
("U.S. Capitol Police", "U.S. Capitol Police"),
|
||||||
|
("U.S. Chemical Safety Board", "U.S. Chemical Safety Board"),
|
||||||
|
(
|
||||||
|
"U.S. China Economic and Security Review Commission",
|
||||||
|
"U.S. China Economic and Security Review Commission",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"U.S. Commission for the Preservation of Americas Heritage Abroad",
|
||||||
|
"U.S. Commission for the Preservation of Americas Heritage Abroad",
|
||||||
|
),
|
||||||
|
("U.S. Commission of Fine Arts", "U.S. Commission of Fine Arts"),
|
||||||
|
("U.S. Commission on Civil Rights", "U.S. Commission on Civil Rights"),
|
||||||
|
(
|
||||||
|
"U.S. Commission on International Religious Freedom",
|
||||||
|
"U.S. Commission on International Religious Freedom",
|
||||||
|
),
|
||||||
|
("U.S. Courts", "U.S. Courts"),
|
||||||
|
("U.S. Department of Agriculture", "U.S. Department of Agriculture"),
|
||||||
|
("U.S. Interagency Council on Homelessness", "U.S. Interagency Council on Homelessness"),
|
||||||
|
("U.S. International Trade Commission", "U.S. International Trade Commission"),
|
||||||
|
("U.S. Nuclear Waste Technical Review Board", "U.S. Nuclear Waste Technical Review Board"),
|
||||||
|
("U.S. Office of Special Counsel", "U.S. Office of Special Counsel"),
|
||||||
|
("U.S. Peace Corps", "U.S. Peace Corps"),
|
||||||
|
("U.S. Postal Service", "U.S. Postal Service"),
|
||||||
|
("U.S. Semiquincentennial Commission", "U.S. Semiquincentennial Commission"),
|
||||||
|
("U.S. Trade and Development Agency", "U.S. Trade and Development Agency"),
|
||||||
|
(
|
||||||
|
"U.S.-China Economic and Security Review Commission",
|
||||||
|
"U.S.-China Economic and Security Review Commission",
|
||||||
|
),
|
||||||
|
("Udall Foundation", "Udall Foundation"),
|
||||||
|
("United States AbilityOne", "United States AbilityOne"),
|
||||||
|
("United States Access Board", "United States Access Board"),
|
||||||
|
("United States African Development Foundation", "United States African Development Foundation"),
|
||||||
|
("United States Agency for Global Media", "United States Agency for Global Media"),
|
||||||
|
("United States Arctic Research Commission", "United States Arctic Research Commission"),
|
||||||
|
("United States Global Change Research Program", "United States Global Change Research Program"),
|
||||||
|
("United States Holocaust Memorial Museum", "United States Holocaust Memorial Museum"),
|
||||||
|
("United States Institute of Peace", "United States Institute of Peace"),
|
||||||
|
(
|
||||||
|
"United States Interagency Council on Homelessness",
|
||||||
|
"United States Interagency Council on Homelessness",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"United States International Development Finance Corporation",
|
||||||
|
"United States International Development Finance Corporation",
|
||||||
|
),
|
||||||
|
("United States International Trade Commission", "United States International Trade Commission"),
|
||||||
|
("United States Postal Service", "United States Postal Service"),
|
||||||
|
("United States Senate", "United States Senate"),
|
||||||
|
("United States Trade and Development Agency", "United States Trade and Development Agency"),
|
||||||
|
(
|
||||||
|
"Utah Reclamation Mitigation and Conservation Commission",
|
||||||
|
"Utah Reclamation Mitigation and Conservation Commission",
|
||||||
|
),
|
||||||
|
("Vietnam Education Foundation", "Vietnam Education Foundation"),
|
||||||
|
("Western Hemisphere Drug Policy Commission", "Western Hemisphere Drug Policy Commission"),
|
||||||
|
(
|
||||||
|
"Woodrow Wilson International Center for Scholars",
|
||||||
|
"Woodrow Wilson International Center for Scholars",
|
||||||
|
),
|
||||||
|
("World War I Centennial Commission", "World War I Centennial Commission"),
|
||||||
|
],
|
||||||
|
help_text="Federal agency",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="organization_name",
|
||||||
|
field=models.CharField(blank=True, db_index=True, help_text="Organization name", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="tribe_name",
|
||||||
|
field=models.CharField(blank=True, help_text="Name of tribe", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="urbanization",
|
||||||
|
field=models.CharField(blank=True, help_text="Urbanization (required for Puerto Rico only)", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="address_line1",
|
||||||
|
field=models.CharField(blank=True, help_text="Street address", null=True, verbose_name="Street address"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="address_line2",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Street address line 2 (optional)",
|
||||||
|
null=True,
|
||||||
|
verbose_name="Street address line 2 (optional)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="city",
|
||||||
|
field=models.CharField(blank=True, help_text="City", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="federal_agency",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
(
|
||||||
|
"Administrative Conference of the United States",
|
||||||
|
"Administrative Conference of the United States",
|
||||||
|
),
|
||||||
|
("Advisory Council on Historic Preservation", "Advisory Council on Historic Preservation"),
|
||||||
|
("American Battle Monuments Commission", "American Battle Monuments Commission"),
|
||||||
|
("AMTRAK", "AMTRAK"),
|
||||||
|
("Appalachian Regional Commission", "Appalachian Regional Commission"),
|
||||||
|
(
|
||||||
|
"Appraisal Subcommittee of the Federal Financial Institutions Examination Council",
|
||||||
|
"Appraisal Subcommittee of the Federal Financial Institutions Examination Council",
|
||||||
|
),
|
||||||
|
("Appraisal Subcommittee", "Appraisal Subcommittee"),
|
||||||
|
("Architect of the Capitol", "Architect of the Capitol"),
|
||||||
|
("Armed Forces Retirement Home", "Armed Forces Retirement Home"),
|
||||||
|
(
|
||||||
|
"Barry Goldwater Scholarship and Excellence in Education Foundation",
|
||||||
|
"Barry Goldwater Scholarship and Excellence in Education Foundation",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Barry Goldwater Scholarship and Excellence in Education Program",
|
||||||
|
"Barry Goldwater Scholarship and Excellence in Education Program",
|
||||||
|
),
|
||||||
|
("Central Intelligence Agency", "Central Intelligence Agency"),
|
||||||
|
("Chemical Safety Board", "Chemical Safety Board"),
|
||||||
|
("Christopher Columbus Fellowship Foundation", "Christopher Columbus Fellowship Foundation"),
|
||||||
|
("Civil Rights Cold Case Records Review Board", "Civil Rights Cold Case Records Review Board"),
|
||||||
|
(
|
||||||
|
"Commission for the Preservation of America's Heritage Abroad",
|
||||||
|
"Commission for the Preservation of America's Heritage Abroad",
|
||||||
|
),
|
||||||
|
("Commission of Fine Arts", "Commission of Fine Arts"),
|
||||||
|
(
|
||||||
|
"Committee for Purchase From People Who Are Blind or Severely Disabled",
|
||||||
|
"Committee for Purchase From People Who Are Blind or Severely Disabled",
|
||||||
|
),
|
||||||
|
("Commodity Futures Trading Commission", "Commodity Futures Trading Commission"),
|
||||||
|
("Congressional Budget Office", "Congressional Budget Office"),
|
||||||
|
("Consumer Financial Protection Bureau", "Consumer Financial Protection Bureau"),
|
||||||
|
("Consumer Product Safety Commission", "Consumer Product Safety Commission"),
|
||||||
|
("Corporation for National & Community Service", "Corporation for National & Community Service"),
|
||||||
|
(
|
||||||
|
"Corporation for National and Community Service",
|
||||||
|
"Corporation for National and Community Service",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Council of Inspectors General on Integrity and Efficiency",
|
||||||
|
"Council of Inspectors General on Integrity and Efficiency",
|
||||||
|
),
|
||||||
|
("Court Services and Offender Supervision", "Court Services and Offender Supervision"),
|
||||||
|
("Cyberspace Solarium Commission", "Cyberspace Solarium Commission"),
|
||||||
|
(
|
||||||
|
"DC Court Services and Offender Supervision Agency",
|
||||||
|
"DC Court Services and Offender Supervision Agency",
|
||||||
|
),
|
||||||
|
("DC Pre-trial Services", "DC Pre-trial Services"),
|
||||||
|
("Defense Nuclear Facilities Safety Board", "Defense Nuclear Facilities Safety Board"),
|
||||||
|
("Delta Regional Authority", "Delta Regional Authority"),
|
||||||
|
("Denali Commission", "Denali Commission"),
|
||||||
|
("Department of Agriculture", "Department of Agriculture"),
|
||||||
|
("Department of Commerce", "Department of Commerce"),
|
||||||
|
("Department of Defense", "Department of Defense"),
|
||||||
|
("Department of Education", "Department of Education"),
|
||||||
|
("Department of Energy", "Department of Energy"),
|
||||||
|
("Department of Health and Human Services", "Department of Health and Human Services"),
|
||||||
|
("Department of Homeland Security", "Department of Homeland Security"),
|
||||||
|
("Department of Housing and Urban Development", "Department of Housing and Urban Development"),
|
||||||
|
("Department of Justice", "Department of Justice"),
|
||||||
|
("Department of Labor", "Department of Labor"),
|
||||||
|
("Department of State", "Department of State"),
|
||||||
|
("Department of the Interior", "Department of the Interior"),
|
||||||
|
("Department of the Treasury", "Department of the Treasury"),
|
||||||
|
("Department of Transportation", "Department of Transportation"),
|
||||||
|
("Department of Veterans Affairs", "Department of Veterans Affairs"),
|
||||||
|
("Director of National Intelligence", "Director of National Intelligence"),
|
||||||
|
("Dwight D. Eisenhower Memorial Commission", "Dwight D. Eisenhower Memorial Commission"),
|
||||||
|
("Election Assistance Commission", "Election Assistance Commission"),
|
||||||
|
("Environmental Protection Agency", "Environmental Protection Agency"),
|
||||||
|
("Equal Employment Opportunity Commission", "Equal Employment Opportunity Commission"),
|
||||||
|
("Executive Office of the President", "Executive Office of the President"),
|
||||||
|
("Export-Import Bank of the United States", "Export-Import Bank of the United States"),
|
||||||
|
("Export/Import Bank of the U.S.", "Export/Import Bank of the U.S."),
|
||||||
|
("Farm Credit Administration", "Farm Credit Administration"),
|
||||||
|
("Farm Credit System Insurance Corporation", "Farm Credit System Insurance Corporation"),
|
||||||
|
("Federal Communications Commission", "Federal Communications Commission"),
|
||||||
|
("Federal Deposit Insurance Corporation", "Federal Deposit Insurance Corporation"),
|
||||||
|
("Federal Election Commission", "Federal Election Commission"),
|
||||||
|
("Federal Energy Regulatory Commission", "Federal Energy Regulatory Commission"),
|
||||||
|
(
|
||||||
|
"Federal Financial Institutions Examination Council",
|
||||||
|
"Federal Financial Institutions Examination Council",
|
||||||
|
),
|
||||||
|
("Federal Housing Finance Agency", "Federal Housing Finance Agency"),
|
||||||
|
("Federal Judiciary", "Federal Judiciary"),
|
||||||
|
("Federal Labor Relations Authority", "Federal Labor Relations Authority"),
|
||||||
|
("Federal Maritime Commission", "Federal Maritime Commission"),
|
||||||
|
("Federal Mediation and Conciliation Service", "Federal Mediation and Conciliation Service"),
|
||||||
|
(
|
||||||
|
"Federal Mine Safety and Health Review Commission",
|
||||||
|
"Federal Mine Safety and Health Review Commission",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Federal Permitting Improvement Steering Council",
|
||||||
|
"Federal Permitting Improvement Steering Council",
|
||||||
|
),
|
||||||
|
("Federal Reserve Board of Governors", "Federal Reserve Board of Governors"),
|
||||||
|
("Federal Reserve System", "Federal Reserve System"),
|
||||||
|
("Federal Trade Commission", "Federal Trade Commission"),
|
||||||
|
("General Services Administration", "General Services Administration"),
|
||||||
|
("gov Administration", "gov Administration"),
|
||||||
|
("Government Accountability Office", "Government Accountability Office"),
|
||||||
|
("Government Publishing Office", "Government Publishing Office"),
|
||||||
|
("Gulf Coast Ecosystem Restoration Council", "Gulf Coast Ecosystem Restoration Council"),
|
||||||
|
("Harry S Truman Scholarship Foundation", "Harry S Truman Scholarship Foundation"),
|
||||||
|
("Harry S. Truman Scholarship Foundation", "Harry S. Truman Scholarship Foundation"),
|
||||||
|
("Institute of Museum and Library Services", "Institute of Museum and Library Services"),
|
||||||
|
("Institute of Peace", "Institute of Peace"),
|
||||||
|
("Inter-American Foundation", "Inter-American Foundation"),
|
||||||
|
(
|
||||||
|
"International Boundary and Water Commission: United States and Mexico",
|
||||||
|
"International Boundary and Water Commission: United States and Mexico",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"International Boundary Commission: United States and Canada",
|
||||||
|
"International Boundary Commission: United States and Canada",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"International Joint Commission: United States and Canada",
|
||||||
|
"International Joint Commission: United States and Canada",
|
||||||
|
),
|
||||||
|
("James Madison Memorial Fellowship Foundation", "James Madison Memorial Fellowship Foundation"),
|
||||||
|
("Japan-United States Friendship Commission", "Japan-United States Friendship Commission"),
|
||||||
|
("Japan-US Friendship Commission", "Japan-US Friendship Commission"),
|
||||||
|
("John F. Kennedy Center for Performing Arts", "John F. Kennedy Center for Performing Arts"),
|
||||||
|
(
|
||||||
|
"John F. Kennedy Center for the Performing Arts",
|
||||||
|
"John F. Kennedy Center for the Performing Arts",
|
||||||
|
),
|
||||||
|
("Legal Services Corporation", "Legal Services Corporation"),
|
||||||
|
("Legislative Branch", "Legislative Branch"),
|
||||||
|
("Library of Congress", "Library of Congress"),
|
||||||
|
("Marine Mammal Commission", "Marine Mammal Commission"),
|
||||||
|
(
|
||||||
|
"Medicaid and CHIP Payment and Access Commission",
|
||||||
|
"Medicaid and CHIP Payment and Access Commission",
|
||||||
|
),
|
||||||
|
("Medical Payment Advisory Commission", "Medical Payment Advisory Commission"),
|
||||||
|
("Medicare Payment Advisory Commission", "Medicare Payment Advisory Commission"),
|
||||||
|
("Merit Systems Protection Board", "Merit Systems Protection Board"),
|
||||||
|
("Millennium Challenge Corporation", "Millennium Challenge Corporation"),
|
||||||
|
(
|
||||||
|
"Morris K. Udall and Stewart L. Udall Foundation",
|
||||||
|
"Morris K. Udall and Stewart L. Udall Foundation",
|
||||||
|
),
|
||||||
|
("National Aeronautics and Space Administration", "National Aeronautics and Space Administration"),
|
||||||
|
("National Archives and Records Administration", "National Archives and Records Administration"),
|
||||||
|
("National Capital Planning Commission", "National Capital Planning Commission"),
|
||||||
|
("National Council on Disability", "National Council on Disability"),
|
||||||
|
("National Credit Union Administration", "National Credit Union Administration"),
|
||||||
|
("National Endowment for the Arts", "National Endowment for the Arts"),
|
||||||
|
("National Endowment for the Humanities", "National Endowment for the Humanities"),
|
||||||
|
(
|
||||||
|
"National Foundation on the Arts and the Humanities",
|
||||||
|
"National Foundation on the Arts and the Humanities",
|
||||||
|
),
|
||||||
|
("National Gallery of Art", "National Gallery of Art"),
|
||||||
|
("National Indian Gaming Commission", "National Indian Gaming Commission"),
|
||||||
|
("National Labor Relations Board", "National Labor Relations Board"),
|
||||||
|
("National Mediation Board", "National Mediation Board"),
|
||||||
|
("National Science Foundation", "National Science Foundation"),
|
||||||
|
(
|
||||||
|
"National Security Commission on Artificial Intelligence",
|
||||||
|
"National Security Commission on Artificial Intelligence",
|
||||||
|
),
|
||||||
|
("National Transportation Safety Board", "National Transportation Safety Board"),
|
||||||
|
(
|
||||||
|
"Networking Information Technology Research and Development",
|
||||||
|
"Networking Information Technology Research and Development",
|
||||||
|
),
|
||||||
|
("Non-Federal Agency", "Non-Federal Agency"),
|
||||||
|
("Northern Border Regional Commission", "Northern Border Regional Commission"),
|
||||||
|
("Nuclear Regulatory Commission", "Nuclear Regulatory Commission"),
|
||||||
|
("Nuclear Safety Oversight Committee", "Nuclear Safety Oversight Committee"),
|
||||||
|
("Nuclear Waste Technical Review Board", "Nuclear Waste Technical Review Board"),
|
||||||
|
(
|
||||||
|
"Occupational Safety & Health Review Commission",
|
||||||
|
"Occupational Safety & Health Review Commission",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Occupational Safety and Health Review Commission",
|
||||||
|
"Occupational Safety and Health Review Commission",
|
||||||
|
),
|
||||||
|
("Office of Compliance", "Office of Compliance"),
|
||||||
|
("Office of Congressional Workplace Rights", "Office of Congressional Workplace Rights"),
|
||||||
|
("Office of Government Ethics", "Office of Government Ethics"),
|
||||||
|
("Office of Navajo and Hopi Indian Relocation", "Office of Navajo and Hopi Indian Relocation"),
|
||||||
|
("Office of Personnel Management", "Office of Personnel Management"),
|
||||||
|
("Open World Leadership Center", "Open World Leadership Center"),
|
||||||
|
("Overseas Private Investment Corporation", "Overseas Private Investment Corporation"),
|
||||||
|
("Peace Corps", "Peace Corps"),
|
||||||
|
("Pension Benefit Guaranty Corporation", "Pension Benefit Guaranty Corporation"),
|
||||||
|
("Postal Regulatory Commission", "Postal Regulatory Commission"),
|
||||||
|
("Presidio Trust", "Presidio Trust"),
|
||||||
|
("Privacy and Civil Liberties Oversight Board", "Privacy and Civil Liberties Oversight Board"),
|
||||||
|
("Public Buildings Reform Board", "Public Buildings Reform Board"),
|
||||||
|
(
|
||||||
|
"Public Defender Service for the District of Columbia",
|
||||||
|
"Public Defender Service for the District of Columbia",
|
||||||
|
),
|
||||||
|
("Railroad Retirement Board", "Railroad Retirement Board"),
|
||||||
|
("Securities and Exchange Commission", "Securities and Exchange Commission"),
|
||||||
|
("Selective Service System", "Selective Service System"),
|
||||||
|
("Small Business Administration", "Small Business Administration"),
|
||||||
|
("Smithsonian Institution", "Smithsonian Institution"),
|
||||||
|
("Social Security Administration", "Social Security Administration"),
|
||||||
|
("Social Security Advisory Board", "Social Security Advisory Board"),
|
||||||
|
("Southeast Crescent Regional Commission", "Southeast Crescent Regional Commission"),
|
||||||
|
("Southwest Border Regional Commission", "Southwest Border Regional Commission"),
|
||||||
|
("State Justice Institute", "State Justice Institute"),
|
||||||
|
("State, Local, and Tribal Government", "State, Local, and Tribal Government"),
|
||||||
|
("Stennis Center for Public Service", "Stennis Center for Public Service"),
|
||||||
|
("Surface Transportation Board", "Surface Transportation Board"),
|
||||||
|
("Tennessee Valley Authority", "Tennessee Valley Authority"),
|
||||||
|
("The Executive Office of the President", "The Executive Office of the President"),
|
||||||
|
("The Intelligence Community", "The Intelligence Community"),
|
||||||
|
("The Legislative Branch", "The Legislative Branch"),
|
||||||
|
("The Supreme Court", "The Supreme Court"),
|
||||||
|
(
|
||||||
|
"The United States World War One Centennial Commission",
|
||||||
|
"The United States World War One Centennial Commission",
|
||||||
|
),
|
||||||
|
("U.S. Access Board", "U.S. Access Board"),
|
||||||
|
("U.S. Agency for Global Media", "U.S. Agency for Global Media"),
|
||||||
|
("U.S. Agency for International Development", "U.S. Agency for International Development"),
|
||||||
|
("U.S. Capitol Police", "U.S. Capitol Police"),
|
||||||
|
("U.S. Chemical Safety Board", "U.S. Chemical Safety Board"),
|
||||||
|
(
|
||||||
|
"U.S. China Economic and Security Review Commission",
|
||||||
|
"U.S. China Economic and Security Review Commission",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"U.S. Commission for the Preservation of Americas Heritage Abroad",
|
||||||
|
"U.S. Commission for the Preservation of Americas Heritage Abroad",
|
||||||
|
),
|
||||||
|
("U.S. Commission of Fine Arts", "U.S. Commission of Fine Arts"),
|
||||||
|
("U.S. Commission on Civil Rights", "U.S. Commission on Civil Rights"),
|
||||||
|
(
|
||||||
|
"U.S. Commission on International Religious Freedom",
|
||||||
|
"U.S. Commission on International Religious Freedom",
|
||||||
|
),
|
||||||
|
("U.S. Courts", "U.S. Courts"),
|
||||||
|
("U.S. Department of Agriculture", "U.S. Department of Agriculture"),
|
||||||
|
("U.S. Interagency Council on Homelessness", "U.S. Interagency Council on Homelessness"),
|
||||||
|
("U.S. International Trade Commission", "U.S. International Trade Commission"),
|
||||||
|
("U.S. Nuclear Waste Technical Review Board", "U.S. Nuclear Waste Technical Review Board"),
|
||||||
|
("U.S. Office of Special Counsel", "U.S. Office of Special Counsel"),
|
||||||
|
("U.S. Peace Corps", "U.S. Peace Corps"),
|
||||||
|
("U.S. Postal Service", "U.S. Postal Service"),
|
||||||
|
("U.S. Semiquincentennial Commission", "U.S. Semiquincentennial Commission"),
|
||||||
|
("U.S. Trade and Development Agency", "U.S. Trade and Development Agency"),
|
||||||
|
(
|
||||||
|
"U.S.-China Economic and Security Review Commission",
|
||||||
|
"U.S.-China Economic and Security Review Commission",
|
||||||
|
),
|
||||||
|
("Udall Foundation", "Udall Foundation"),
|
||||||
|
("United States AbilityOne", "United States AbilityOne"),
|
||||||
|
("United States Access Board", "United States Access Board"),
|
||||||
|
("United States African Development Foundation", "United States African Development Foundation"),
|
||||||
|
("United States Agency for Global Media", "United States Agency for Global Media"),
|
||||||
|
("United States Arctic Research Commission", "United States Arctic Research Commission"),
|
||||||
|
("United States Global Change Research Program", "United States Global Change Research Program"),
|
||||||
|
("United States Holocaust Memorial Museum", "United States Holocaust Memorial Museum"),
|
||||||
|
("United States Institute of Peace", "United States Institute of Peace"),
|
||||||
|
(
|
||||||
|
"United States Interagency Council on Homelessness",
|
||||||
|
"United States Interagency Council on Homelessness",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"United States International Development Finance Corporation",
|
||||||
|
"United States International Development Finance Corporation",
|
||||||
|
),
|
||||||
|
("United States International Trade Commission", "United States International Trade Commission"),
|
||||||
|
("United States Postal Service", "United States Postal Service"),
|
||||||
|
("United States Senate", "United States Senate"),
|
||||||
|
("United States Trade and Development Agency", "United States Trade and Development Agency"),
|
||||||
|
(
|
||||||
|
"Utah Reclamation Mitigation and Conservation Commission",
|
||||||
|
"Utah Reclamation Mitigation and Conservation Commission",
|
||||||
|
),
|
||||||
|
("Vietnam Education Foundation", "Vietnam Education Foundation"),
|
||||||
|
("Western Hemisphere Drug Policy Commission", "Western Hemisphere Drug Policy Commission"),
|
||||||
|
(
|
||||||
|
"Woodrow Wilson International Center for Scholars",
|
||||||
|
"Woodrow Wilson International Center for Scholars",
|
||||||
|
),
|
||||||
|
("World War I Centennial Commission", "World War I Centennial Commission"),
|
||||||
|
],
|
||||||
|
help_text="Federal agency",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="organization_name",
|
||||||
|
field=models.CharField(blank=True, db_index=True, help_text="Organization name", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="tribe_name",
|
||||||
|
field=models.CharField(blank=True, help_text="Name of tribe", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="urbanization",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Urbanization (required for Puerto Rico only)",
|
||||||
|
null=True,
|
||||||
|
verbose_name="Urbanization (required for Puerto Rico only)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="cc",
|
||||||
|
field=models.CharField(help_text="Contact's country code"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="city",
|
||||||
|
field=models.CharField(help_text="Contact's city"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="email",
|
||||||
|
field=models.EmailField(help_text="Contact's email address", max_length=254),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="fax",
|
||||||
|
field=phonenumber_field.modelfields.PhoneNumberField(
|
||||||
|
help_text="Contact's fax number (null ok). Must be in ITU.E164.2005 format.",
|
||||||
|
max_length=128,
|
||||||
|
null=True,
|
||||||
|
region=None,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="name",
|
||||||
|
field=models.CharField(help_text="Contact's full name"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="org",
|
||||||
|
field=models.CharField(help_text="Contact's organization (null ok)", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="pc",
|
||||||
|
field=models.CharField(help_text="Contact's postal code"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="pw",
|
||||||
|
field=models.CharField(help_text="Contact's authorization code. 16 characters minimum."),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="sp",
|
||||||
|
field=models.CharField(help_text="Contact's state or province"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="street1",
|
||||||
|
field=models.CharField(help_text="Contact's street"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="street2",
|
||||||
|
field=models.CharField(help_text="Contact's street (null ok)", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="street3",
|
||||||
|
field=models.CharField(help_text="Contact's street (null ok)", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="voice",
|
||||||
|
field=phonenumber_field.modelfields.PhoneNumberField(
|
||||||
|
help_text="Contact's phone number. Must be in ITU.E164.2005 format", max_length=128, region=None
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="address_line",
|
||||||
|
field=models.CharField(blank=True, help_text="Street address", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="city",
|
||||||
|
field=models.CharField(blank=True, help_text="City", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="domain_name",
|
||||||
|
field=models.CharField(blank=True, null=True, verbose_name="Domain name"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="email",
|
||||||
|
field=models.EmailField(blank=True, help_text="Email", max_length=254, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="federal_agency",
|
||||||
|
field=models.CharField(blank=True, help_text="Federal agency", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="federal_type",
|
||||||
|
field=models.CharField(blank=True, help_text="Federal government branch", max_length=50, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="first_name",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True, db_index=True, help_text="First name", null=True, verbose_name="first name / given name"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="last_name",
|
||||||
|
field=models.CharField(blank=True, help_text="Last name", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="middle_name",
|
||||||
|
field=models.CharField(blank=True, help_text="Middle name (optional)", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="organization_name",
|
||||||
|
field=models.CharField(blank=True, db_index=True, help_text="Organization name", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="organization_type",
|
||||||
|
field=models.CharField(blank=True, help_text="Type of organization", max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="phone",
|
||||||
|
field=models.CharField(blank=True, help_text="Phone", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="title",
|
||||||
|
field=models.CharField(blank=True, help_text="Title", null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="transitiondomain",
|
||||||
|
name="username",
|
||||||
|
field=models.CharField(help_text="Username - this will be an email address", verbose_name="Username"),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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"),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,9 +1,9 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
|
|
||||||
|
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
|
||||||
|
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class Contact(TimeStampedModel):
|
class Contact(TimeStampedModel):
|
||||||
"""Contact information follows a similar pattern for each contact."""
|
"""Contact information follows a similar pattern for each contact."""
|
||||||
|
@ -15,23 +15,23 @@ class Contact(TimeStampedModel):
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
)
|
)
|
||||||
|
|
||||||
first_name = models.TextField(
|
first_name = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name="first name / given name",
|
verbose_name="first name / given name",
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
middle_name = models.TextField(
|
middle_name = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
last_name = models.TextField(
|
last_name = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name="last name / family name",
|
verbose_name="last name / family name",
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
title = models.TextField(
|
title = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name="title or role in your organization",
|
verbose_name="title or role in your organization",
|
||||||
|
|
|
@ -416,13 +416,13 @@ class DomainApplication(TimeStampedModel):
|
||||||
help_text="Is the tribe recognized by a state",
|
help_text="Is the tribe recognized by a state",
|
||||||
)
|
)
|
||||||
|
|
||||||
tribe_name = models.TextField(
|
tribe_name = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Name of tribe",
|
help_text="Name of tribe",
|
||||||
)
|
)
|
||||||
|
|
||||||
federal_agency = models.TextField(
|
federal_agency = models.CharField(
|
||||||
choices=AGENCY_CHOICES,
|
choices=AGENCY_CHOICES,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
@ -443,25 +443,25 @@ class DomainApplication(TimeStampedModel):
|
||||||
help_text="Is your organization an election office?",
|
help_text="Is your organization an election office?",
|
||||||
)
|
)
|
||||||
|
|
||||||
organization_name = models.TextField(
|
organization_name = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Organization name",
|
help_text="Organization name",
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
address_line1 = models.TextField(
|
address_line1 = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Street address",
|
help_text="Street address",
|
||||||
verbose_name="Address line 1",
|
verbose_name="Address line 1",
|
||||||
)
|
)
|
||||||
address_line2 = models.TextField(
|
address_line2 = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Street address line 2 (optional)",
|
help_text="Street address line 2 (optional)",
|
||||||
verbose_name="Address line 2",
|
verbose_name="Address line 2",
|
||||||
)
|
)
|
||||||
city = models.TextField(
|
city = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="City",
|
help_text="City",
|
||||||
|
@ -480,7 +480,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
help_text="Zip code",
|
help_text="Zip code",
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
urbanization = models.TextField(
|
urbanization = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Urbanization (required for Puerto Rico only)",
|
help_text="Urbanization (required for Puerto Rico only)",
|
||||||
|
|
|
@ -67,13 +67,13 @@ class DomainInformation(TimeStampedModel):
|
||||||
help_text="Is the tribe recognized by a state",
|
help_text="Is the tribe recognized by a state",
|
||||||
)
|
)
|
||||||
|
|
||||||
tribe_name = models.TextField(
|
tribe_name = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Name of tribe",
|
help_text="Name of tribe",
|
||||||
)
|
)
|
||||||
|
|
||||||
federal_agency = models.TextField(
|
federal_agency = models.CharField(
|
||||||
choices=AGENCY_CHOICES,
|
choices=AGENCY_CHOICES,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
@ -94,25 +94,25 @@ class DomainInformation(TimeStampedModel):
|
||||||
help_text="Is your organization an election office?",
|
help_text="Is your organization an election office?",
|
||||||
)
|
)
|
||||||
|
|
||||||
organization_name = models.TextField(
|
organization_name = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Organization name",
|
help_text="Organization name",
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
address_line1 = models.TextField(
|
address_line1 = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Street address",
|
help_text="Street address",
|
||||||
verbose_name="Street address",
|
verbose_name="Street address",
|
||||||
)
|
)
|
||||||
address_line2 = models.TextField(
|
address_line2 = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Street address line 2 (optional)",
|
help_text="Street address line 2 (optional)",
|
||||||
verbose_name="Street address line 2 (optional)",
|
verbose_name="Street address line 2 (optional)",
|
||||||
)
|
)
|
||||||
city = models.TextField(
|
city = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="City",
|
help_text="City",
|
||||||
|
@ -132,7 +132,7 @@ class DomainInformation(TimeStampedModel):
|
||||||
help_text="Zip code",
|
help_text="Zip code",
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
urbanization = models.TextField(
|
urbanization = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Urbanization (required for Puerto Rico only)",
|
help_text="Urbanization (required for Puerto Rico only)",
|
||||||
|
|
|
@ -59,22 +59,22 @@ class PublicContact(TimeStampedModel):
|
||||||
related_name="contacts",
|
related_name="contacts",
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.TextField(null=False, help_text="Contact's full name")
|
name = models.CharField(null=False, help_text="Contact's full name")
|
||||||
org = models.TextField(null=True, help_text="Contact's organization (null ok)")
|
org = models.CharField(null=True, help_text="Contact's organization (null ok)")
|
||||||
street1 = models.TextField(null=False, help_text="Contact's street")
|
street1 = models.CharField(null=False, help_text="Contact's street")
|
||||||
street2 = models.TextField(null=True, help_text="Contact's street (null ok)")
|
street2 = models.CharField(null=True, help_text="Contact's street (null ok)")
|
||||||
street3 = models.TextField(null=True, help_text="Contact's street (null ok)")
|
street3 = models.CharField(null=True, help_text="Contact's street (null ok)")
|
||||||
city = models.TextField(null=False, help_text="Contact's city")
|
city = models.CharField(null=False, help_text="Contact's city")
|
||||||
sp = models.TextField(null=False, help_text="Contact's state or province")
|
sp = models.CharField(null=False, help_text="Contact's state or province")
|
||||||
pc = models.TextField(null=False, help_text="Contact's postal code")
|
pc = models.CharField(null=False, help_text="Contact's postal code")
|
||||||
cc = models.TextField(null=False, help_text="Contact's country code")
|
cc = models.CharField(null=False, help_text="Contact's country code")
|
||||||
email = models.TextField(null=False, help_text="Contact's email address")
|
email = models.EmailField(null=False, help_text="Contact's email address")
|
||||||
voice = models.TextField(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 = models.TextField(
|
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.",
|
||||||
)
|
)
|
||||||
pw = models.TextField(null=False, help_text="Contact's authorization code. 16 characters minimum.")
|
pw = models.CharField(null=False, help_text="Contact's authorization code. 16 characters minimum.")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_registrant(cls):
|
def get_default_registrant(cls):
|
||||||
|
|
|
@ -17,13 +17,13 @@ class TransitionDomain(TimeStampedModel):
|
||||||
# classes that import TransitionDomain
|
# classes that import TransitionDomain
|
||||||
StatusChoices = StatusChoices
|
StatusChoices = StatusChoices
|
||||||
|
|
||||||
username = models.TextField(
|
username = models.CharField(
|
||||||
null=False,
|
null=False,
|
||||||
blank=False,
|
blank=False,
|
||||||
verbose_name="Username",
|
verbose_name="Username",
|
||||||
help_text="Username - this will be an email address",
|
help_text="Username - this will be an email address",
|
||||||
)
|
)
|
||||||
domain_name = models.TextField(
|
domain_name = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name="Domain name",
|
verbose_name="Domain name",
|
||||||
|
@ -49,25 +49,25 @@ class TransitionDomain(TimeStampedModel):
|
||||||
verbose_name="Processed",
|
verbose_name="Processed",
|
||||||
help_text="Indicates whether this TransitionDomain was already processed",
|
help_text="Indicates whether this TransitionDomain was already processed",
|
||||||
)
|
)
|
||||||
organization_type = models.TextField(
|
organization_type = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Type of organization",
|
help_text="Type of organization",
|
||||||
)
|
)
|
||||||
organization_name = models.TextField(
|
organization_name = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Organization name",
|
help_text="Organization name",
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
federal_type = models.TextField(
|
federal_type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Federal government branch",
|
help_text="Federal government branch",
|
||||||
)
|
)
|
||||||
federal_agency = models.TextField(
|
federal_agency = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Federal agency",
|
help_text="Federal agency",
|
||||||
|
@ -80,44 +80,44 @@ class TransitionDomain(TimeStampedModel):
|
||||||
null=True,
|
null=True,
|
||||||
help_text=("Duplication of registry's expiration " "date saved for ease of reporting"),
|
help_text=("Duplication of registry's expiration " "date saved for ease of reporting"),
|
||||||
)
|
)
|
||||||
first_name = models.TextField(
|
first_name = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="First name",
|
help_text="First name",
|
||||||
verbose_name="first name / given name",
|
verbose_name="first name / given name",
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
middle_name = models.TextField(
|
middle_name = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Middle name (optional)",
|
help_text="Middle name (optional)",
|
||||||
)
|
)
|
||||||
last_name = models.TextField(
|
last_name = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Last name",
|
help_text="Last name",
|
||||||
)
|
)
|
||||||
title = models.TextField(
|
title = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Title",
|
help_text="Title",
|
||||||
)
|
)
|
||||||
email = models.TextField(
|
email = models.EmailField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Email",
|
help_text="Email",
|
||||||
)
|
)
|
||||||
phone = models.TextField(
|
phone = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Phone",
|
help_text="Phone",
|
||||||
)
|
)
|
||||||
address_line = models.TextField(
|
address_line = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Street address",
|
help_text="Street address",
|
||||||
)
|
)
|
||||||
city = models.TextField(
|
city = models.CharField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="City",
|
help_text="City",
|
||||||
|
|
|
@ -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"""
|
||||||
|
|
|
@ -26,6 +26,7 @@ def write_header(writer, columns):
|
||||||
def get_domain_infos(filter_condition, sort_fields):
|
def get_domain_infos(filter_condition, sort_fields):
|
||||||
domain_infos = (
|
domain_infos = (
|
||||||
DomainInformation.objects.select_related("domain", "authorizing_official")
|
DomainInformation.objects.select_related("domain", "authorizing_official")
|
||||||
|
.prefetch_related("domain__permissions")
|
||||||
.filter(**filter_condition)
|
.filter(**filter_condition)
|
||||||
.order_by(*sort_fields)
|
.order_by(*sort_fields)
|
||||||
)
|
)
|
||||||
|
@ -49,6 +50,7 @@ def parse_row(columns, domain_info: DomainInformation, security_emails_dict=None
|
||||||
|
|
||||||
# Domain should never be none when parsing this information
|
# Domain should never be none when parsing this information
|
||||||
if domain_info.domain is None:
|
if domain_info.domain is None:
|
||||||
|
logger.error("Attemting to parse row for csv exports but Domain is none in a DomainInfo")
|
||||||
raise ValueError("Domain is none")
|
raise ValueError("Domain is none")
|
||||||
|
|
||||||
domain = domain_info.domain # type: ignore
|
domain = domain_info.domain # type: ignore
|
||||||
|
@ -127,15 +129,6 @@ def _get_security_emails(sec_contact_ids):
|
||||||
return security_emails_dict
|
return security_emails_dict
|
||||||
|
|
||||||
|
|
||||||
def update_columns_with_domain_managers(columns, max_dm_count):
|
|
||||||
"""
|
|
||||||
Update the columns list to include "Domain manager email {#}" headers
|
|
||||||
based on the maximum domain manager count.
|
|
||||||
"""
|
|
||||||
for i in range(1, max_dm_count + 1):
|
|
||||||
columns.append(f"Domain manager email {i}")
|
|
||||||
|
|
||||||
|
|
||||||
def write_csv(
|
def write_csv(
|
||||||
writer,
|
writer,
|
||||||
columns,
|
columns,
|
||||||
|
@ -161,16 +154,26 @@ def write_csv(
|
||||||
# Reduce the memory overhead when performing the write operation
|
# Reduce the memory overhead when performing the write operation
|
||||||
paginator = Paginator(all_domain_infos, 1000)
|
paginator = Paginator(all_domain_infos, 1000)
|
||||||
|
|
||||||
if get_domain_managers and len(all_domain_infos) > 0:
|
# The maximum amount of domain managers an account has
|
||||||
# We want to get the max amont of domain managers an
|
# We get the max so we can set the column header accurately
|
||||||
# account has to set the column header dynamically
|
max_dm_count = 0
|
||||||
max_dm_count = max(len(domain_info.domain.permissions.all()) for domain_info in all_domain_infos)
|
total_body_rows = []
|
||||||
update_columns_with_domain_managers(columns, max_dm_count)
|
|
||||||
|
|
||||||
for page_num in paginator.page_range:
|
for page_num in paginator.page_range:
|
||||||
page = paginator.page(page_num)
|
|
||||||
rows = []
|
rows = []
|
||||||
|
page = paginator.page(page_num)
|
||||||
for domain_info in page.object_list:
|
for domain_info in page.object_list:
|
||||||
|
|
||||||
|
# Get count of all the domain managers for an account
|
||||||
|
if get_domain_managers:
|
||||||
|
dm_count = domain_info.domain.permissions.count()
|
||||||
|
if dm_count > max_dm_count:
|
||||||
|
max_dm_count = dm_count
|
||||||
|
for i in range(1, max_dm_count + 1):
|
||||||
|
column_name = f"Domain manager email {i}"
|
||||||
|
if column_name not in columns:
|
||||||
|
columns.append(column_name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
row = parse_row(columns, domain_info, security_emails_dict, get_domain_managers)
|
row = parse_row(columns, domain_info, security_emails_dict, get_domain_managers)
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
|
@ -179,11 +182,11 @@ def write_csv(
|
||||||
# It indicates that DomainInformation.domain is None.
|
# It indicates that DomainInformation.domain is None.
|
||||||
logger.error("csv_export -> Error when parsing row, domain was None")
|
logger.error("csv_export -> Error when parsing row, domain was None")
|
||||||
continue
|
continue
|
||||||
|
total_body_rows.extend(rows)
|
||||||
|
|
||||||
if should_write_header:
|
if should_write_header:
|
||||||
write_header(writer, columns)
|
write_header(writer, columns)
|
||||||
|
writer.writerows(total_body_rows)
|
||||||
writer.writerows(rows)
|
|
||||||
|
|
||||||
|
|
||||||
def export_data_type_to_csv(csv_file):
|
def export_data_type_to_csv(csv_file):
|
||||||
|
|
|
@ -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