Support 202201 (#1111)

* remove survey notifications

* substantially rate limit unauthenticated /api/ queries to encourage authenticated queries #853

* move api throttle class configuration to settings (#853)

* #722 with a more generic validation approach

* Add organisations and registered users to "Global System Statistics" in footer #620

* poetry relock

* linting

* regen docs

* fix test data

Co-authored-by: Stefan Pratter <stefan@20c.com>
Co-authored-by: David Poarch <dpoarch@20c.com>
This commit is contained in:
Matt Griswold
2022-02-08 13:14:27 -06:00
committed by GitHub
parent 7fea18af92
commit e2619a001f
62 changed files with 946 additions and 296 deletions
+5 -1
View File
@@ -1,4 +1,4 @@
Generated on 2022-01-11 07:58:23.871466
Generated on 2022-02-07 09:42:46.413503
## _db_command.py
@@ -115,6 +115,10 @@ Checks entity status integrity (looks for orphaned entities).
Restore soft-deleted objects.
## pdb_validate_data.py
# Classes
## pdb_whois.py
Command line whois.
+1 -1
View File
@@ -1,4 +1,4 @@
Generated on 2022-01-11 07:58:23.871466
Generated on 2022-02-07 09:42:46.413503
## [admin.py](/docs/dev/modules/admin.py.md)
+13 -1
View File
@@ -1,4 +1,4 @@
Generated from admin.py on 2022-01-11 07:58:24.072700
Generated from admin.py on 2022-02-07 09:42:46.681191
# peeringdb_server.admin
@@ -243,6 +243,18 @@ These attributes / properties will be available on instances of the class
- media (`@property`): None
### Methods
#### clean
`def clean(self)`
Hook for doing any extra form-wide cleaning after Field.clean() has been
called on every field. Any ValidationError raised by this method will
not be associated with a particular field; it will have a special-case
association with the field named '__all__'.
---
## FacilityAdmin
```
+16 -1
View File
@@ -1,4 +1,4 @@
Generated from admin_commandline_tools.py on 2022-01-11 07:58:24.072700
Generated from admin_commandline_tools.py on 2022-02-07 09:42:46.681191
# peeringdb_server.admin_commandline_tools
@@ -108,6 +108,21 @@ ToolUndelete(peeringdb_server.admin_commandline_tools.CommandLineToolWrapper)
Allows restoration of an object object and it's child objects.
### Instanced Attributes
These attributes / properties will be available on instances of the class
- description (`@property`): None
## ToolValidateData
```
ToolValidateData(peeringdb_server.admin_commandline_tools.CommandLineToolWrapper)
```
Validate data in the database.
### Instanced Attributes
These attributes / properties will be available on instances of the class
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from api_cache.py on 2022-01-11 07:58:24.072700
Generated from api_cache.py on 2022-02-07 09:42:46.681191
# peeringdb_server.api_cache
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from api_key_views.py on 2022-01-11 07:58:24.072700
Generated from api_key_views.py on 2022-02-07 09:42:46.681191
# peeringdb_server.api_key_views
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from api_schema.py on 2022-01-11 07:58:24.072700
Generated from api_schema.py on 2022-02-07 09:42:46.681191
# peeringdb_server.api_schema
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from apps.py on 2022-01-11 07:58:24.072700
Generated from apps.py on 2022-02-07 09:42:46.681191
# peeringdb_server.apps
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from autocomplete_views.py on 2022-01-11 07:58:23.871466
Generated from autocomplete_views.py on 2022-02-07 09:42:46.413503
# peeringdb_server.autocomplete_views
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from context.py on 2022-01-11 07:58:24.072700
Generated from context.py on 2022-02-07 09:42:46.681191
# peeringdb_server.context
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from data_views.py on 2022-01-11 07:58:24.072700
Generated from data_views.py on 2022-02-07 09:42:46.681191
# peeringdb_server.data_views
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from db_router.py on 2022-01-11 07:58:23.871466
Generated from db_router.py on 2022-02-07 09:42:46.413503
# peeringdb_server.db_router
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from deskpro.py on 2022-01-11 07:58:24.072700
Generated from deskpro.py on 2022-02-07 09:42:46.681191
# peeringdb_server.deskpro
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from export_views.py on 2022-01-11 07:58:24.072700
Generated from export_views.py on 2022-02-07 09:42:46.681191
# peeringdb_server.export_views
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from forms.py on 2022-01-11 07:58:24.072700
Generated from forms.py on 2022-02-07 09:42:46.681191
# peeringdb_server.forms
+1 -1
View File
@@ -1,3 +1,3 @@
Generated from gendocs.py on 2022-01-11 07:58:23.871466
Generated from gendocs.py on 2022-02-07 09:42:46.413503
# peeringdb_server.gendocs
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from geo.py on 2022-01-11 07:58:24.072700
Generated from geo.py on 2022-02-07 09:42:46.681191
# peeringdb_server.geo
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from import_views.py on 2022-01-11 07:58:24.072700
Generated from import_views.py on 2022-02-07 09:42:46.681191
# peeringdb_server.import_views
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from inet.py on 2022-01-11 07:58:24.072700
Generated from inet.py on 2022-02-07 09:42:46.681191
# peeringdb_server.inet
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from ixf.py on 2022-01-11 07:58:24.072700
Generated from ixf.py on 2022-02-07 09:42:46.681191
# peeringdb_server.ixf
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from mail.py on 2022-01-11 07:58:24.072700
Generated from mail.py on 2022-02-07 09:42:46.681191
# peeringdb_server.mail
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from maintenance.py on 2022-01-11 07:58:24.072700
Generated from maintenance.py on 2022-02-07 09:42:46.681191
# peeringdb_server.maintenance
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from middleware.py on 2022-01-11 07:58:24.072700
Generated from middleware.py on 2022-02-07 09:42:46.681191
# peeringdb_server.middleware
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from mock.py on 2022-01-11 07:58:24.072700
Generated from mock.py on 2022-02-07 09:42:46.681191
# peeringdb_server.mock
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from models.py on 2022-01-11 07:58:24.072700
Generated from models.py on 2022-02-07 09:42:46.681191
# peeringdb_server.models
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from org_admin_views.py on 2022-01-11 07:58:24.072700
Generated from org_admin_views.py on 2022-02-07 09:42:46.681191
# peeringdb_server.org_admin_views
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from permissions.py on 2022-01-11 07:58:24.072700
Generated from permissions.py on 2022-02-07 09:42:46.681191
# peeringdb_server.permissions
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from renderers.py on 2022-01-11 07:58:24.072700
Generated from renderers.py on 2022-02-07 09:42:46.681191
# peeringdb_server.renderers
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from request.py on 2022-01-11 07:58:24.072700
Generated from request.py on 2022-02-07 09:42:46.681191
# peeringdb_server.request
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from rest.py on 2022-01-11 07:58:24.072700
Generated from rest.py on 2022-02-07 09:42:46.681191
# peeringdb_server.rest
+43 -1
View File
@@ -1,4 +1,4 @@
Generated from rest_throttles.py on 2022-01-11 07:58:24.072700
Generated from rest_throttles.py on 2022-02-07 09:42:46.681191
# peeringdb_server.rest_throttles
@@ -7,6 +7,48 @@ Custom rate limit handlers for the REST API.
# Classes
---
## APIAnonUserThrottle
```
APIAnonUserThrottle(rest_framework.throttling.AnonRateThrottle)
```
Rate limiting for anonymous users.
### Methods
#### allow_request
`def allow_request(self, request, view)`
Implement the check to see if the request should be throttled.
On success calls `throttle_success`.
On failure calls `throttle_failure`.
---
## APIUserThrottle
```
APIUserThrottle(rest_framework.throttling.UserRateThrottle)
```
Rate limiting for authenticated users.
### Methods
#### allow_request
`def allow_request(self, request, view)`
Implement the check to see if the request should be throttled.
On success calls `throttle_success`.
On failure calls `throttle_failure`.
---
## FilterDistanceThrottle
```
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from search.py on 2022-01-11 07:58:24.072700
Generated from search.py on 2022-02-07 09:42:46.681191
# peeringdb_server.search
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from search_indexes.py on 2022-01-11 07:58:24.072700
Generated from search_indexes.py on 2022-02-07 09:42:46.681191
# peeringdb_server.search_indexes
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from serializers.py on 2022-01-11 07:58:24.072700
Generated from serializers.py on 2022-02-07 09:42:46.681191
# peeringdb_server.serializers
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from settings.py on 2022-01-11 07:58:24.072700
Generated from settings.py on 2022-02-07 09:42:46.681191
# peeringdb_server.settings
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from signals.py on 2022-01-11 07:58:24.072700
Generated from signals.py on 2022-02-07 09:42:46.681191
# peeringdb_server.signals
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from stats.py on 2022-01-11 07:58:24.072700
Generated from stats.py on 2022-02-07 09:42:46.681191
# peeringdb_server.stats
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from urls.py on 2022-01-11 07:58:24.072700
Generated from urls.py on 2022-02-07 09:42:46.681191
# peeringdb_server.urls
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from util.py on 2022-01-11 07:58:24.072700
Generated from util.py on 2022-02-07 09:42:46.681191
# peeringdb_server.util
+1 -1
View File
@@ -1,4 +1,4 @@
Generated from validators.py on 2022-01-11 07:58:24.072700
Generated from validators.py on 2022-02-07 09:42:46.681191
# peeringdb_server.validators
+7 -1
View File
@@ -1,4 +1,4 @@
Generated from views.py on 2022-01-11 07:58:24.072700
Generated from views.py on 2022-02-07 09:42:46.681191
# peeringdb_server.views
@@ -60,6 +60,12 @@ without hitting enter (quasi autocomplete).
Triggered by hitting enter on the main search bar.
Renders a search result page.
---
## validator_result_cache
`def validator_result_cache(request, cache_id)`
Return CSV data from cache.
---
## view_about
`def view_about(request)`
Binary file not shown.

Before

Width:  |  Height:  |  Size: 969 KiB

After

Width:  |  Height:  |  Size: 969 KiB

+5 -2
View File
@@ -684,8 +684,8 @@ if API_THROTTLE_ENABLED:
REST_FRAMEWORK.update(
{
"DEFAULT_THROTTLE_CLASSES": (
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
"peeringdb_server.rest_throttles.APIAnonUserThrottle",
"peeringdb_server.rest_throttles.APIUserThrottle",
"peeringdb_server.rest_throttles.FilterDistanceThrottle",
),
"DEFAULT_THROTTLE_RATES": {
@@ -940,6 +940,9 @@ set_option("ORG_CHILDLESS_DELETE_DURATION", 90)
# n days after creation
set_option("ORG_CHILDLESS_GRACE_DURATION", 1)
# pdb_validate_data cache timeout default
set_option("PDB_VALIDATE_DATA_CACHE_TIMEOUT", 3600)
TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG
+40 -2
View File
@@ -56,6 +56,7 @@ from peeringdb_server.models import (
CommandLineTool,
DeskProTicket,
DeskProTicketCC,
EnvironmentSetting,
Facility,
GeoCoordinateCache,
InternetExchange,
@@ -1722,6 +1723,7 @@ class CommandLineToolAdmin(CustomResultLengthAdmin, admin.ModelAdmin):
"tool",
"description",
"arguments",
"download",
"result",
"user",
"created",
@@ -1731,6 +1733,24 @@ class CommandLineToolAdmin(CustomResultLengthAdmin, admin.ModelAdmin):
def has_delete_permission(self, request, obj=None):
return False
def download(self, obj):
tool = acltools.get_tool_from_data({"tool": obj.tool})
if obj.status != "done" or not tool.download_link():
return "-"
args = json.loads(obj.arguments)
tool.args = args["args"]
tool.kwargs = args["kwargs"]
url, label = tool.download_link()
url = html.escape(url)
label = html.escape(label)
return mark_safe(f'<a href="{url}">{label}</a>')
def get_urls(self):
urls = super().get_urls()
my_urls = [
@@ -2148,6 +2168,25 @@ class EnvironmentSettingForm(baseForms.ModelForm):
class Meta:
fields = ["setting", "value"]
def clean(self):
cleaned_data = super().clean()
setting = cleaned_data.get("setting")
value = cleaned_data.get("value")
if setting in ["API_THROTTLE_RATE_ANON", "API_THROTTLE_RATE_USER"]:
if re.match(
r"([/\d]+)\s*(?:minute|hour|seconds|day|week|month|year)", value
):
return cleaned_data
else:
raise ValidationError(
_(
"Invalid setting! Acceptable value is a number followed by one of the following: minute, hour, seconds, day, week, month, year. eg (10/minute, 1/hour, 5/day, 1/week, 1/month, 1/year)"
)
)
return cleaned_data
class EnvironmentSettingAdmin(CustomResultLengthAdmin, admin.ModelAdmin):
list_display = ["setting", "value", "created", "updated", "user"]
@@ -2189,8 +2228,7 @@ class GeoCoordinateAdmin(admin.ModelAdmin):
]
# Commented out via issue #860
# admin.site.register(EnvironmentSetting, EnvironmentSettingAdmin)
admin.site.register(EnvironmentSetting, EnvironmentSettingAdmin)
admin.site.register(IXFMemberData, IXFMemberDataAdmin)
admin.site.register(Facility, FacilityAdmin)
admin.site.register(InternetExchange, InternetExchangeAdmin)
+75 -10
View File
@@ -9,6 +9,7 @@ command to exposed in this manner.
import io
import json
import traceback
import reversion
from dal import autocomplete
@@ -125,7 +126,7 @@ class CommandLineToolWrapper:
def validate(self):
pass
def _run(self, user, commit=False):
def _run(self, command, commit=False):
r = io.StringIO()
if self.maintenance and commit:
@@ -141,6 +142,7 @@ class CommandLineToolWrapper:
call_command(self.tool, *self.args, stdout=r, **self.kwargs)
self.result = r.getvalue()
except Exception as inst:
traceback.print_exc()
self.result = f"[error] {inst}"
self.status = 1
finally:
@@ -148,14 +150,10 @@ class CommandLineToolWrapper:
maintenance.off()
if commit:
CommandLineTool.objects.create(
user=user,
tool=self.tool,
description=self.description,
status="done",
arguments=json.dumps({"args": self.args, "kwargs": self.kwargs}),
result=self.result,
)
command.description = self.description
command.status = "done"
command.result = self.result
command.save()
return self.result
@transaction.atomic
@@ -175,7 +173,7 @@ class CommandLineToolWrapper:
)
return self.result
CommandLineTool.objects.create(
self.cmd_instance = CommandLineTool.objects.create(
user=user,
tool=self.tool,
description=self.description,
@@ -191,11 +189,15 @@ class CommandLineToolWrapper:
"review once the command has finished."
)
)
return self.result
else:
with reversion.create_revision():
return self._run(user, commit=commit)
def download_link(self):
return None
# TOOL: RENUMBER LAN
@@ -457,3 +459,66 @@ class ToolIXFIXPMemberImport(CommandLineToolWrapper):
if form_data.get("ix"):
self.kwargs["ixlan"] = [form_data.get("ix").id]
@register_tool
class ToolValidateData(CommandLineToolWrapper):
"""
Validate data in the database.
"""
tool = "pdb_validate_data"
queue = 10
class Form(forms.Form):
models = []
for tag, model in REFTAG_MAP.items():
models.append((tag, model._meta.verbose_name.title()))
handleref_tag = forms.ChoiceField(
choices=models,
help_text=_("Select a handleref tag to validate"),
label=_("Object type"),
)
field_name = forms.CharField(
required=True,
help_text=_("Enter a field name to validate"),
)
exclude = forms.ChoiceField(
choices=(
(None, "--"),
("valid", _("Valid")),
("invalid", _("Invalid")),
),
required=False,
help_text=_("Exclude data based on validation result"),
)
verbose = forms.BooleanField(
required=False, initial=False, help_text=_("Verbose output")
)
@property
def description(self):
return "Validate data: {}.{}".format(self.args[0], self.args[1])
def set_arguments(self, form_data):
self.args = [
form_data.get("handleref_tag"),
form_data.get("field_name"),
]
self.kwargs = {
"exclude": form_data.get("exclude", None),
"verbose": form_data.get("verbose", False),
}
self.form_data = form_data
def download_link(self):
handleref_tag = self.args[0]
field_name = self.args[1]
return (
f"/pdb_validate_data/export/pdb_validate_data_{handleref_tag}_{field_name}.csv",
"Download validation results (CSV) (This file has an expiry date!)",
)
+9 -6
View File
@@ -227,16 +227,19 @@ class IXLanAutocomplete(AutocompleteHTMLResponse):
return qs
def get_result_label(self, item):
return '<span data-value="%d">\
return (
'<span data-value="%d">\
<div class="main">%s \
<div class="tiny suffix">%s</div>\
</div> \
<div class="sub">%s</div>\
</span>' % (
item.id,
html.escape(item.ix.name),
html.escape(item.ix.country.code),
html.escape(item.ix.name_long),
</span>'
% (
item.id,
html.escape(item.ix.name),
html.escape(item.ix.country.code),
html.escape(item.ix.name_long),
)
)
@@ -32,8 +32,7 @@ class Command(BaseCommand):
arguments = json.loads(command.arguments)
tool.kwargs = arguments.get("kwargs")
tool.args = arguments.get("args")
tool._run(command.user, commit=True)
command.delete()
tool._run(command, commit=True)
except Exception as exc:
command.status = "done"
command.result = f"Command ended with error: {exc}"
@@ -0,0 +1,158 @@
from io import StringIO
from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
from peeringdb_server import models as pdb_model
from peeringdb_server.management.commands.pdb_base_command import PeeringDBBaseCommand
class Command(PeeringDBBaseCommand):
help = "Validates data"
def add_arguments(self, parser):
parser.add_argument(
"object_handleref_tag", type=str, help="Object model handleref tag"
)
parser.add_argument("field_name", type=str, help="Handleref field name")
parser.add_argument(
"--exclude",
"-e",
choices=["valid", "invalid"],
default=None,
help="Exclude valid or invalid data",
)
parser.add_argument(
"--verbose",
action="store_true",
help="Display validation results on console",
)
parser.add_argument(
"--display-csv",
action="store_true",
help="Display validation results in CSV format on console",
)
super().add_arguments(parser)
def handle(self, *args, **options):
model = None
exclude = options.get("exclude")
self.commit = options.get("commit")
verbose = options.get("verbose")
display_csv = options.get("display_csv")
field_name = options.get("field_name")
reftag = options.get("object_handleref_tag")
valid_count = 0
invalid_count = 0
model = pdb_model.REFTAG_MAP.get(reftag)
# model does not exist
if model is None:
self.log("[error] Unknown model handleref tag: {reftag}")
return
# in preview mode truncate the result set to 20 entries
# so it can finish quickly
q = (
model.handleref.get_queryset().filter(status="ok")
if self.commit
else model.handleref.get_queryset().filter(status="ok")[:20]
)
results = []
results.append(["id", "valid"])
# check that provided field exists on provided model
try:
model._meta.get_field(field_name)
except Exception:
self.log(f"[error] Unsupported field for validation: {field_name}")
return
if self.commit and not display_csv:
self.log(
f"Starting validation for {reftag} - {field_name} with {q.count()} total objects"
)
if not self.commit and not display_csv:
self.log(
f"Starting validation in preview mode for {reftag} - {field_name} with {q.count()} total objects"
)
# validate
for item in q:
try:
item.full_clean()
if exclude == "valid":
continue
results.append([item.pk, True])
except ValidationError as exc:
# if the provided field name did not contribute to the validation
# issues it is counted as valid
if field_name not in exc.error_dict:
results.append((item.pk, True))
continue
# there were some validation issues
if exclude == "invalid":
continue
results.append((item.pk, False))
# tally results
for pk, result in results:
# first row are header columns
if pk == "id":
continue
if verbose and not display_csv:
self.log(f"{pk}: {result}")
if result:
valid_count += 1
else:
invalid_count += 1
if self.commit:
# Write the CSV output to django-cache
result_csv = StringIO()
result_csv.writelines(["%s,%s\n" % (r[0], r[1]) for r in results])
cache.delete(f"pdb_validate_data_{reftag}_{field_name}.csv")
cache.set(
f"pdb_validate_data_{reftag}_{field_name}.csv",
result_csv.getvalue(),
settings.PDB_VALIDATE_DATA_CACHE_TIMEOUT,
)
if options.get("display_csv"):
# write the CSV output to console
self.log("\n".join(["%s,%s" % (r[0], r[1]) for r in results]))
if self.commit and not options.get("display_csv"):
# write completion summary
self.log(
f"\nValidation Complete\nValid objects: {valid_count}\nInvalid objects: {invalid_count}"
)
@@ -0,0 +1,30 @@
# Generated by Django 3.2.9 on 2022-01-06 12:54
import django.core.validators
import django_inet.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("peeringdb_server", "0079_org_add_flag"),
]
operations = [
migrations.AlterField(
model_name="environmentsetting",
name="setting",
field=models.CharField(
choices=[
("API_THROTTLE_RATE_ANON", "API: Anonymous user API throttle rate"),
(
"API_THROTTLE_RATE_USER",
"API: Authenticated user API throttle rate",
),
],
max_length=255,
unique=True,
),
),
]
+46 -10
View File
@@ -96,6 +96,7 @@ COMMANDLINE_TOOLS = (
("pdb_fac_merge", _("Merge Facilities")),
("pdb_fac_merge_undo", _("Merge Facilities: UNDO")),
("pdb_undelete", _("Restore Object(s)")),
("pdb_validate_data", _("Validate Data")),
)
@@ -1641,8 +1642,15 @@ class Facility(ProtectedMixin, pdb_models.FacilityBase, GeocodeBaseMixin):
return True
def validate_phonenumbers(self):
self.tech_phone = validate_phonenumber(self.tech_phone, self.country.code)
self.sales_phone = validate_phonenumber(self.sales_phone, self.country.code)
try:
self.tech_phone = validate_phonenumber(self.tech_phone, self.country.code)
except ValidationError as exc:
raise ValidationError({"tech_phone": exc})
try:
self.sales_phone = validate_phonenumber(self.sales_phone, self.country.code)
except ValidationError as exc:
raise ValidationError({"sales_phone": exc})
@grainy_model(namespace="internetexchange", parent="org")
@@ -2180,9 +2188,23 @@ class InternetExchange(ProtectedMixin, pdb_models.InternetExchangeBase):
return r
def validate_phonenumbers(self):
self.tech_phone = validate_phonenumber(self.tech_phone, self.country.code)
self.policy_phone = validate_phonenumber(self.policy_phone, self.country.code)
self.sales_phone = validate_phonenumber(self.sales_phone, self.country.code)
try:
self.tech_phone = validate_phonenumber(self.tech_phone, self.country.code)
except ValidationError as exc:
raise ValidationError({"tech_phone": exc})
try:
self.sales_phone = validate_phonenumber(self.sales_phone, self.country.code)
except ValidationError as exc:
raise ValidationError({"sales_phone": exc})
try:
self.policy_phone = validate_phonenumber(
self.policy_phone, self.country.code
)
except ValidationError as exc:
raise ValidationError({"policy_phone": exc})
def clean(self):
self.validate_phonenumbers()
@@ -4442,7 +4464,11 @@ class NetworkContact(ProtectedMixin, pdb_models.ContactBase):
return True
def clean(self):
self.phone = validate_phonenumber(self.phone)
try:
self.phone = validate_phonenumber(self.phone)
except ValidationError as exc:
raise ValidationError({"phone": exc})
self.visible = validate_poc_visible(self.visible)
@@ -4537,7 +4563,7 @@ class NetworkFacility(pdb_models.NetworkFacilityBase):
def format_speed(value):
if value >= 1000000:
value = value / 10 ** 6
value = value / 10**6
if not value % 1:
return f"{value:.0f}T"
return f"{value:.1f}T"
@@ -5300,9 +5326,17 @@ class EnvironmentSetting(models.Model):
setting = models.CharField(
max_length=255,
choices=(
# (
# "IXF_IMPORTER_DAYS_UNTIL_TICKET",
# _("IX-F Importer: Days until DeskPRO ticket is created"),
# ),
(
"IXF_IMPORTER_DAYS_UNTIL_TICKET",
_("IX-F Importer: Days until DeskPRO ticket is created"),
"API_THROTTLE_RATE_ANON",
_("API: Anonymous user API throttle rate"),
),
(
"API_THROTTLE_RATE_USER",
_("API: Authenticated user API throttle rate"),
),
),
unique=True,
@@ -5336,7 +5370,9 @@ class EnvironmentSetting(models.Model):
)
setting_to_field = {
"IXF_IMPORTER_DAYS_UNTIL_TICKET": "value_int",
# "IXF_IMPORTER_DAYS_UNTIL_TICKET": "value_int",
"API_THROTTLE_RATE_ANON": "value_str",
"API_THROTTLE_RATE_USER": "value_str",
}
@classmethod
+31
View File
@@ -6,6 +6,7 @@ from django.conf import settings
from rest_framework import throttling
from rest_framework.exceptions import PermissionDenied
from peeringdb_server.models import EnvironmentSetting
from peeringdb_server.permissions import get_org_key_from_request, get_user_from_request
@@ -110,3 +111,33 @@ class FilterDistanceThrottle(FilterThrottle):
"""
filter_name = "distance"
class APIAnonUserThrottle(throttling.AnonRateThrottle):
"""
Rate limiting for anonymous users.
"""
filter_name = "anon"
def allow_request(self, request, view):
self.rate = EnvironmentSetting.get_setting_value("API_THROTTLE_RATE_ANON")
self.num_requests, self.duration = self.parse_rate(self.rate)
return super().allow_request(request, view)
class APIUserThrottle(throttling.UserRateThrottle):
"""
Rate limiting for authenticated users.
"""
filter_name = "user"
def allow_request(self, request, view):
self.rate = EnvironmentSetting.get_setting_value("API_THROTTLE_RATE_USER")
self.num_requests, self.duration = self.parse_rate(self.rate)
return super().allow_request(request, view)
+1 -1
View File
@@ -1162,7 +1162,7 @@ class ModelSerializer(serializers.ModelSerializer):
# unique set that caused the collision
columns = "|".join(self.Meta.fields)
m = re.findall(fr"\b({columns})\b", v)
m = re.findall(rf"\b({columns})\b", v)
# build django queryset filters we can use
# to retrieve the blocking object
+4
View File
@@ -8,6 +8,8 @@ from peeringdb_server.models import (
Network,
NetworkFacility,
NetworkIXLan,
Organization,
User,
)
@@ -23,6 +25,8 @@ def stats():
status="ok"
).count(),
"automated_nets": Network.handleref.filter(allow_ixp_update=True).count(),
"registered_users": User.objects.count(),
"organizations": Organization.objects.filter(status="ok").count(),
}
@@ -37,6 +37,9 @@
{% endfor %}
</div>
<div class="result">
{% if tool.queue and tool.cmd_instance %}
{% trans "You can monitor progress and review the results here" %}: <a href="{% url "admin:peeringdb_server_commandlinetool_change" object_id=tool.cmd_instance.id %}">{% trans "Command status" %}</a>
{% endif %}
{{ tool.pretty_result|safe }}
</div>
</form>
@@ -54,6 +54,8 @@
<div>{{ global_stats.netixlan }} {% trans "Connections to Exchanges" %}</div>
<div>{{ global_stats.netfac }} {% trans "Connections to Facilities" %}</div>
<div>{{ global_stats.automated_nets }} {% trans "Automated Networks" %}</div>
<div>{{ global_stats.registered_users }} {% trans "Registered Users" %}</div>
<div>{{ global_stats.organizations }} {% trans "Organizations" %}</div>
</div>
</div>
</div>
+10 -2
View File
@@ -46,6 +46,7 @@ from peeringdb_server.views import (
request_search,
request_translation,
resend_confirmation_mail,
validator_result_cache,
view_about,
view_advanced_search,
view_affiliate_to_org,
@@ -294,18 +295,25 @@ urlpatterns += [
name="autocomplete-admin-deleted-versions",
),
]
# Admin CSV export for pdb_validate_data command
urlpatterns += [
path(
"pdb_validate_data/export/<cache_id>",
validator_result_cache,
)
]
# Admin autocomplete for commandlinetool history
urlpatterns += [
url(
fr"^autocomplete/admin/clt-history/{tool_id}/$",
rf"^autocomplete/admin/clt-history/{tool_id}/$",
ToolHistory.as_view(),
name=f"autocomplete-admin-clt-history-{tool_id}",
)
for tool_id, ToolHistory in list(clt_history.items())
]
# Oauth2
urlpatterns += [
+20
View File
@@ -23,6 +23,7 @@ from allauth.account.models import EmailAddress
from django.conf import settings as dj_settings
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.core.cache import cache
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.db import transaction
from django.http import (
@@ -2550,3 +2551,22 @@ def network_dismiss_ixf_proposal(request, net_id, ixf_id):
ixf_member_data.save()
return JsonResponse({"status": "ok"})
def validator_result_cache(request, cache_id):
"""
Return CSV data from cache.
"""
data = cache.get(cache_id)
# If cache key doesn't exist, return 404
# Prevent downloads from non-admin in users
if not data or not request.user.is_superuser:
return view_http_error_404(request)
response = HttpResponse(
content_type="text/csv",
headers={"Content-Disposition": f'attachment; filename="{cache_id}"'},
)
response.write(data)
return response
Generated
+237 -218
View File
@@ -1,10 +1,10 @@
[[package]]
name = "asgiref"
version = "3.4.1"
version = "3.5.0"
description = "ASGI specs, helper code, and adapters"
category = "main"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.extras]
tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
@@ -57,28 +57,24 @@ typecheck = ["mypy"]
[[package]]
name = "black"
version = "21.12b0"
version = "22.1.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.6.2"
[package.dependencies]
click = ">=7.1.2"
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.9.0,<1"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = ">=0.2.6,<2.0.0"
typing-extensions = [
{version = ">=3.10.0.0", markers = "python_version < \"3.10\""},
{version = "!=3.10.0.1", markers = "python_version >= \"3.10\""},
]
tomli = ">=1.1.0"
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
python2 = ["typed-ast (>=1.4.3)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
@@ -107,7 +103,7 @@ attrs = ">=20"
[[package]]
name = "cbor2"
version = "5.4.2"
version = "5.4.2.post1"
description = "Pure Python CBOR (de)serializer with extensive tag support"
category = "main"
optional = false
@@ -157,7 +153,7 @@ six = "<=2.0.0"
[[package]]
name = "charset-normalizer"
version = "2.0.10"
version = "2.0.11"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
@@ -212,11 +208,11 @@ jinja2 = "*"
[[package]]
name = "coverage"
version = "6.2"
version = "6.3.1"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.dependencies]
tomli = {version = "*", optional = true, markers = "extra == \"toml\""}
@@ -291,7 +287,7 @@ python-versions = "*"
[[package]]
name = "django"
version = "3.2.11"
version = "3.2.12"
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false
@@ -308,7 +304,7 @@ bcrypt = ["bcrypt"]
[[package]]
name = "django-allauth"
version = "0.47.0"
version = "0.48.0"
description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication."
category = "main"
optional = false
@@ -323,7 +319,7 @@ requests-oauthlib = ">=0.3.0"
[[package]]
name = "django-autocomplete-light"
version = "3.8.2"
version = "3.9.1"
description = "Fresh autocompletes for Django"
category = "main"
optional = false
@@ -384,11 +380,11 @@ test = ["pytest", "pytest-django", "pytest-cov", "graphene-django"]
[[package]]
name = "django-crispy-forms"
version = "1.13.0"
version = "1.14.0"
description = "Best way to have Django DRY forms"
category = "main"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[[package]]
name = "django-debug-toolbar"
@@ -438,7 +434,7 @@ six = ">=1.11.0,<=2.0.0"
[[package]]
name = "django-grappelli"
version = "2.15.4"
version = "3.0.2"
description = "A jazzy skin for the Django Admin-Interface."
category = "main"
optional = false
@@ -529,7 +525,7 @@ django_inet = ">=1,<2"
[[package]]
name = "django-phonenumber-field"
version = "5.2.0"
version = "6.0.0"
description = "An international phone number field for django models."
category = "main"
optional = false
@@ -577,7 +573,7 @@ simplejson = "*"
[[package]]
name = "django-reversion"
version = "4.0.1"
version = "4.0.2"
description = "An extension to the Django web framework that provides version control for model instances."
category = "main"
optional = false
@@ -631,7 +627,7 @@ tablib = ["tablib"]
[[package]]
name = "django-two-factor-auth"
version = "1.13.1"
version = "1.13.2"
description = "Complete Two-Factor Authentication for Django"
category = "main"
optional = false
@@ -641,8 +637,8 @@ python-versions = "*"
Django = ">=2.2"
django-formtools = "*"
django-otp = ">=0.8.0"
django-phonenumber-field = ">=1.1.0,<6"
qrcode = ">=4.0.0,<6.99"
django-phonenumber-field = ">=1.1.0,<7"
qrcode = ">=4.0.0,<7.99"
[package.extras]
call = ["twilio (>=6.0)"]
@@ -789,7 +785,7 @@ dev = ["twine", "markdown", "flake8", "wheel"]
[[package]]
name = "googlemaps"
version = "4.5.3"
version = "4.6.0"
description = "Python client library for Google Maps Platform"
category = "main"
optional = false
@@ -808,11 +804,11 @@ python-versions = ">=3.6,<4.0"
[[package]]
name = "identify"
version = "2.4.2"
version = "2.4.8"
description = "File identification library for Python"
category = "dev"
optional = false
python-versions = ">=3.6.1"
python-versions = ">=3.7"
[package.extras]
license = ["ukkonen"]
@@ -827,7 +823,7 @@ python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
version = "4.10.0"
version = "4.10.1"
description = "Read metadata from Python packages"
category = "main"
optional = false
@@ -1029,16 +1025,16 @@ python-versions = "*"
[[package]]
name = "oauthlib"
version = "3.1.1"
version = "3.2.0"
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
rsa = ["cryptography (>=3.0.0,<4)"]
rsa = ["cryptography (>=3.0.0)"]
signals = ["blinker (>=1.4.0)"]
signedtoken = ["cryptography (>=3.0.0,<4)", "pyjwt (>=2.0.0,<3)"]
signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "openapi-codec"
@@ -1119,7 +1115,7 @@ PyYAML = ">=3.11"
[[package]]
name = "phonenumbers"
version = "8.12.40"
version = "8.12.42"
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
category = "main"
optional = false
@@ -1127,7 +1123,7 @@ python-versions = "*"
[[package]]
name = "pillow"
version = "9.0.0"
version = "9.0.1"
description = "Python Imaging Library (Fork)"
category = "main"
optional = false
@@ -1159,7 +1155,7 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
version = "2.16.0"
version = "2.17.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false
@@ -1258,15 +1254,14 @@ tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"]
[[package]]
name = "pyopenssl"
version = "21.0.0"
version = "22.0.0"
description = "Python wrapper module around the OpenSSL library"
category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
python-versions = ">=3.6"
[package.dependencies]
cryptography = ">=3.3"
six = ">=1.5.2"
cryptography = ">=35.0"
[package.extras]
docs = ["sphinx", "sphinx-rtd-theme"]
@@ -1274,7 +1269,7 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"]
[[package]]
name = "pyparsing"
version = "3.0.6"
version = "3.0.7"
description = "Python parsing module"
category = "main"
optional = false
@@ -1285,15 +1280,15 @@ diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pyrsistent"
version = "0.18.0"
version = "0.18.1"
description = "Persistent/Functional/Immutable data structures"
category = "dev"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[[package]]
name = "pytest"
version = "6.2.5"
version = "7.0.0"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
@@ -1307,10 +1302,10 @@ iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
py = ">=1.8.2"
toml = "*"
tomli = ">=1.0.0"
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
[[package]]
name = "pytest-cov"
@@ -1357,11 +1352,11 @@ requests-mock = ">=1,<2"
[[package]]
name = "pytest-mock"
version = "3.6.1"
version = "3.7.0"
description = "Thin-wrapper around the mock package for easier use with pytest"
category = "dev"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.dependencies]
pytest = ">=5.0"
@@ -1454,21 +1449,21 @@ pyyaml = "*"
[[package]]
name = "qrcode"
version = "6.1"
version = "7.3.1"
description = "QR Code image generator"
category = "main"
optional = false
python-versions = "*"
python-versions = ">=3.6"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
six = "*"
[package.extras]
dev = ["tox", "pytest", "mock"]
all = ["zest.releaser", "tox", "pytest", "pytest", "pytest-cov", "pillow"]
dev = ["tox", "pytest"]
maintainer = ["zest.releaser"]
pil = ["pillow"]
test = ["pytest", "pytest-cov", "mock"]
test = ["pytest", "pytest-cov"]
[[package]]
name = "rdap"
@@ -1517,7 +1512,7 @@ test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.1
[[package]]
name = "requests-oauthlib"
version = "1.3.0"
version = "1.3.1"
description = "OAuthlib authentication support for Requests."
category = "main"
optional = false
@@ -1588,11 +1583,11 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "tomli"
version = "1.2.3"
version = "2.0.0"
description = "A lil' TOML parser"
category = "dev"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[[package]]
name = "twentyc.rpc"
@@ -1653,7 +1648,7 @@ python-versions = "*"
[[package]]
name = "virtualenv"
version = "20.13.0"
version = "20.13.1"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
@@ -1750,8 +1745,8 @@ content-hash = "319181f4ba388850224eb23a69489a916c1ede351fd66c652baa650f9318ae0e
[metadata.files]
asgiref = [
{file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"},
{file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"},
{file = "asgiref-3.5.0-py3-none-any.whl", hash = "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9"},
{file = "asgiref-3.5.0.tar.gz", hash = "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0"},
]
asn1crypto = [
{file = "asn1crypto-1.4.0-py2.py3-none-any.whl", hash = "sha256:4bcdf33c861c7d40bdcd74d8e4dd7661aac320fcdf40b9a3f95b4ee12fde2fa8"},
@@ -1778,8 +1773,29 @@ bcrypt = [
{file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"},
]
black = [
{file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"},
{file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"},
{file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
{file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
{file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"},
{file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"},
{file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"},
{file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"},
{file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"},
{file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"},
{file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"},
{file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"},
{file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"},
{file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"},
{file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"},
{file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"},
{file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"},
{file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"},
{file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"},
{file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"},
{file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"},
{file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"},
{file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"},
{file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"},
{file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
]
bleach = [
{file = "bleach-4.1.0-py2.py3-none-any.whl", hash = "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"},
@@ -1790,19 +1806,27 @@ cattrs = [
{file = "cattrs-1.10.0.tar.gz", hash = "sha256:211800f725cdecedcbcf4c753bbd22d248312b37d130f06045434acb7d9b34e1"},
]
cbor2 = [
{file = "cbor2-5.4.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0153635a78e62d70f26f5b3469cb8de822420eda69c996304fb3d0dc1a53d7f3"},
{file = "cbor2-5.4.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70fb47a4bca70ae2d1b2b6c9d5ed6c898494739966653f7c84737f795b26d754"},
{file = "cbor2-5.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:10f178697e66eaae51534c3ef9acce1abf2f747e63c841e8702b9453c5bc4cfe"},
{file = "cbor2-5.4.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:4c112b21fad53b2d936ef2d55c698641a6d49025c1a9cf73db3ef24684514db1"},
{file = "cbor2-5.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93baf1ee33a305acf8f1391fed4e63ea2e837b561294fd6275ce8785acb795fa"},
{file = "cbor2-5.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d9f64241bb30439f6ebde74101683e9dceecab95afe105560ac9d0e54d1b3c2e"},
{file = "cbor2-5.4.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:2757a7b624478d3b6707adf8ab7aef03d81fdaca6bea981b2323069660b9360f"},
{file = "cbor2-5.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32c1d00d8ad1a89f2358bf444fd43cacc3ca61f3c3feb1a2f2b2bea8ba4853d2"},
{file = "cbor2-5.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:79c10306d9128258dea110c01abbe9c58c48ee2ffcf995f982fb063f1a82e2ee"},
{file = "cbor2-5.4.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:0e27c52abdccadadab858437f6d98d5e8711aa84f0af7417dd1404d16127db7f"},
{file = "cbor2-5.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aec8f415f039ab3be6cb58861c21bd2202f5c0ad6537b31956da532ea74ad"},
{file = "cbor2-5.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:310c7d7925f7aa6cb90791606be17c164bbf3b28d4d17047b5d19d303f2fe817"},
{file = "cbor2-5.4.2.tar.gz", hash = "sha256:e283e70b55a049ff364cc5e648fde587e4d9b0e87e4b2664c69e639135e6b3b8"},
{file = "cbor2-5.4.2.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21a8778a92fae2fa713dfee2dc781fce64bc8fcb2e085368eff3a0b3434f83c7"},
{file = "cbor2-5.4.2.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c55b683d2e84df1da6db527faac12a9b2b844a80c0a7864088c1aaf07e4ad1d4"},
{file = "cbor2-5.4.2.post1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8b0455854dc53d816518ec5c117bf159b8d23d178ff8f655e3290192878264d5"},
{file = "cbor2-5.4.2.post1-cp310-cp310-win_amd64.whl", hash = "sha256:566b6f85fd8caf85b34b75dd7056dd0ae076334af4def0e27da805c10f941ae1"},
{file = "cbor2-5.4.2.post1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:92126b8fb5b20aa7167a5c0a84bb9562f54004af06ef601d05897dcad8a926f0"},
{file = "cbor2-5.4.2.post1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5e01b12a2f9172d31e096d65c1965a5b3b95bdb1ca91feb89741e6ce6a533a"},
{file = "cbor2-5.4.2.post1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:6930fe3f83d5f4d9f83baf9a225651591e2f97ae04579bac6598c9520f71bbcc"},
{file = "cbor2-5.4.2.post1-cp36-cp36m-win_amd64.whl", hash = "sha256:1e4f694b135688d6126988af602409e1d94dcdefbb7b242b56ba3a09779930fb"},
{file = "cbor2-5.4.2.post1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7c2a4336becc4021777df04371f119d74cf83befb604fb4e62cfb2d50dac9fbd"},
{file = "cbor2-5.4.2.post1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:231cba333cbac0e8912042f04e78b3f8eacde8ee08777e197f10f7a2e42c43af"},
{file = "cbor2-5.4.2.post1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e497c91bf107490503c1d834a04ccfc849d8b0b36ff8ee1598f10712864a4c87"},
{file = "cbor2-5.4.2.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:92e40496c33de912f16f275b88e063073343b39faa63a6d10deb6fdf5127ae44"},
{file = "cbor2-5.4.2.post1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:565fe95a720b2e999cb56d19d1d309097930144f0c85eed34a058c14cf3ac897"},
{file = "cbor2-5.4.2.post1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d140b20b0bcbdfa80a910c6832aaa13f870b7045e39f8d9b5b652ef87ce3eac5"},
{file = "cbor2-5.4.2.post1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85c048f5fc170b619127c0eb2dcc7bc832e982e3cefbaec19c028afe0d231864"},
{file = "cbor2-5.4.2.post1-cp38-cp38-win_amd64.whl", hash = "sha256:75621aaa144e5f51bea3a1c753bad11ed7f3669a086222d09975953b5df6b0bf"},
{file = "cbor2-5.4.2.post1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1c924fefd4fc7419a87463186c0c0ffc65c88635e813e02eff98751c49e43ab0"},
{file = "cbor2-5.4.2.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a54b9cfe5ddfc7d1199d2a9e37e799c0c2b01385ce837b80feaeaf912d4797f2"},
{file = "cbor2-5.4.2.post1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53239908e4e80395dada750f612536dab9d4f09b9a419479b4d69e1e2419656f"},
{file = "cbor2-5.4.2.post1-cp39-cp39-win_amd64.whl", hash = "sha256:1621f53af3b8016c991f9e27123efae54006cf57c321693f234fec9636c55d6b"},
{file = "cbor2-5.4.2.post1.tar.gz", hash = "sha256:9cf21d59604b9529d7877c8e0342a2ebaae1a07fe8ff5683dc75fec15847c797"},
]
certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
@@ -1868,8 +1892,8 @@ cfu = [
{file = "cfu-1.5.0.tar.gz", hash = "sha256:5052fdec7a808823893b73cb438c39a4f780d2c0bba8af06e02192af99424f60"},
]
charset-normalizer = [
{file = "charset-normalizer-2.0.10.tar.gz", hash = "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"},
{file = "charset_normalizer-2.0.10-py3-none-any.whl", hash = "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"},
{file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"},
{file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"},
]
click = [
{file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
@@ -1888,53 +1912,47 @@ coreschema = [
{file = "coreschema-0.0.4.tar.gz", hash = "sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"},
]
coverage = [
{file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"},
{file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"},
{file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"},
{file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"},
{file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"},
{file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"},
{file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"},
{file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"},
{file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"},
{file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"},
{file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"},
{file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"},
{file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"},
{file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"},
{file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"},
{file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"},
{file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"},
{file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"},
{file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"},
{file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"},
{file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"},
{file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"},
{file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"},
{file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"},
{file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"},
{file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"},
{file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"},
{file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"},
{file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"},
{file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"},
{file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"},
{file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"},
{file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"},
{file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"},
{file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"},
{file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"},
{file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"},
{file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"},
{file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"},
{file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"},
{file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"},
{file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"},
{file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"},
{file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"},
{file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"},
{file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"},
{file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"},
{file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"},
{file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"},
{file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"},
{file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"},
{file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"},
{file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"},
{file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"},
{file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"},
{file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"},
{file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"},
{file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"},
{file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"},
{file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"},
{file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"},
{file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"},
{file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"},
{file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"},
{file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"},
{file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"},
{file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"},
{file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"},
{file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"},
{file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"},
{file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"},
{file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"},
{file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"},
{file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"},
{file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"},
{file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"},
{file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"},
{file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"},
{file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"},
{file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"},
{file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"},
{file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"},
{file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"},
{file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"},
{file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"},
{file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"},
{file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"},
{file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"},
]
cryptography = [
{file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"},
@@ -1979,14 +1997,14 @@ distro = [
{file = "distro-1.6.0.tar.gz", hash = "sha256:83f5e5a09f9c5f68f60173de572930effbcc0287bb84fdc4426cb4168c088424"},
]
django = [
{file = "Django-3.2.11-py3-none-any.whl", hash = "sha256:0a0a37f0b93aef30c4bf3a839c187e1175bcdeb7e177341da0cb7b8194416891"},
{file = "Django-3.2.11.tar.gz", hash = "sha256:69c94abe5d6b1b088bf475e09b7b74403f943e34da107e798465d2045da27e75"},
{file = "Django-3.2.12-py3-none-any.whl", hash = "sha256:9b06c289f9ba3a8abea16c9c9505f25107809fb933676f6c891ded270039d965"},
{file = "Django-3.2.12.tar.gz", hash = "sha256:9772e6935703e59e993960832d66a614cf0233a1c5123bc6224ecc6ad69e41e2"},
]
django-allauth = [
{file = "django-allauth-0.47.0.tar.gz", hash = "sha256:2bcf09d4c6e672620981d283f555d643982ac066b71f8947dbd81882a1d7c5e8"},
{file = "django-allauth-0.48.0.tar.gz", hash = "sha256:531821ce6a2278168054add13421776c9f8e565cf39926e799fa02d6c29da920"},
]
django-autocomplete-light = [
{file = "django-autocomplete-light-3.8.2.tar.gz", hash = "sha256:25f0ea71b59a8f1f97a8a564e33e429570b0ea77c5eac81f7beb283073b4ba90"},
{file = "django-autocomplete-light-3.9.1.tar.gz", hash = "sha256:50bc562fe13c206cf53304d7f6a8b929729016ab1dae64d6176d4ccd6caff6ab"},
]
django-bootstrap3 = [
{file = "django-bootstrap3-21.2.tar.gz", hash = "sha256:9a7e95a053455c15cdcbc5dc7f9261f9aecf01d0558435b59e7b364765f2c3e2"},
@@ -2005,8 +2023,8 @@ django-countries = [
{file = "django_countries-7.2.1-py3-none-any.whl", hash = "sha256:adc965f1d348124274b7d918fc1aad5e29609758af999e1822baa9f2cc06d1b8"},
]
django-crispy-forms = [
{file = "django-crispy-forms-1.13.0.tar.gz", hash = "sha256:4dc9e5263c91c5f51766806a050ca2ec5c349d34c5cd91fcca3a1a0363b5e29e"},
{file = "django_crispy_forms-1.13.0-py3-none-any.whl", hash = "sha256:9c49d29dd9e98dc2e6e3a8b02670d72deb6edf7bc616986ca412d72c306b36a0"},
{file = "django-crispy-forms-1.14.0.tar.gz", hash = "sha256:35887b8851a931374dd697207a8f56c57a9c5cb9dbf0b9fa54314da5666cea5b"},
{file = "django_crispy_forms-1.14.0-py3-none-any.whl", hash = "sha256:bc4d2037f6de602d39c0bc452ac3029d1f5d65e88458872cc4dbc01c3a400604"},
]
django-debug-toolbar = [
{file = "django-debug-toolbar-3.2.4.tar.gz", hash = "sha256:644bbd5c428d3283aa9115722471769cac1bec189edf3a0c855fd8ff870375a9"},
@@ -2024,8 +2042,8 @@ django-grainy = [
{file = "django-grainy-1.9.1.tar.gz", hash = "sha256:a2ed568a185fdce27bc4765c732fa67a10627d0e392f7a41d645ddfcd74763f0"},
]
django-grappelli = [
{file = "django-grappelli-2.15.4.tar.gz", hash = "sha256:4c85062109fc8bd7e8399a88f146d934c57c8fba9865aa3ecd6392b9bed60ead"},
{file = "django_grappelli-2.15.4-py2.py3-none-any.whl", hash = "sha256:a076042f801bae324e259c919659b4ca653dc730fce9f055b4e5ab54e67d109d"},
{file = "django-grappelli-3.0.2.tar.gz", hash = "sha256:604cfce601a5246690e4e2d9b3c6a0db57bccbefdba5e81733f2db9a7ba62fac"},
{file = "django_grappelli-3.0.2-py2.py3-none-any.whl", hash = "sha256:c04a4a93c18e8790976def56e487a6f2d59b6894a3fe75473e0e5826d1145a04"},
]
django-handleref = [
{file = "django-handleref-1.0.1.tar.gz", hash = "sha256:f822d98a896cfae6a8d3e70448b68e6ad0da0a2a6536d0d37b61342789d25054"},
@@ -2056,8 +2074,8 @@ django-peeringdb = [
{file = "django_peeringdb-2.11.0-py3-none-any.whl", hash = "sha256:fc35b72d2cf27c7bd53fb3d0f555150a94d8386279e832c10c8a576272bc684e"},
]
django-phonenumber-field = [
{file = "django-phonenumber-field-5.2.0.tar.gz", hash = "sha256:52b2e5970133ec5ab701218b802f7ab237229854dc95fd239b7e9e77dc43731d"},
{file = "django_phonenumber_field-5.2.0-py3-none-any.whl", hash = "sha256:5547fb2b2cc690a306ba77a5038419afc8fa8298a486fb7895008e9067cc7e75"},
{file = "django-phonenumber-field-6.0.0.tar.gz", hash = "sha256:9695d3beda772c503ad4e04a4f7012a8227e9e3e4fd0ea4ffb07c43245bf4a8d"},
{file = "django_phonenumber_field-6.0.0-py3-none-any.whl", hash = "sha256:bbb9cb2e6fc53c476de40428e1354c313a040e8b2fb69ea8ead4ba41a60f926a"},
]
django-ranged-response = [
{file = "django-ranged-response-0.2.0.tar.gz", hash = "sha256:f71fff352a37316b9bead717fc76e4ddd6c9b99c4680cdf4783b9755af1cf985"},
@@ -2071,8 +2089,8 @@ django-rest-swagger = [
{file = "django_rest_swagger-2.2.0-py2.py3-none-any.whl", hash = "sha256:b039b0288bab4665cd45dc5d16f94b13911bc4ad0ed55f74ad3b90aa31c87c17"},
]
django-reversion = [
{file = "django-reversion-4.0.1.tar.gz", hash = "sha256:6991f16e5d3a972912db3d56e3a714d10b07becd566ab87f85f2e9b671981339"},
{file = "django_reversion-4.0.1-py3-none-any.whl", hash = "sha256:2e40ed41e08cdd83a05dc70a1974feface52a61ba7d289727117163052081ae6"},
{file = "django-reversion-4.0.2.tar.gz", hash = "sha256:5d33ba944dbf19c7030c9e70e3731248ae34368cf2afede10e0d08fbf89e6f8c"},
{file = "django_reversion-4.0.2-py3-none-any.whl", hash = "sha256:095ec684626a07b48a1a32640e30cae93eb2acd3c452a9111f77ac934c2efb91"},
]
django-security-keys = [
{file = "django-security-keys-1.0.1.tar.gz", hash = "sha256:1e1b76dd31bb8d147601337c2f365f6edcb629c575eb013583db7b203ea7dc75"},
@@ -2087,8 +2105,8 @@ django-tables2 = [
{file = "django_tables2-2.4.1-py2.py3-none-any.whl", hash = "sha256:50762bf3d7c61a4eb70e763c3e278650d7266bb78d0497fc8fafcf4e507c9a64"},
]
django-two-factor-auth = [
{file = "django-two-factor-auth-1.13.1.tar.gz", hash = "sha256:a20e03d256fd9fd668988545f052cedcc47e5a981888562e5e27d0bb83deae89"},
{file = "django_two_factor_auth-1.13.1-py2.py3-none-any.whl", hash = "sha256:d270d4288731233621a9462a89a8dfed2dcb86fa354125c816a89772d55f9e29"},
{file = "django-two-factor-auth-1.13.2.tar.gz", hash = "sha256:3fac266d12472ac66475dd737bb18f2992484313bf56acf5a2eea5e824291ee6"},
{file = "django_two_factor_auth-1.13.2-py2.py3-none-any.whl", hash = "sha256:44fb9f6a52dbf83229de52c6f642eb2e0feafdb919854f3dc0e7716877c340ba"},
]
django-vanilla-views = [
{file = "django-vanilla-views-3.0.0.tar.gz", hash = "sha256:b65f27b8d5de53f695a397438337be882a6c7b5d8d30333a4cf98c9239fe0b86"},
@@ -2132,22 +2150,22 @@ ghp-import = [
{file = "ghp_import-2.0.2-py3-none-any.whl", hash = "sha256:5f8962b30b20652cdffa9c5a9812f7de6bcb56ec475acac579807719bf242c46"},
]
googlemaps = [
{file = "googlemaps-4.5.3.tar.gz", hash = "sha256:cd6a4bcf6fbdbbffeb5e4cffd7f9db223864cce96c9149fb48358b62050f3a2a"},
{file = "googlemaps-4.6.0.tar.gz", hash = "sha256:2cc88cdf206deccc18e7fdfd23a0ed244209a08b2f0561fd8739c4c277325241"},
]
grainy = [
{file = "grainy-1.8.1.tar.gz", hash = "sha256:2cfd8d50b3f5cce3c463f3c5e86324442f61a7cd46dfe7b134ee926559e56556"},
]
identify = [
{file = "identify-2.4.2-py2.py3-none-any.whl", hash = "sha256:67c1e66225870dce721228176637a8ef965e8dd58450bcc7592249d0dfc4da6c"},
{file = "identify-2.4.2.tar.gz", hash = "sha256:93e8ec965e888f2212aa5c24b2b662f4832c39acb1d7196a70ea45acb626a05e"},
{file = "identify-2.4.8-py2.py3-none-any.whl", hash = "sha256:a55bdd671b6063eb837af938c250ec00bba6e610454265133b0d2db7ae718d0f"},
{file = "identify-2.4.8.tar.gz", hash = "sha256:97e839c1779f07011b84c92af183e1883d9745d532d83412cca1ca76d3808c1c"},
]
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
importlib-metadata = [
{file = "importlib_metadata-4.10.0-py3-none-any.whl", hash = "sha256:b7cf7d3fef75f1e4c80a96ca660efbd51473d7e8f39b5ab9210febc7809012a4"},
{file = "importlib_metadata-4.10.0.tar.gz", hash = "sha256:92a8b58ce734b2a4494878e0ecf7d79ccd7a128b5fc6014c401e0b61f006f0f6"},
{file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"},
{file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
@@ -2283,8 +2301,8 @@ nodeenv = [
{file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
]
oauthlib = [
{file = "oauthlib-3.1.1-py2.py3-none-any.whl", hash = "sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc"},
{file = "oauthlib-3.1.1.tar.gz", hash = "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3"},
{file = "oauthlib-3.2.0-py3-none-any.whl", hash = "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe"},
{file = "oauthlib-3.2.0.tar.gz", hash = "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2"},
]
openapi-codec = [
{file = "openapi-codec-1.3.2.tar.gz", hash = "sha256:1bce63289edf53c601ea3683120641407ff6b708803b8954c8a876fe778d2145"},
@@ -2309,42 +2327,45 @@ peeringdb = [
{file = "peeringdb-1.1.0.tar.gz", hash = "sha256:927a34c31e5b93130a855bb4c8fd84dcf604b9939678f75918f7c1bd8a501471"},
]
phonenumbers = [
{file = "phonenumbers-8.12.40-py2.py3-none-any.whl", hash = "sha256:fc105364dfee2bdb5be9116071e877b66fc7afbff6491c1b739fe7cff9a4c7bf"},
{file = "phonenumbers-8.12.40.tar.gz", hash = "sha256:00f2955a456b458f9b6ab0d24329049c3e7358c44dfc1979fe4908ced40f1eb8"},
{file = "phonenumbers-8.12.42-py2.py3-none-any.whl", hash = "sha256:19f353604ef09b19165ebfc86f0a9e54df75939480c2508770f7d1a4adc8e194"},
{file = "phonenumbers-8.12.42.tar.gz", hash = "sha256:8b54871ed64ecfce494a078e9d56b363c23e24c5916dfd89e84b985a18955bee"},
]
pillow = [
{file = "Pillow-9.0.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:113723312215b25c22df1fdf0e2da7a3b9c357a7d24a93ebbe80bfda4f37a8d4"},
{file = "Pillow-9.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bb47a548cea95b86494a26c89d153fd31122ed65255db5dcbc421a2d28eb3379"},
{file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31b265496e603985fad54d52d11970383e317d11e18e856971bdbb86af7242a4"},
{file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154ed971a4cc04b93a6d5b47f37948d1f621f25de3e8fa0c26b2d44f24e3e8f"},
{file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fe92813d208ce8aa7d76da878bdc84b90809f79ccbad2a288e9bcbeac1d9bd"},
{file = "Pillow-9.0.0-cp310-cp310-win32.whl", hash = "sha256:d5dcea1387331c905405b09cdbfb34611050cc52c865d71f2362f354faee1e9f"},
{file = "Pillow-9.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:52abae4c96b5da630a8b4247de5428f593465291e5b239f3f843a911a3cf0105"},
{file = "Pillow-9.0.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:72c3110228944019e5f27232296c5923398496b28be42535e3b2dc7297b6e8b6"},
{file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97b6d21771da41497b81652d44191489296555b761684f82b7b544c49989110f"},
{file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72f649d93d4cc4d8cf79c91ebc25137c358718ad75f99e99e043325ea7d56100"},
{file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aaf07085c756f6cb1c692ee0d5a86c531703b6e8c9cae581b31b562c16b98ce"},
{file = "Pillow-9.0.0-cp37-cp37m-win32.whl", hash = "sha256:03b27b197deb4ee400ed57d8d4e572d2d8d80f825b6634daf6e2c18c3c6ccfa6"},
{file = "Pillow-9.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a09a9d4ec2b7887f7a088bbaacfd5c07160e746e3d47ec5e8050ae3b2a229e9f"},
{file = "Pillow-9.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:490e52e99224858f154975db61c060686df8a6b3f0212a678e5d2e2ce24675c9"},
{file = "Pillow-9.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:500d397ddf4bbf2ca42e198399ac13e7841956c72645513e8ddf243b31ad2128"},
{file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ebd8b9137630a7bbbff8c4b31e774ff05bbb90f7911d93ea2c9371e41039b52"},
{file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd0e5062f11cb3e730450a7d9f323f4051b532781026395c4323b8ad055523c4"},
{file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f3b4522148586d35e78313db4db0df4b759ddd7649ef70002b6c3767d0fdeb7"},
{file = "Pillow-9.0.0-cp38-cp38-win32.whl", hash = "sha256:0b281fcadbb688607ea6ece7649c5d59d4bbd574e90db6cd030e9e85bde9fecc"},
{file = "Pillow-9.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5050d681bcf5c9f2570b93bee5d3ec8ae4cf23158812f91ed57f7126df91762"},
{file = "Pillow-9.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c2067b3bb0781f14059b112c9da5a91c80a600a97915b4f48b37f197895dd925"},
{file = "Pillow-9.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2d16b6196fb7a54aff6b5e3ecd00f7c0bab1b56eee39214b2b223a9d938c50af"},
{file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98cb63ca63cb61f594511c06218ab4394bf80388b3d66cd61d0b1f63ee0ea69f"},
{file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc462d24500ba707e9cbdef436c16e5c8cbf29908278af053008d9f689f56dee"},
{file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3586e12d874ce2f1bc875a3ffba98732ebb12e18fb6d97be482bd62b56803281"},
{file = "Pillow-9.0.0-cp39-cp39-win32.whl", hash = "sha256:68e06f8b2248f6dc8b899c3e7ecf02c9f413aab622f4d6190df53a78b93d97a5"},
{file = "Pillow-9.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:6579f9ba84a3d4f1807c4aab4be06f373017fc65fff43498885ac50a9b47a553"},
{file = "Pillow-9.0.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:47f5cf60bcb9fbc46011f75c9b45a8b5ad077ca352a78185bd3e7f1d294b98bb"},
{file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fd8053e1f8ff1844419842fd474fc359676b2e2a2b66b11cc59f4fa0a301315"},
{file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c5439bfb35a89cac50e81c751317faea647b9a3ec11c039900cd6915831064d"},
{file = "Pillow-9.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95545137fc56ce8c10de646074d242001a112a92de169986abd8c88c27566a05"},
{file = "Pillow-9.0.0.tar.gz", hash = "sha256:ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e"},
{file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"},
{file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"},
{file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"},
{file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"},
{file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"},
{file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"},
{file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"},
{file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"},
{file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"},
{file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"},
{file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"},
{file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"},
{file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"},
{file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"},
{file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"},
{file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"},
{file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"},
{file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"},
{file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"},
{file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"},
{file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"},
{file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"},
{file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"},
{file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"},
{file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"},
{file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"},
{file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"},
{file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"},
{file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"},
{file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"},
{file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"},
{file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"},
{file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"},
{file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"},
{file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"},
]
platformdirs = [
{file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"},
@@ -2355,8 +2376,8 @@ pluggy = [
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
pre-commit = [
{file = "pre_commit-2.16.0-py2.py3-none-any.whl", hash = "sha256:758d1dc9b62c2ed8881585c254976d66eae0889919ab9b859064fc2fe3c7743e"},
{file = "pre_commit-2.16.0.tar.gz", hash = "sha256:fe9897cac830aa7164dbd02a4e7b90cae49630451ce88464bca73db486ba9f65"},
{file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"},
{file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"},
]
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
@@ -2398,39 +2419,39 @@ pynacl = [
{file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"},
]
pyopenssl = [
{file = "pyOpenSSL-21.0.0-py2.py3-none-any.whl", hash = "sha256:8935bd4920ab9abfebb07c41a4f58296407ed77f04bd1a92914044b848ba1ed6"},
{file = "pyOpenSSL-21.0.0.tar.gz", hash = "sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3"},
{file = "pyOpenSSL-22.0.0-py2.py3-none-any.whl", hash = "sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0"},
{file = "pyOpenSSL-22.0.0.tar.gz", hash = "sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf"},
]
pyparsing = [
{file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"},
{file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"},
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
]
pyrsistent = [
{file = "pyrsistent-0.18.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"},
{file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d"},
{file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2"},
{file = "pyrsistent-0.18.0-cp36-cp36m-win32.whl", hash = "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1"},
{file = "pyrsistent-0.18.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7"},
{file = "pyrsistent-0.18.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396"},
{file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710"},
{file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35"},
{file = "pyrsistent-0.18.0-cp37-cp37m-win32.whl", hash = "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f"},
{file = "pyrsistent-0.18.0-cp37-cp37m-win_amd64.whl", hash = "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2"},
{file = "pyrsistent-0.18.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427"},
{file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef"},
{file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c"},
{file = "pyrsistent-0.18.0-cp38-cp38-win32.whl", hash = "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78"},
{file = "pyrsistent-0.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b"},
{file = "pyrsistent-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4"},
{file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680"},
{file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426"},
{file = "pyrsistent-0.18.0-cp39-cp39-win32.whl", hash = "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b"},
{file = "pyrsistent-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea"},
{file = "pyrsistent-0.18.0.tar.gz", hash = "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b"},
{file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"},
{file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"},
{file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"},
{file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"},
{file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"},
{file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"},
{file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"},
{file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"},
{file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"},
{file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"},
{file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"},
{file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"},
{file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"},
{file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"},
{file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"},
{file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"},
{file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"},
{file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"},
{file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"},
{file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"},
{file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"},
]
pytest = [
{file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
{file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
{file = "pytest-7.0.0-py3-none-any.whl", hash = "sha256:42901e6bd4bd4a0e533358a86e848427a49005a3256f657c5c8f8dd35ef137a9"},
{file = "pytest-7.0.0.tar.gz", hash = "sha256:dad48ffda394e5ad9aa3b7d7ddf339ed502e5e365b1350e0af65f4a602344b11"},
]
pytest-cov = [
{file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
@@ -2444,8 +2465,8 @@ pytest-filedata = [
{file = "pytest_filedata-0.4.0.tar.gz", hash = "sha256:3a2a3f346087ac82dfd313212cd2d61c5fcfd23b0aecaa2484e6c31cfcb32fd5"},
]
pytest-mock = [
{file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"},
{file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"},
{file = "pytest-mock-3.7.0.tar.gz", hash = "sha256:5112bd92cc9f186ee96e1a92efc84969ea494939c3aead39c50f421c4cc69534"},
{file = "pytest_mock-3.7.0-py3-none-any.whl", hash = "sha256:6cff27cec936bf81dc5ee87f07132b807bcda51106b5ec4b90a04331cba76231"},
]
python-dateutil = [
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
@@ -2517,8 +2538,7 @@ pyyaml-env-tag = [
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
]
qrcode = [
{file = "qrcode-6.1-py2.py3-none-any.whl", hash = "sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5"},
{file = "qrcode-6.1.tar.gz", hash = "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"},
{file = "qrcode-7.3.1.tar.gz", hash = "sha256:375a6ff240ca9bd41adc070428b5dfc1dcfbb0f2507f1ac848f6cded38956578"},
]
rdap = [
{file = "rdap-1.2.1-py3-none-any.whl", hash = "sha256:dc2602c1ceaf3cd1c6c5f5ba0428c5efbfcbd974765543d3ac3f7b70c2e501a7"},
@@ -2533,9 +2553,8 @@ requests-mock = [
{file = "requests_mock-1.9.3-py2.py3-none-any.whl", hash = "sha256:0a2d38a117c08bb78939ec163522976ad59a6b7fdd82b709e23bb98004a44970"},
]
requests-oauthlib = [
{file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"},
{file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"},
{file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"},
{file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"},
{file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"},
]
simplejson = [
{file = "simplejson-3.17.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a89acae02b2975b1f8e4974cb8cdf9bf9f6c91162fb8dec50c259ce700f2770a"},
@@ -2630,8 +2649,8 @@ toml = [
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
tomli = [
{file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"},
{file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"},
{file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"},
{file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"},
]
"twentyc.rpc" = [
{file = "twentyc.rpc-0.4.0.tar.gz", hash = "sha256:c6a08a0fa8610332f430911061a662efee8c251a5568c1ffd592c566d9da0768"},
@@ -2656,8 +2675,8 @@ uwsgi = [
{file = "uwsgi-2.0.20.tar.gz", hash = "sha256:88ab9867d8973d8ae84719cf233b7dafc54326fcaec89683c3f9f77c002cdff9"},
]
virtualenv = [
{file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"},
{file = "virtualenv-20.13.0.tar.gz", hash = "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd"},
{file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"},
{file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"},
]
watchdog = [
{file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"},
+3 -1
View File
@@ -4,5 +4,7 @@
"fac": 4,
"netixlan": 3,
"netfac": 3,
"automated_nets": 4
"automated_nets": 4,
"organizations": 7,
"registered_users": 6
}
@@ -4,5 +4,7 @@
"fac": 4,
"netixlan": 3,
"netfac": 3,
"automated_nets": 3
"automated_nets": 3,
"organizations": 7,
"registered_users": 6
}
+100
View File
@@ -0,0 +1,100 @@
import pytest
from django.core.cache import cache
from django.test import TestCase
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory
from peeringdb_server import models
from peeringdb_server.rest import ModelViewSet
from peeringdb_server.rest_throttles import APIAnonUserThrottle, APIUserThrottle
class MockView(ModelViewSet):
"""
Dummy view for testing throttling
"""
throttle_classes = (APIUserThrottle, APIAnonUserThrottle)
def get(self, request):
return Response("example")
class APIThrottleTests(TestCase):
"""
API tests
"""
@classmethod
def setUp(self):
"""
Reset the cache so that no throttles will be active
"""
cache.clear()
self.factory = APIRequestFactory()
env = models.EnvironmentSetting(
setting="API_THROTTLE_RATE_ANON", value_str="10/minute"
)
env.save()
env = models.EnvironmentSetting(
setting="API_THROTTLE_RATE_USER", value_str="10/minute"
)
env.save()
def test_environment_throttle_setting(self):
"""
Test if default throttle settings are overridden by environment settings
"""
assert (
models.EnvironmentSetting.get_setting_value("API_THROTTLE_RATE_ANON")
== "10/minute"
)
assert (
models.EnvironmentSetting.get_setting_value("API_THROTTLE_RATE_USER")
== "10/minute"
)
def test_anon_requests_below_throttle_rate(self):
"""
Test if request rate is limited for anonymous users
"""
request = self.factory.get("/")
for dummy in range(10):
response = MockView.as_view({"get": "get"})(request)
assert response.status_code == 200
def test_authenticated_requests_below_throttle_rate(self):
"""
Test request rate is not limited for authenticated users
"""
user = models.User(username="test")
user.save()
request = self.factory.get("/")
request.user = user
for dummy in range(10):
response = MockView.as_view({"get": "get"})(request)
assert response.status_code == 200
def test_anon_requests_above_throttle_rate(self):
"""
Ensure request rate is limited for anonymous users
"""
request = self.factory.get("/")
for dummy in range(11):
response = MockView.as_view({"get": "get"})(request)
assert response.status_code == 429
def test_authenticated_requests_above_throttle_rate(self):
"""
Ensure request rate is not limited for authenticated users
"""
user = models.User(username="test")
user.save()
request = self.factory.get("/")
request.user = user
for dummy in range(11):
response = MockView.as_view({"get": "get"})(request)
assert response.status_code == 429
+48
View File
@@ -0,0 +1,48 @@
from io import StringIO
import pytest
from django.core.management import call_command
"""
Tests for the pdb_validate_data command.
"""
@pytest.mark.django_db
def test_cmd_pdb_validate_data():
out = StringIO()
call_command(
"pdb_validate_data",
"ix",
"tech_phone",
commit=True,
stdout=out,
stderr=StringIO(),
)
assert "Validation Complete" in out.getvalue()
@pytest.mark.django_db
def test_cmd_pdb_validate_data_invalid_field():
out = StringIO()
call_command(
"pdb_validate_data", "ix", "invalid_field", commit=True, stdout=out, stderr=out
)
assert "Unsupported field for validation" in out.getvalue()
@pytest.mark.django_db
def test_cmd_pdb_validate_data_invalid_model():
out = StringIO()
call_command(
"pdb_validate_data",
"invalid_model",
"tech_phone",
commit=True,
stdout=out,
stderr=out,
)
assert "Unknown model handleref tag" in out.getvalue()