From cfc7297c282b2183f62cf474fb8a2e0ecaf60781 Mon Sep 17 00:00:00 2001 From: Justin Drew <2396364+jdrew82@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:24:59 -0600 Subject: [PATCH 1/8] build: Bump to prepatch --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d588ca5..cb5c0fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nautobot-firewall-models" -version = "2.2.0" +version = "2.2.1a0" description = "Nautobot app to model firewall objects." authors = ["Network to Code, LLC "] license = "Apache-2.0" From d8373b13100c3e17536578d0f59f5604faf25ac6 Mon Sep 17 00:00:00 2001 From: bakebot Date: Tue, 7 Jan 2025 18:41:17 +0000 Subject: [PATCH 2/8] Cookie updated by NetworkToCode Cookie Drift Manager Tool Template: ``` { "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "dir": "nautobot-app", "ref": "refs/tags/nautobot-app-v2.4.1", "path": null } ``` Cookie: ``` { "remote": "https://github.com/nautobot/nautobot-app-firewall-models.git", "path": "/tmp/tmp1r6mhcvu/nautobot-app-firewall-models", "repository_path": "/tmp/tmp1r6mhcvu/nautobot-app-firewall-models", "dir": "", "branch_prefix": "drift-manager", "context": { "codeowner_github_usernames": "@whitej6 @itdependsnetworks @jdrew82", "full_name": "Network to Code, LLC", "email": "opensource@networktocode.com", "github_org": "nautobot", "app_name": "nautobot_firewall_models", "verbose_name": "Nautobot Firewall Models", "app_slug": "nautobot-firewall-models", "project_slug": "nautobot-app-firewall-models", "repo_url": "https://github.com/nautobot/nautobot-app-firewall-models", "base_url": "firewall-models", "min_nautobot_version": "2.0.0", "max_nautobot_version": "2.9999", "camel_name": "NautobotFirewallModels", "project_short_description": "Nautobot App to model firewall and security objects. Allows users to model policies in a vendor-neutral manner and use that data to drive network security automation", "model_class_name": "IPRange", "open_source_license": "Apache-2.0", "docs_base_url": "https://docs.nautobot.com", "docs_app_url": "https://docs.nautobot.com/projects/firewall-models/en/latest", "_template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "_output_dir": "/tmp/tmp1r6mhcvu", "_repo_dir": "/github/home/.cookiecutters/cookiecutter-nautobot-app/nautobot-app", "_checkout": "refs/tags/nautobot-app-v2.4.1" }, "base_branch": "develop", "remote_name": "origin", "pull_request_strategy": "PullRequestStrategy.CREATE", "post_actions": [ "PostAction.RUFF", "PostAction.POETRY" ], "baked_commit_ref": "40d9cefc5c5b9f7db4b1c9bdb3e6645a4dc4933c", "draft": false } ``` CLI Arguments: ``` { "cookie_dir": "", "input": false, "json_filename": "", "output_dir": "", "push": true, "template": "", "template_dir": "", "template_ref": "refs/tags/nautobot-app-v2.4.1", "pull_request": null, "post_action": [ "ruff", "poetry" ], "disable_post_actions": true, "draft": false } ``` --- .cookiecutter.json | 4 +- .../pull_request_template.md | 0 .github/workflows/ci.yml | 16 + LICENSE | 2 +- changes/+nautobot-app-v2.4.1.housekeeping | 1 + development/docker-compose.dev.yml | 6 +- development/docker-compose.mysql.yml | 7 + development/nautobot_config.py | 14 +- mkdocs.yml | 3 +- nautobot_firewall_models/api/serializers.py | 238 +-- nautobot_firewall_models/api/urls.py | 24 +- nautobot_firewall_models/api/views.py | 164 +- nautobot_firewall_models/filters.py | 269 +--- nautobot_firewall_models/forms.py | 915 +---------- nautobot_firewall_models/models.py | 38 + nautobot_firewall_models/navigation.py | 234 +-- nautobot_firewall_models/tables.py | 342 +--- .../iprange_retrieve.html | 34 +- nautobot_firewall_models/tests/fixtures.py | 443 +----- .../tests/test_api_views.py | 27 + .../tests/test_filter_iprange.py | 28 + .../tests/test_form_iprange.py | 33 + .../tests/test_model_iprange.py | 22 + nautobot_firewall_models/tests/test_views.py | 28 + nautobot_firewall_models/urls.py | 33 +- nautobot_firewall_models/views.py | 19 + poetry.lock | 1392 ++++++++--------- tasks.py | 28 +- 28 files changed, 1027 insertions(+), 3337 deletions(-) rename .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md (100%) create mode 100644 changes/+nautobot-app-v2.4.1.housekeeping create mode 100644 nautobot_firewall_models/models.py create mode 100644 nautobot_firewall_models/tests/test_api_views.py create mode 100644 nautobot_firewall_models/tests/test_filter_iprange.py create mode 100644 nautobot_firewall_models/tests/test_form_iprange.py create mode 100644 nautobot_firewall_models/tests/test_model_iprange.py create mode 100644 nautobot_firewall_models/tests/test_views.py create mode 100644 nautobot_firewall_models/views.py diff --git a/.cookiecutter.json b/.cookiecutter.json index 3f369ed..534cc57 100644 --- a/.cookiecutter.json +++ b/.cookiecutter.json @@ -21,7 +21,7 @@ "_drift_manager": { "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git", "template_dir": "nautobot-app", - "template_ref": "refs/tags/nautobot-app-v2.4.0", + "template_ref": "refs/tags/nautobot-app-v2.4.1", "cookie_dir": "", "branch_prefix": "drift-manager", "pull_request_strategy": "create", @@ -30,7 +30,7 @@ "poetry" ], "draft": false, - "baked_commit_ref": "40d9cefc5c5b9f7db4b1c9bdb3e6645a4dc4933c" + "baked_commit_ref": "b5d763fab2885bec541b910e18c19d5a06238648" } } } diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/pull_request_template.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46052a1..9a73be4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,8 @@ jobs: uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v6" + with: + poetry-version: "1.8.5" - name: "Linting: ruff format" run: "poetry run invoke ruff --action format" ruff-lint: @@ -36,6 +38,8 @@ jobs: uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v6" + with: + poetry-version: "1.8.5" - name: "Linting: ruff" run: "poetry run invoke ruff --action lint" check-docs-build: @@ -47,6 +51,8 @@ jobs: uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v6" + with: + poetry-version: "1.8.5" - name: "Check Docs Build" run: "poetry run invoke build-and-check-docs" poetry: @@ -58,6 +64,8 @@ jobs: uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v6" + with: + poetry-version: "1.8.5" - name: "Checking: poetry lock file" run: "poetry run invoke lock --check" yamllint: @@ -69,6 +77,8 @@ jobs: uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v6" + with: + poetry-version: "1.8.5" - name: "Linting: yamllint" run: "poetry run invoke yamllint" check-in-docker: @@ -91,6 +101,8 @@ jobs: uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v6" + with: + poetry-version: "1.8.5" - name: "Constrain Nautobot version and regenerate lock file" env: INVOKE_NAUTOBOT_FIREWALL_MODELS_LOCAL: "true" @@ -146,6 +158,8 @@ jobs: uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v6" + with: + poetry-version: "1.8.5" - name: "Constrain Nautobot version and regenerate lock file" env: INVOKE_NAUTOBOT_FIREWALL_MODELS_LOCAL: "true" @@ -187,6 +201,8 @@ jobs: fetch-depth: "0" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v6" + with: + poetry-version: "1.8.5" - name: "Check for changelog entry" run: | git fetch --no-tags origin +refs/heads/${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }} diff --git a/LICENSE b/LICENSE index bf295f4..e923d12 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Apache Software License 2.0 -Copyright (c) 2024, Network to Code, LLC +Copyright (c) 2025, Network to Code, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/changes/+nautobot-app-v2.4.1.housekeeping b/changes/+nautobot-app-v2.4.1.housekeeping new file mode 100644 index 0000000..3171488 --- /dev/null +++ b/changes/+nautobot-app-v2.4.1.housekeeping @@ -0,0 +1 @@ +Rebaked from the cookie `nautobot-app-v2.4.1`. diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml index 9752abb..2c8269a 100644 --- a/development/docker-compose.dev.yml +++ b/development/docker-compose.dev.yml @@ -45,10 +45,12 @@ services: volumes: - "./nautobot_config.py:/opt/nautobot/nautobot_config.py" - "../:/source" -# To expose postgres or redis to the host uncomment the following -# postgres: +# To expose postgres (5432), myql (3306) on db service or redis (6379) to the host uncomment the +# following. Ensure to match the 2 idented spaces which to have the service nested under services. +# db: # ports: # - "5432:5432" +# - "3306:3306" # redis: # ports: # - "6379:6379" diff --git a/development/docker-compose.mysql.yml b/development/docker-compose.mysql.yml index dbe31cb..6751d72 100644 --- a/development/docker-compose.mysql.yml +++ b/development/docker-compose.mysql.yml @@ -14,6 +14,13 @@ services: - "development.env" - "creds.env" - "development_mysql.env" + beat: + environment: + - "NAUTOBOT_DB_ENGINE=django.db.backends.mysql" + env_file: + - "development.env" + - "creds.env" + - "development_mysql.env" db: image: "mysql:8" command: diff --git a/development/nautobot_config.py b/development/nautobot_config.py index a9fdd3c..7d5ebbb 100644 --- a/development/nautobot_config.py +++ b/development/nautobot_config.py @@ -4,7 +4,7 @@ import sys from nautobot.core.settings import * # noqa: F403 # pylint: disable=wildcard-import,unused-wildcard-import -from nautobot.core.settings_funcs import is_truthy, parse_redis_connection +from nautobot.core.settings_funcs import is_truthy # # Debug @@ -65,16 +65,8 @@ # # The django-redis cache is used to establish concurrent locks using Redis. -CACHES = { - "default": { - "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": parse_redis_connection(redis_database=0), - "TIMEOUT": 300, - "OPTIONS": { - "CLIENT_CLASS": "django_redis.client.DefaultClient", - }, - } -} +# Inherited from nautobot.core.settings +# CACHES = {....} # # Celery settings are not defined here because they can be overloaded with diff --git a/mkdocs.yml b/mkdocs.yml index b319906..2279da0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -128,7 +128,8 @@ nav: - Extending the App: "dev/extending.md" - Contributing to the App: "dev/contributing.md" - Development Environment: "dev/dev_environment.md" - # - Architecture Decision Records: "dev/arch_decision.md" + - Release Checklist: "dev/release_checklist.md" + - Architecture Decision Records: "dev/arch_decision.md" - Code Reference: - "dev/code_reference/index.md" - Package: "dev/code_reference/package.md" diff --git a/nautobot_firewall_models/api/serializers.py b/nautobot_firewall_models/api/serializers.py index 8e86bbb..02eddeb 100644 --- a/nautobot_firewall_models/api/serializers.py +++ b/nautobot_firewall_models/api/serializers.py @@ -1,246 +1,18 @@ -"""API serializers for firewall models.""" +"""API serializers for nautobot_firewall_models.""" -from nautobot.apps.api import NautobotModelSerializer, ValidatedModelSerializer -from rest_framework import serializers +from nautobot.apps.api import NautobotModelSerializer, TaggedModelSerializerMixin from nautobot_firewall_models import models -class IPRangeSerializer(NautobotModelSerializer): +class IPRangeSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): # pylint: disable=too-many-ancestors """IPRange Serializer.""" - start_address = serializers.CharField() - end_address = serializers.CharField() - class Meta: """Meta attributes.""" model = models.IPRange fields = "__all__" - # Omit the UniqueTogetherValidators that would be automatically added to validate (start_address, end_address, vrf). - # This prevents vrf from being interpreted as a required field. - validators = [] - - def validate(self, data): - """Custom validate method to enforce unique constraints on IPRange model.""" - # Validate uniqueness of (start_address, end_address, vrf) since we omitted the automatically-created validator above. - start_address = data.get("start_address") - end_address = data.get("end_address") - vrf = data.get("vrf") - if not any([start_address is not None, end_address is not None, vrf is not None]): - return super().validate(data) - - # Use existing object's attributes for partial updates - if self.instance: - start_address = start_address or self.instance.start_address - end_address = end_address or self.instance.end_address - vrf = vrf or self.instance.vrf - qs = models.IPRange.objects.exclude(pk=self.instance.pk) - else: - qs = models.IPRange.objects.all() - - if vrf is not None: - if qs.filter(start_address=start_address, end_address=end_address, vrf=vrf).exists(): - raise serializers.ValidationError("The fields start_address, end_address, vrf must make a unique set.") - elif qs.filter(start_address=start_address, end_address=end_address, vrf__isnull=True).exists(): - raise serializers.ValidationError("The fields start_address, end_address must make a unique set.") - - return super().validate(data) - - -class FQDNSerializer(NautobotModelSerializer): - """FQDN Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.FQDN - fields = "__all__" - - -class AddressObjectSerializer(NautobotModelSerializer): - """AddressObject Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.AddressObject - fields = "__all__" - - -class AddressObjectGroupSerializer(NautobotModelSerializer): - """AddressObjectGroup Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.AddressObjectGroup - fields = "__all__" - - -class ApplicationObjectSerializer(NautobotModelSerializer): - """ApplicationObject Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.ApplicationObject - fields = "__all__" - - -class ApplicationObjectGroupSerializer(NautobotModelSerializer): - """ApplicationObjectGroup Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.ApplicationObjectGroup - fields = "__all__" - - -class ServiceObjectSerializer(NautobotModelSerializer): - """ServiceObject Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.ServiceObject - fields = "__all__" - - -class ServiceObjectGroupSerializer(NautobotModelSerializer): - """ServiceObjectGroup Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.ServiceObjectGroup - fields = "__all__" - - -class UserObjectSerializer(NautobotModelSerializer): - """UserObject Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.UserObject - fields = "__all__" - - -class UserObjectGroupSerializer(NautobotModelSerializer): - """UserObjectGroup Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.UserObjectGroup - fields = "__all__" - - -class ZoneSerializer(NautobotModelSerializer): - """Zone Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.Zone - fields = "__all__" - - -class PolicyRuleSerializer(NautobotModelSerializer): - """PolicyRule Serializer.""" - - index = serializers.IntegerField(required=False, default=None) - - class Meta: - """Meta attributes.""" - - model = models.PolicyRule - fields = "__all__" - - -class PolicySerializer(NautobotModelSerializer): - """Policy Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.Policy - fields = "__all__" - - -class NATPolicyRuleSerializer(NautobotModelSerializer): - """PolicyRule Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.NATPolicyRule - fields = "__all__" - - -class NATPolicySerializer(NautobotModelSerializer): - """NATPolicy Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.NATPolicy - fields = "__all__" - - -class CapircaPolicySerializer(NautobotModelSerializer): - """CapircaPolicy Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.CapircaPolicy - fields = "__all__" - - -########################### -# Through Models -########################### - - -class PolicyDeviceM2MSerializer(ValidatedModelSerializer): - """PolicyDeviceM2M Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.PolicyDeviceM2M - fields = "__all__" - - -class PolicyDynamicGroupM2MSerializer(ValidatedModelSerializer): - """PolicyDynamicGroupM2M Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.PolicyDynamicGroupM2M - fields = "__all__" - - -class NATPolicyDeviceM2MSerializer(ValidatedModelSerializer): - """NATPolicyDeviceM2M Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.NATPolicyDeviceM2M - fields = "__all__" - - -class NATPolicyDynamicGroupM2MSerializer(ValidatedModelSerializer): - """NATPolicyDynamicGroupM2M Serializer.""" - - class Meta: - """Meta attributes.""" - - model = models.NATPolicyDynamicGroupM2M - fields = "__all__" + # Option for disabling write for certain fields: + # read_only_fields = [] diff --git a/nautobot_firewall_models/api/urls.py b/nautobot_firewall_models/api/urls.py index 8c18b15..eba0064 100644 --- a/nautobot_firewall_models/api/urls.py +++ b/nautobot_firewall_models/api/urls.py @@ -1,29 +1,11 @@ -"""Django API urlpatterns declaration for firewall model app.""" +"""Django API urlpatterns declaration for nautobot_firewall_models app.""" from nautobot.apps.api import OrderedDefaultRouter from nautobot_firewall_models.api import views router = OrderedDefaultRouter() -router.register("address-object", views.AddressObjectViewSet) -router.register("address-object-group", views.AddressObjectGroupViewSet) -router.register("application-object", views.ApplicationObjectViewSet) -router.register("application-object-group", views.ApplicationObjectGroupViewSet) -router.register("capirca-policy", views.CapircaPolicyViewSet) -router.register("fqdn", views.FQDNViewSet) -router.register("ip-range", views.IPRangeViewSet) -router.register("nat-policy-rule", views.NATPolicyRuleViewSet) -router.register("nat-policy", views.NATPolicyViewSet) -router.register("nat-policy-device-association", views.NATPolicyDeviceM2MViewSet) -router.register("nat-policy-dynamic-group-association", views.NATPolicyDynamicGroupM2MViewSet) -router.register("policy-rule", views.PolicyRuleViewSet) -router.register("policy", views.PolicyViewSet) -router.register("policy-device-association", views.PolicyDeviceM2MViewSet) -router.register("policy-dynamic-group-association", views.PolicyDynamicGroupM2MViewSet) -router.register("service-object", views.ServiceObjectViewSet) -router.register("service-object-group", views.ServiceObjectGroupViewSet) -router.register("user-object", views.UserObjectViewSet) -router.register("user-object-group", views.UserObjectGroupViewSet) -router.register("zone", views.ZoneViewSet) +# add the name of your api endpoint, usually hyphenated model name in plural, e.g. "my-model-classes" +router.register("iprange", views.IPRangeViewSet) urlpatterns = router.urls diff --git a/nautobot_firewall_models/api/views.py b/nautobot_firewall_models/api/views.py index 1bf3de4..e66e9e6 100644 --- a/nautobot_firewall_models/api/views.py +++ b/nautobot_firewall_models/api/views.py @@ -1,171 +1,17 @@ -"""API views for firewall models.""" +"""API views for nautobot_firewall_models.""" -from nautobot.apps.api import ModelViewSet, NautobotModelViewSet +from nautobot.apps.api import NautobotModelViewSet from nautobot_firewall_models import filters, models from nautobot_firewall_models.api import serializers -class IPRangeViewSet(NautobotModelViewSet): +class IPRangeViewSet(NautobotModelViewSet): # pylint: disable=too-many-ancestors """IPRange viewset.""" queryset = models.IPRange.objects.all() serializer_class = serializers.IPRangeSerializer filterset_class = filters.IPRangeFilterSet - -class FQDNViewSet(NautobotModelViewSet): - """FQDN viewset.""" - - queryset = models.FQDN.objects.all() - serializer_class = serializers.FQDNSerializer - filterset_class = filters.FQDNFilterSet - - -class AddressObjectViewSet(NautobotModelViewSet): - """AddressObject viewset.""" - - queryset = models.AddressObject.objects.all() - serializer_class = serializers.AddressObjectSerializer - filterset_class = filters.AddressObjectFilterSet - - -class AddressObjectGroupViewSet(NautobotModelViewSet): - """AddressObjectGroup viewset.""" - - queryset = models.AddressObjectGroup.objects.all() - serializer_class = serializers.AddressObjectGroupSerializer - filterset_class = filters.AddressObjectGroupFilterSet - - -class ApplicationObjectViewSet(NautobotModelViewSet): - """ApplicationObject viewset.""" - - queryset = models.ApplicationObject.objects.all() - serializer_class = serializers.ApplicationObjectSerializer - filterset_class = filters.ApplicationObjectFilterSet - - -class ApplicationObjectGroupViewSet(NautobotModelViewSet): - """ApplicationObjectGroup viewset.""" - - queryset = models.ApplicationObjectGroup.objects.all() - serializer_class = serializers.ApplicationObjectGroupSerializer - filterset_class = filters.ApplicationObjectGroupFilterSet - - -class ServiceObjectViewSet(NautobotModelViewSet): - """ServiceObject viewset.""" - - queryset = models.ServiceObject.objects.all() - serializer_class = serializers.ServiceObjectSerializer - filterset_class = filters.ServiceObjectFilterSet - - -class ServiceObjectGroupViewSet(NautobotModelViewSet): - """ServiceObjectGroup viewset.""" - - queryset = models.ServiceObjectGroup.objects.all() - serializer_class = serializers.ServiceObjectGroupSerializer - filterset_class = filters.ServiceObjectGroupFilterSet - - -class UserObjectViewSet(NautobotModelViewSet): - """UserObject viewset.""" - - queryset = models.UserObject.objects.all() - serializer_class = serializers.UserObjectSerializer - filterset_class = filters.UserObjectFilterSet - - -class UserObjectGroupViewSet(NautobotModelViewSet): - """UserObjectGroup viewset.""" - - queryset = models.UserObjectGroup.objects.all() - serializer_class = serializers.UserObjectGroupSerializer - filterset_class = filters.UserObjectGroupFilterSet - - -class ZoneViewSet(NautobotModelViewSet): - """Zone viewset.""" - - queryset = models.Zone.objects.all() - serializer_class = serializers.ZoneSerializer - filterset_class = filters.ZoneFilterSet - - -class PolicyRuleViewSet(NautobotModelViewSet): - """PolicyRule viewset.""" - - queryset = models.PolicyRule.objects.all() - serializer_class = serializers.PolicyRuleSerializer - filterset_class = filters.PolicyRuleFilterSet - - -class PolicyViewSet(NautobotModelViewSet): - """Policy viewset.""" - - queryset = models.Policy.objects.all() - serializer_class = serializers.PolicySerializer - filterset_class = filters.PolicyFilterSet - - -class NATPolicyRuleViewSet(NautobotModelViewSet): - """NATPolicyRule viewset.""" - - queryset = models.NATPolicyRule.objects.all() - serializer_class = serializers.NATPolicyRuleSerializer - filterset_class = filters.NATPolicyRuleFilterSet - - -class NATPolicyViewSet(NautobotModelViewSet): - """NATPolicy viewset.""" - - queryset = models.NATPolicy.objects.all() - serializer_class = serializers.NATPolicySerializer - filterset_class = filters.NATPolicyFilterSet - - -class CapircaPolicyViewSet(ModelViewSet): - """CapircaPolicy viewset.""" - - queryset = models.CapircaPolicy.objects.all() - serializer_class = serializers.CapircaPolicySerializer - filterset_class = filters.CapircaPolicyFilterSet - - -########################### -# Through Models -########################### - - -class PolicyDeviceM2MViewSet(ModelViewSet): - """PolicyDeviceM2M viewset.""" - - queryset = models.PolicyDeviceM2M.objects.all() - serializer_class = serializers.PolicyDeviceM2MSerializer - filterset_class = filters.PolicyDeviceM2MFilterSet - - -class PolicyDynamicGroupM2MViewSet(ModelViewSet): - """PolicyDynamicGroupM2M viewset.""" - - queryset = models.PolicyDynamicGroupM2M.objects.all() - serializer_class = serializers.PolicyDynamicGroupM2MSerializer - filterset_class = filters.PolicyDynamicGroupM2MFilterSet - - -class NATPolicyDeviceM2MViewSet(ModelViewSet): - """NATPolicyDeviceM2M viewset.""" - - queryset = models.NATPolicyDeviceM2M.objects.all() - serializer_class = serializers.NATPolicyDeviceM2MSerializer - filterset_class = filters.NATPolicyDeviceM2MFilterSet - - -class NATPolicyDynamicGroupM2MViewSet(ModelViewSet): - """NATPolicyDynamicGroupM2M viewset.""" - - queryset = models.NATPolicyDynamicGroupM2M.objects.all() - serializer_class = serializers.NATPolicyDynamicGroupM2MSerializer - filterset_class = filters.NATPolicyDynamicGroupM2MFilterSet + # Option for modifying the default HTTP methods: + # http_method_names = ["get", "post", "put", "patch", "delete", "head", "options", "trace"] diff --git a/nautobot_firewall_models/filters.py b/nautobot_firewall_models/filters.py index 558778d..8502ba3 100644 --- a/nautobot_firewall_models/filters.py +++ b/nautobot_firewall_models/filters.py @@ -1,276 +1,17 @@ -"""Filtering for Firewall Model App.""" +"""Filtering for nautobot_firewall_models.""" -import django_filters -from django.contrib.contenttypes.fields import GenericRelation -from django.core.exceptions import ValidationError -from django.db.models import Q -from nautobot.apps.filters import ( - MultiValueCharFilter, - NaturalKeyOrPKMultipleChoiceFilter, - NautobotFilterSet, - StatusModelFilterSetMixin, -) -from nautobot.dcim.models import Device +from nautobot.apps.filters import NameSearchFilterSet, NautobotFilterSet from nautobot_firewall_models import models -class BaseFilterSet(StatusModelFilterSetMixin, django_filters.filterset.FilterSet): - """A base class for adding the search method to models which only expose the `name` and `description` fields.""" - - q = django_filters.CharFilter( - method="search", - label="Search", - ) - - def search(self, queryset, name, value): # pylint: disable=unused-argument - """Construct Q filter for filterset.""" - if not value.strip(): - return queryset - return queryset.filter(Q(name__icontains=value) | Q(description__icontains=value)) - - -class IPRangeFilterSet(BaseFilterSet, NautobotFilterSet): +class IPRangeFilterSet(NautobotFilterSet, NameSearchFilterSet): # pylint: disable=too-many-ancestors """Filter for IPRange.""" - start_address = MultiValueCharFilter( - method="filter_address", - label="Address", - ) - end_address = MultiValueCharFilter( - method="filter_address", - label="Address", - ) - class Meta: """Meta attributes for filter.""" model = models.IPRange - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - def filter_address(self, queryset, name, value): # pylint: disable=unused-argument - """Filter method for start & end addresses.""" - try: - return queryset.net_in(value) - except ValidationError: - return queryset.none() - - -class FQDNFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for FQDN.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.FQDN - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class AddressObjectFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for AddressObject.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.AddressObject - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class AddressObjectGroupFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for AddressObjectGroup.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.AddressObjectGroup - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class ApplicationObjectFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for ApplicationObject.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.ApplicationObject - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class ApplicationObjectGroupFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for ApplicationObjectGroup.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.ApplicationObjectGroup - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class ServiceObjectFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for ServiceObject.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.ServiceObject - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class ServiceObjectGroupFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for ServiceObjectGroup.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.ServiceObjectGroup - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class UserObjectFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for UserObject.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.UserObject - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class UserObjectGroupFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for UserObjectGroup.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.UserObjectGroup - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class ZoneFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for Zone.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.Zone - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class PolicyRuleFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for PolicyRule.""" - - def search(self, queryset, name, value): # pylint: disable=unused-argument - """Construct Q filter for filterset.""" - if not value.strip(): - return queryset - # pylint: disable=unsupported-binary-operation - return queryset.filter( - Q(name__icontains=value) | Q(description__icontains=value) | Q(request_id__icontains=value) - ) - - class Meta: - """Meta attributes for filter.""" - - model = models.PolicyRule - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class NATPolicyRuleFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for NATPolicyRule.""" - - def search(self, queryset, name, value): # pylint: disable=unused-argument - """Construct Q filter for filterset.""" - if not value.strip(): - return queryset - # pylint: disable=unsupported-binary-operation - return queryset.filter( - Q(name__icontains=value) | Q(description__icontains=value) | Q(request_id__icontains=value) - ) - - class Meta: - """Meta attributes for filter.""" - - model = models.NATPolicyRule - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class PolicyFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for Policy.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.Policy - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class NATPolicyFilterSet(BaseFilterSet, NautobotFilterSet): - """Filter for NATPolicy.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.NATPolicy - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class CapircaPolicyFilterSet(NautobotFilterSet): - """Filter for CapircaPolicy.""" - - device = NaturalKeyOrPKMultipleChoiceFilter( - field_name="device", - queryset=Device.objects.all(), - to_field_name="name", - label="Schema (name or PK)", - ) - - class Meta: - """Meta attributes for filter.""" - - model = models.CapircaPolicy - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -########################### -# Through Models -########################### - - -class PolicyDeviceM2MFilterSet(NautobotFilterSet): - """Filter for PolicyDeviceM2M.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.PolicyDeviceM2M - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class PolicyDynamicGroupM2MFilterSet(NautobotFilterSet): - """Filter for PolicyDynamicGroupM2M.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.PolicyDynamicGroupM2M - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class NATPolicyDeviceM2MFilterSet(NautobotFilterSet): - """Filter for NATPolicyDeviceM2M.""" - - class Meta: - """Meta attributes for filter.""" - - model = models.NATPolicyDeviceM2M - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - - -class NATPolicyDynamicGroupM2MFilterSet(NautobotFilterSet): - """Filter for NATPolicyDynamicGroupM2M.""" - - class Meta: - """Meta attributes for filter.""" - model = models.NATPolicyDynamicGroupM2M - fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + # add any fields from the model that you would like to filter your searches by using those + fields = ["id", "name", "description"] diff --git a/nautobot_firewall_models/forms.py b/nautobot_firewall_models/forms.py index d5b1a71..bd00de7 100644 --- a/nautobot_firewall_models/forms.py +++ b/nautobot_firewall_models/forms.py @@ -1,683 +1,29 @@ -"""Forms for the Firewall app.""" +"""Forms for nautobot_firewall_models.""" from django import forms -from nautobot.apps.forms import ( - DynamicModelChoiceField, - DynamicModelMultipleChoiceField, - TagFilterField, - add_blank_choice, -) -from nautobot.dcim.models import Device, Interface -from nautobot.extras.forms import ( - CustomFieldModelCSVForm, - LocalContextFilterForm, - LocalContextModelBulkEditForm, - LocalContextModelForm, - NautobotBulkEditForm, - NautobotFilterForm, - NautobotModelForm, -) -from nautobot.extras.models import DynamicGroup, Tag -from nautobot.ipam.models import VRF, IPAddress, Prefix -from nautobot.tenancy.forms import TenancyFilterForm, TenancyForm -from nautobot.tenancy.models import Tenant +from nautobot.apps.forms import NautobotBulkEditForm, NautobotFilterForm, NautobotModelForm, TagsBulkEditFormMixin -from nautobot_firewall_models import choices, fields, models +from nautobot_firewall_models import models -class IPRangeFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "start_address", "end_address", "vrf"] - - model = models.IPRange - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - start_address = forms.CharField(required=False, label="Starting Address") - end_address = forms.CharField(required=False, label="Ending Address") - vrf = DynamicModelChoiceField(queryset=VRF.objects.all(), label="VRF", required=False) - - -class IPRangeForm(fields.IPRangeFieldMixin, LocalContextModelForm, NautobotModelForm): +class IPRangeForm(NautobotModelForm): # pylint: disable=too-many-ancestors """IPRange creation/edit form.""" - vrf = DynamicModelChoiceField(queryset=VRF.objects.all(), label="VRF", required=False) - class Meta: """Meta attributes.""" model = models.IPRange - fields = ["vrf", "description", "status", "tags"] - - -class IPRangeBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """IPRange bulk edit form.""" - - pk = DynamicModelMultipleChoiceField(queryset=models.IPRange.objects.all(), widget=forms.MultipleHiddenInput) - description = forms.CharField(required=False) - # start_address = forms.CharField(required=False) - # end_address = forms.CharField(required=False) - vrf = DynamicModelChoiceField(queryset=VRF.objects.all(), required=False) - - class Meta: - """Meta attributes.""" - - nullable_fields = ["description", "vrf"] - - -class FQDNFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "name"] - - model = models.FQDN - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - - -class FQDNForm(LocalContextModelForm, NautobotModelForm): - """FQDN creation/edit form.""" - - ip_addresses = DynamicModelMultipleChoiceField(queryset=IPAddress.objects.all(), required=False) - - class Meta: - """Meta attributes.""" - - model = models.FQDN - fields = ["name", "description", "ip_addresses", "status", "tags"] - - -class FQDNBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """FQDN bulk edit form.""" - - pk = DynamicModelMultipleChoiceField(queryset=models.FQDN.objects.all(), widget=forms.MultipleHiddenInput) - description = forms.CharField(required=False) - ip_addresses = DynamicModelMultipleChoiceField(queryset=IPAddress.objects.all(), required=False) - - class Meta: - """Meta attributes.""" - - nullable_fields = ["description", "ip_addresses"] - - -class AddressObjectFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "name"] - - model = models.AddressObject - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - ip_address = DynamicModelChoiceField(queryset=IPAddress.objects.all(), required=False, label="IP Address") - ip_range = DynamicModelChoiceField(queryset=models.IPRange.objects.all(), required=False, label="IP Range") - prefix = DynamicModelChoiceField(queryset=Prefix.objects.all(), required=False, label="Prefix") - fqdn = DynamicModelChoiceField(queryset=models.FQDN.objects.all(), required=False, label="FQDN") - - -class AddressObjectForm(LocalContextModelForm, NautobotModelForm): - """AddressObject creation/edit form.""" - - ip_address = DynamicModelChoiceField(queryset=IPAddress.objects.all(), required=False, label="IP Address") - ip_range = DynamicModelChoiceField(queryset=models.IPRange.objects.all(), required=False, label="IP Range") - prefix = DynamicModelChoiceField(queryset=Prefix.objects.all(), required=False, label="Prefix") - fqdn = DynamicModelChoiceField(queryset=models.FQDN.objects.all(), required=False, label="FQDN") - - class Meta: - """Meta attributes.""" - - model = models.AddressObject - fields = ["name", "description", "fqdn", "ip_range", "ip_address", "prefix", "status", "tags"] - - -class AddressObjectBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """AddressObject bulk edit form.""" - - pk = DynamicModelMultipleChoiceField(queryset=models.AddressObject.objects.all(), widget=forms.MultipleHiddenInput) - description = forms.CharField(required=False) - - class Meta: - """Meta attributes.""" - - nullable_fields = ["description", "fqdn", "ip_range", "ip_address", "prefix"] - - -class AddressObjectGroupFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "name"] - - model = models.AddressObjectGroup - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - - -class AddressObjectGroupForm(LocalContextModelForm, NautobotModelForm): - """AddressObjectGroup creation/edit form.""" - - address_objects = DynamicModelMultipleChoiceField(queryset=models.AddressObject.objects.all(), required=False) - - class Meta: - """Meta attributes.""" - - model = models.AddressObjectGroup - fields = ["name", "description", "address_objects", "status", "tags"] - - -class AddressObjectGroupBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """AddressObjectGroup bulk edit form.""" - - pk = DynamicModelMultipleChoiceField( - queryset=models.AddressObjectGroup.objects.all(), widget=forms.MultipleHiddenInput - ) - description = forms.CharField(required=False) - - class Meta: - """Meta attributes.""" - - nullable_fields = [ - "description", - ] - - -class ApplicationObjectFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "name"] - - model = models.ApplicationObject - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - category = DynamicModelChoiceField( - queryset=models.ApplicationObject.objects.all(), required=False, label="Category" - ) - - -class ApplicationObjectForm(LocalContextModelForm, NautobotModelForm): - """ApplicationObject creation/edit form.""" - - class Meta: - """Meta attributes.""" - - model = models.ApplicationObject fields = [ "name", "description", - "category", - "subcategory", - "technology", - "risk", - "default_type", - "default_ip_protocol", - "status", - ] - - -class ApplicationObjectBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """ApplicationObject bulk edit form.""" - - pk = DynamicModelMultipleChoiceField( - queryset=models.ApplicationObject.objects.all(), widget=forms.MultipleHiddenInput - ) - description = forms.CharField(required=False) - risk = forms.IntegerField(required=False) - technology = forms.CharField(required=False) - category = forms.CharField(required=False) - subcategory = forms.CharField(required=False) - - class Meta: - """Meta attributes.""" - - nullable_fields = [ - "description", - "default_ip_protocol", - "default_type", - "technology", - "category", - "subcategory", - ] - - -class ApplicationObjectGroupFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "name"] - - model = models.ApplicationObjectGroup - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - - -class ApplicationObjectGroupForm(LocalContextModelForm, NautobotModelForm): - """ApplicationObjectGroup creation/edit form.""" - - application_objects = DynamicModelMultipleChoiceField(queryset=models.ApplicationObject.objects.all()) - - class Meta: - """Meta attributes.""" - - model = models.ApplicationObjectGroup - fields = ["name", "description", "application_objects", "status", "tags"] - - -class ApplicationObjectGroupBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """ApplicationObjectGroup bulk edit form.""" - - pk = DynamicModelMultipleChoiceField( - queryset=models.ApplicationObjectGroup.objects.all(), widget=forms.MultipleHiddenInput - ) - description = forms.CharField(required=False) - - class Meta: - """Meta attributes.""" - - nullable_fields = [ - "description", ] -class ServiceObjectFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "name"] - - model = models.ServiceObject - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - port = forms.IntegerField(required=False) - ip_protocol = forms.ChoiceField(choices=add_blank_choice(choices.IP_PROTOCOL_CHOICES), required=False) - - -class ServiceObjectForm(LocalContextModelForm, NautobotModelForm): - """ServiceObject creation/edit form.""" - - port = forms.CharField( - help_text="Must be a single integer representation of port OR single port range without spaces (e.g. 80 or 8080-8088)", - required=False, - ) - - class Meta: - """Meta attributes.""" - - model = models.ServiceObject - fields = ["name", "description", "port", "ip_protocol", "status", "tags"] - - -class ServiceObjectBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """ServiceObject bulk edit form.""" - - pk = DynamicModelMultipleChoiceField(queryset=models.ServiceObject.objects.all(), widget=forms.MultipleHiddenInput) - description = forms.CharField(required=False) - port = forms.CharField( - help_text="Must be a single integer representation of port OR single port range without spaces (e.g. 80 or 8080-8088)", - required=False, - ) - - class Meta: - """Meta attributes.""" - - nullable_fields = ["description", "port"] - - -class ServiceObjectGroupFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "name"] - - model = models.ServiceObjectGroup - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - - -class ServiceObjectGroupForm(LocalContextModelForm, NautobotModelForm): - """ServiceObjectGroup creation/edit form.""" - - service_objects = DynamicModelMultipleChoiceField(queryset=models.ServiceObject.objects.all(), required=False) - - class Meta: - """Meta attributes.""" - - model = models.ServiceObjectGroup - fields = ["name", "description", "service_objects", "status", "tags"] - - -class ServiceObjectGroupBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """ServiceObjectGroup bulk edit form.""" - - pk = DynamicModelMultipleChoiceField( - queryset=models.ServiceObjectGroup.objects.all(), widget=forms.MultipleHiddenInput - ) - description = forms.CharField(required=False) - - class Meta: - """Meta attributes.""" - - nullable_fields = [ - "description", - ] - - -class UserObjectFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "username", "name"] - - model = models.UserObject - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - username = forms.CharField(required=False, label="Username") - - -class UserObjectForm(LocalContextModelForm, NautobotModelForm): - """UserObject creation/edit form.""" - - username = forms.CharField(label="Username") - name = forms.CharField( - label="Name", - required=False, - ) - - class Meta: - """Meta attributes.""" - - model = models.UserObject - fields = ["username", "name", "status", "tags"] - - -class UserObjectBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """UserObject bulk edit form.""" - - pk = DynamicModelMultipleChoiceField(queryset=models.UserObject.objects.all(), widget=forms.MultipleHiddenInput) - name = forms.CharField(required=False) - - class Meta: - """Meta attributes.""" - - nullable_fields = [ - "name", - ] - - -class UserObjectGroupFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "name"] - - model = models.UserObjectGroup - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - - -class UserObjectGroupForm(LocalContextModelForm, NautobotModelForm): - """UserObjectGroup creation/edit form.""" - - user_objects = DynamicModelMultipleChoiceField(queryset=models.UserObject.objects.all(), required=False) - - class Meta: - """Meta attributes.""" - - model = models.UserObjectGroup - fields = ["name", "description", "user_objects", "status", "tags"] - - -class UserObjectGroupBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """UserObjectGroup bulk edit form.""" - - pk = DynamicModelMultipleChoiceField( - queryset=models.UserObjectGroup.objects.all(), widget=forms.MultipleHiddenInput - ) - description = forms.CharField(required=False) - - class Meta: - """Meta attributes.""" - - nullable_fields = [ - "description", - ] - - -class ZoneFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "name"] - - model = models.Zone - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - vrfs = DynamicModelChoiceField(queryset=VRF.objects.all(), label="VRF") - interfaces = DynamicModelChoiceField(queryset=Interface.objects.all(), label="Interface") - - -class ZoneForm(LocalContextModelForm, NautobotModelForm): - """Zone creation/edit form.""" - - vrfs = DynamicModelMultipleChoiceField(queryset=VRF.objects.all(), required=False, label="VRF") - device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False) - interfaces = DynamicModelMultipleChoiceField( - queryset=Interface.objects.all(), required=False, label="Interface", query_params={"device_id": "$device"} - ) - - class Meta: - """Meta attributes.""" - - model = models.Zone - fields = ["name", "description", "vrfs", "device", "interfaces", "status", "tags"] - - -class ZoneBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """Zone bulk edit form.""" - - pk = DynamicModelMultipleChoiceField(queryset=models.Zone.objects.all(), widget=forms.MultipleHiddenInput) - description = forms.CharField(required=False) - vrfs = DynamicModelMultipleChoiceField(queryset=VRF.objects.all(), required=False, label="VRF") - interfaces = DynamicModelMultipleChoiceField(queryset=Interface.objects.all(), required=False, label="Interface") - - class Meta: - """Meta attributes.""" - - nullable_fields = ["description", "vrfs", "interfaces"] - - -class PolicyRuleFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "name"] - - model = models.PolicyRule - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - tag = TagFilterField(models.PolicyRule) - - -class PolicyRuleForm(LocalContextModelForm, NautobotModelForm): - """PolicyRule creation/edit form.""" - - tags = DynamicModelMultipleChoiceField(queryset=Tag.objects.all(), required=False) - source_users = DynamicModelMultipleChoiceField( - queryset=models.UserObject.objects.all(), label="Source User Objects", required=False - ) - source_user_groups = DynamicModelMultipleChoiceField( - queryset=models.UserObjectGroup.objects.all(), label="Source User Object Groups", required=False - ) - source_addresses = DynamicModelMultipleChoiceField( - queryset=models.AddressObject.objects.all(), label="Source Address Objects", required=False - ) - source_address_groups = DynamicModelMultipleChoiceField( - queryset=models.AddressObjectGroup.objects.all(), label="Source Address Object Groups", required=False - ) - source_zone = DynamicModelChoiceField(queryset=models.Zone.objects.all(), label="Source Zone", required=False) - source_services = DynamicModelMultipleChoiceField( - queryset=models.ServiceObject.objects.all(), label="Source Service Objects", required=False - ) - source_service_groups = DynamicModelMultipleChoiceField( - queryset=models.ServiceObjectGroup.objects.all(), label="Source Service Object Groups", required=False - ) - destination_addresses = DynamicModelMultipleChoiceField( - queryset=models.AddressObject.objects.all(), label="Destination Address Objects", required=False - ) - destination_address_groups = DynamicModelMultipleChoiceField( - queryset=models.AddressObjectGroup.objects.all(), label="Destination Address Object Groups", required=False - ) - destination_zone = DynamicModelChoiceField( - queryset=models.Zone.objects.all(), label="Destination Zone", required=False - ) - destination_services = DynamicModelMultipleChoiceField( - queryset=models.ServiceObject.objects.all(), label="Destination Service Objects", required=False - ) - destination_service_groups = DynamicModelMultipleChoiceField( - queryset=models.ServiceObjectGroup.objects.all(), label="Destination Service Object Groups", required=False - ) - applications = DynamicModelMultipleChoiceField( - queryset=models.ApplicationObject.objects.all(), label="Destination Application Objects", required=False - ) - application_groups = DynamicModelMultipleChoiceField( - queryset=models.ApplicationObjectGroup.objects.all(), - label="Destination Application Object Groups", - required=False, - ) - request_id = forms.CharField(required=False, label="Optional field for request ticket identifier.") - - class Meta: - """Meta attributes.""" - - model = models.PolicyRule - fields = ( - # pylint: disable=duplicate-code - "name", - "index", - "source_users", - "source_user_groups", - "source_addresses", - "source_address_groups", - "source_zone", - "source_services", - "source_service_groups", - "destination_addresses", - "destination_address_groups", - "destination_zone", - "destination_services", - "destination_service_groups", - "applications", - "application_groups", - "action", - "log", - "status", - "tags", - "request_id", - "description", - ) - - -# TODO: Refactor -class PolicyRuleBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """PolicyRule bulk edit form.""" - - pk = DynamicModelMultipleChoiceField(queryset=models.PolicyRule.objects.all(), widget=forms.MultipleHiddenInput) - action = forms.ChoiceField(choices=add_blank_choice(choices.ACTION_CHOICES), required=False) - log = forms.BooleanField(required=False) - description = forms.CharField(required=False) - - class Meta: - """Meta attributes.""" - - nullable_fields = ["description", "tags"] - - -class PolicyFilterForm(LocalContextFilterForm, NautobotFilterForm, TenancyFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "name", "assigned_devices", "assigned_dynamic_groups"] - - model = models.Policy - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - assigned_devices = DynamicModelChoiceField(queryset=Device.objects.all(), required=False) - assigned_dynamic_groups = DynamicModelChoiceField(queryset=DynamicGroup.objects.all(), required=False) - - -class PolicyForm(LocalContextModelForm, NautobotModelForm, TenancyForm): - """Policy creation/edit form.""" - - assigned_devices = DynamicModelMultipleChoiceField(queryset=Device.objects.all(), required=False) - assigned_dynamic_groups = DynamicModelMultipleChoiceField(queryset=DynamicGroup.objects.all(), required=False) - policy_rules = DynamicModelMultipleChoiceField(queryset=models.PolicyRule.objects.all(), required=False) - - class Meta: - """Meta attributes.""" - - model = models.Policy - fields = [ - "name", - "description", - "policy_rules", - "status", - "assigned_devices", - "assigned_dynamic_groups", - "tenant_group", - "tenant", - "tags", - ] - - -class PolicyBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """Policy bulk edit form.""" +class IPRangeBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm): # pylint: disable=too-many-ancestors + """IPRange bulk edit form.""" - pk = DynamicModelMultipleChoiceField(queryset=models.Policy.objects.all(), widget=forms.MultipleHiddenInput) + pk = forms.ModelMultipleChoiceField(queryset=models.IPRange.objects.all(), widget=forms.MultipleHiddenInput) description = forms.CharField(required=False) - assigned_devices = DynamicModelMultipleChoiceField(queryset=Device.objects.all(), required=False) - assigned_dynamic_groups = DynamicModelMultipleChoiceField(queryset=DynamicGroup.objects.all(), required=False) - policy_rules = DynamicModelMultipleChoiceField(queryset=models.PolicyRule.objects.all(), required=False) - tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False) class Meta: """Meta attributes.""" @@ -687,256 +33,15 @@ class Meta: ] -# NATPolicy - - -class NATPolicyRuleFilterForm(LocalContextFilterForm, NautobotFilterForm): +class IPRangeFilterForm(NautobotFilterForm): """Filter form to filter searches.""" + model = models.IPRange field_order = ["q", "name"] - model = models.NATPolicyRule - q = forms.CharField( - required=False, - label="Search", - help_text="Search within Name or Description.", - ) - name = forms.CharField(required=False, label="Name") - tag = TagFilterField(models.NATPolicyRule) - - -class NATPolicyRuleForm(LocalContextModelForm, NautobotModelForm): - """NATPolicyRule creation/edit form.""" - - # Metadata - tags = DynamicModelMultipleChoiceField(queryset=Tag.objects.all(), required=False) - request_id = forms.CharField(required=False, label="Optional field for request ticket identifier.") - - # Data that can not undergo a translation - source_zone = DynamicModelChoiceField(queryset=models.Zone.objects.all(), label="Source Zone", required=False) - destination_zone = DynamicModelChoiceField( - queryset=models.Zone.objects.all(), label="Destination Zone", required=False - ) - - # Original source data - original_source_addresses = DynamicModelMultipleChoiceField( - queryset=models.AddressObject.objects.all(), label="Original Source Address Objects", required=False - ) - original_source_address_groups = DynamicModelMultipleChoiceField( - queryset=models.AddressObjectGroup.objects.all(), label="Original Source Address Object Groups", required=False - ) - original_source_services = DynamicModelMultipleChoiceField( - queryset=models.ServiceObject.objects.all(), label="Original Source Service Objects", required=False - ) - original_source_service_groups = DynamicModelMultipleChoiceField( - queryset=models.ServiceObjectGroup.objects.all(), label="Original Source Service Object Groups", required=False - ) - - # Translated source data - translated_source_addresses = DynamicModelMultipleChoiceField( - queryset=models.AddressObject.objects.all(), label="Translated Source Address Objects", required=False - ) - translated_source_address_groups = DynamicModelMultipleChoiceField( - queryset=models.AddressObjectGroup.objects.all(), - label="Translated Source Address Object Groups", - required=False, - ) - translated_source_services = DynamicModelMultipleChoiceField( - queryset=models.ServiceObject.objects.all(), label="Translated Source Service Objects", required=False - ) - translated_source_service_groups = DynamicModelMultipleChoiceField( - queryset=models.ServiceObjectGroup.objects.all(), - label="Translated Source Service Object Groups", - required=False, - ) - - # Original destination data - original_destination_addresses = DynamicModelMultipleChoiceField( - queryset=models.AddressObject.objects.all(), label="Original Destination Address Objects", required=False - ) - original_destination_address_groups = DynamicModelMultipleChoiceField( - queryset=models.AddressObjectGroup.objects.all(), - label="Original Destination Address Object Groups", - required=False, - ) - original_destination_services = DynamicModelMultipleChoiceField( - queryset=models.ServiceObject.objects.all(), label="Original Destination Service Objects", required=False - ) - original_destination_service_groups = DynamicModelMultipleChoiceField( - queryset=models.ServiceObjectGroup.objects.all(), - label="Original Destination Service Object Groups", - required=False, - ) - - # Translated destination data - translated_destination_addresses = DynamicModelMultipleChoiceField( - queryset=models.AddressObject.objects.all(), label="Translated Destination Address Objects", required=False - ) - translated_destination_address_groups = DynamicModelMultipleChoiceField( - queryset=models.AddressObjectGroup.objects.all(), - label="Translated Destination Address Object Groups", - required=False, - ) - translated_destination_services = DynamicModelMultipleChoiceField( - queryset=models.ServiceObject.objects.all(), label="Translated Destination Service Objects", required=False - ) - translated_destination_service_groups = DynamicModelMultipleChoiceField( - queryset=models.ServiceObjectGroup.objects.all(), - label="Translated Destination Service Object Groups", - required=False, - ) - - class Meta: - """Meta attributes.""" - - model = models.NATPolicyRule - fields = ( - # pylint: disable=duplicate-code - "name", - "source_zone", - "destination_zone", - "original_source_addresses", - "original_source_address_groups", - "original_source_services", - "original_source_service_groups", - "translated_source_addresses", - "translated_source_address_groups", - "translated_source_services", - "translated_source_service_groups", - "original_destination_addresses", - "original_destination_address_groups", - "original_destination_services", - "original_destination_service_groups", - "translated_destination_addresses", - "translated_destination_address_groups", - "translated_destination_services", - "translated_destination_service_groups", - "remark", - "log", - "status", - "tags", - "request_id", - "description", - ) - - -# TODO: Refactor -class NATPolicyRuleBulkEditForm(PolicyRuleBulkEditForm): - """NATPolicyRule bulk edit form.""" - - pk = DynamicModelMultipleChoiceField(queryset=models.NATPolicyRule.objects.all(), widget=forms.MultipleHiddenInput) - log = forms.BooleanField(required=False) - description = forms.CharField(required=False) - - class Meta: - """Meta attributes.""" - - nullable_fields = ["description", "tags"] - - -class NATPolicyFilterForm(NautobotFilterForm, TenancyFilterForm): - """Filter form to filter searches.""" - - field_order = ["q", "name", "assigned_devices", "assigned_dynamic_groups"] - - model = models.NATPolicy q = forms.CharField( required=False, label="Search", - help_text="Search within Name or Description.", + help_text="Search within Name or Slug.", ) name = forms.CharField(required=False, label="Name") - assigned_devices = DynamicModelChoiceField(queryset=Device.objects.all(), required=False) - assigned_dynamic_groups = DynamicModelChoiceField(queryset=DynamicGroup.objects.all(), required=False) - - -class NATPolicyForm(LocalContextModelForm, NautobotModelForm, TenancyForm): - """NATPolicy creation/edit form.""" - - assigned_devices = DynamicModelMultipleChoiceField(queryset=Device.objects.all(), required=False) - assigned_dynamic_groups = DynamicModelMultipleChoiceField(queryset=DynamicGroup.objects.all(), required=False) - nat_policy_rules = DynamicModelMultipleChoiceField(queryset=models.NATPolicyRule.objects.all(), required=False) - - class Meta: - """Meta attributes.""" - - model = models.NATPolicy - fields = [ - "name", - "description", - "nat_policy_rules", - "status", - "assigned_devices", - "assigned_dynamic_groups", - "tenant_group", - "tenant", - "tags", - ] - - -class NATPolicyBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """NATPolicy bulk edit form.""" - - pk = DynamicModelMultipleChoiceField(queryset=models.NATPolicy.objects.all(), widget=forms.MultipleHiddenInput) - description = forms.CharField(required=False) - assigned_devices = DynamicModelMultipleChoiceField(queryset=Device.objects.all(), required=False) - assigned_dynamic_groups = DynamicModelMultipleChoiceField(queryset=DynamicGroup.objects.all(), required=False) - policy_rules = DynamicModelMultipleChoiceField(queryset=models.NATPolicyRule.objects.all(), required=False) - tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False) - - class Meta: - """Meta attributes.""" - - nullable_fields = [ - "description", - ] - - -# CapircaPolicy - - -class CapircaPolicyForm(LocalContextModelForm, NautobotModelForm): - """Filter Form for CapircaPolicy instances.""" - - device = DynamicModelChoiceField(queryset=Device.objects.all()) - - class Meta: - """Boilerplate form Meta data for compliance rule.""" - - model = models.CapircaPolicy - fields = ( - "device", - "pol", - "net", - "svc", - "cfg", - ) - - -class CapircaPolicyFilterForm(LocalContextFilterForm, NautobotFilterForm): - """Form for CapircaPolicy instances.""" - - model = models.CapircaPolicy - - q = forms.CharField(required=False, label="Search") - - -class CapircaPolicyBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): - """BulkEdit form for CapircaPolicy instances.""" - - pk = forms.ModelMultipleChoiceField(queryset=models.CapircaPolicy.objects.all(), widget=forms.MultipleHiddenInput) - - class Meta: - """Boilerplate form Meta data for CapircaPolicy.""" - - nullable_fields = [] - - -class CapircaPolicyCSVForm(CustomFieldModelCSVForm): - """CSV Form for CapircaPolicy instances.""" - - class Meta: - """Boilerplate form Meta data for CapircaPolicy.""" - - model = models.CapircaPolicy - fields = models.CapircaPolicy.csv_headers diff --git a/nautobot_firewall_models/models.py b/nautobot_firewall_models/models.py new file mode 100644 index 0000000..cbe55cf --- /dev/null +++ b/nautobot_firewall_models/models.py @@ -0,0 +1,38 @@ +"""Models for Nautobot Firewall Models.""" + +# Django imports +from django.db import models + +# Nautobot imports +from nautobot.apps.models import PrimaryModel + +# from nautobot.apps.models import extras_features +# If you want to use the extras_features decorator please reference the following documentation +# https://docs.nautobot.com/projects/core/en/latest/plugins/development/#using-the-extras_features-decorator-for-graphql +# Then based on your reading you may decide to put the following decorator before the declaration of your class +# @extras_features("custom_fields", "custom_validators", "relationships", "graphql") + + +# If you want to choose a specific model to overload in your class declaration, please reference the following documentation: +# how to chose a database model: https://docs.nautobot.com/projects/core/en/stable/plugins/development/#database-models +class IPRange(PrimaryModel): # pylint: disable=too-many-ancestors + """Base model for Nautobot Firewall Models app.""" + + name = models.CharField(max_length=100, unique=True) + description = models.CharField(max_length=200, blank=True) + # additional model fields + + class Meta: + """Meta class.""" + + ordering = ["name"] + + # Option for fixing capitalization (i.e. "Snmp" vs "SNMP") + # verbose_name = "Nautobot Firewall Models" + + # Option for fixing plural name (i.e. "Chicken Tenders" vs "Chicken Tendies") + # verbose_name_plural = "Nautobot Firewall Modelss" + + def __str__(self): + """Stringify instance.""" + return self.name diff --git a/nautobot_firewall_models/navigation.py b/nautobot_firewall_models/navigation.py index a7bd280..5f23939 100644 --- a/nautobot_firewall_models/navigation.py +++ b/nautobot_firewall_models/navigation.py @@ -1,224 +1,24 @@ """Menu items.""" -from nautobot.core.apps import NavMenuAddButton, NavMenuGroup, NavMenuItem, NavMenuTab +from nautobot.apps.ui import NavMenuAddButton, NavMenuGroup, NavMenuItem, NavMenuTab + +items = ( + NavMenuItem( + link="plugins:nautobot_firewall_models:iprange_list", + name="Nautobot Firewall Models", + permissions=["nautobot_firewall_models.view_iprange"], + buttons=( + NavMenuAddButton( + link="plugins:nautobot_firewall_models:iprange_add", + permissions=["nautobot_firewall_models.add_iprange"], + ), + ), + ), +) menu_items = ( NavMenuTab( - name="Security", - # weight=150, - groups=[ - NavMenuGroup( - name="Address", - weight=100, - items=[ - NavMenuItem( - link="plugins:nautobot_firewall_models:fqdn_list", - name="FQDNs", - permissions=["nautobot_firewall_models.view_fqdn"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:fqdn_add", - permissions=["nautobot_firewall_models.add_fqdn"], - ), - ], - ), - NavMenuItem( - link="plugins:nautobot_firewall_models:iprange_list", - name="IP Ranges", - permissions=["nautobot_firewall_models.view_iprange"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:iprange_add", - permissions=["nautobot_firewall_models.add_iprange"], - ), - ], - ), - NavMenuItem( - link="plugins:nautobot_firewall_models:addressobject_list", - name="Address Objects", - permissions=["nautobot_firewall_models.view_addressobject"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:addressobject_add", - permissions=["nautobot_firewall_models.add_addressobject"], - ), - ], - ), - NavMenuItem( - link="plugins:nautobot_firewall_models:addressobjectgroup_list", - name="Address Object Groups", - permissions=["nautobot_firewall_models.view_addressobjectgroup"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:addressobjectgroup_add", - permissions=["nautobot_firewall_models.add_addressobjectgroup"], - ), - ], - ), - ], - ), - NavMenuGroup( - name="Service", - weight=200, - items=[ - NavMenuItem( - link="plugins:nautobot_firewall_models:applicationobject_list", - name="Applications", - permissions=["nautobot_firewall_models.view_applicationobject"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:applicationobject_add", - permissions=["nautobot_firewall_models.add_applicationobject"], - ), - ], - ), - NavMenuItem( - link="plugins:nautobot_firewall_models:applicationobjectgroup_list", - name="Application Groups", - permissions=["nautobot_firewall_models.view_applicationobjectgroup"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:applicationobjectgroup_add", - permissions=["nautobot_firewall_models.add_applicationobjectgroup"], - ), - ], - ), - NavMenuItem( - link="plugins:nautobot_firewall_models:serviceobject_list", - name="Service Objects", - permissions=["nautobot_firewall_models.view_serviceobject"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:serviceobject_add", - permissions=["nautobot_firewall_models.add_serviceobject"], - ), - ], - ), - NavMenuItem( - link="plugins:nautobot_firewall_models:serviceobjectgroup_list", - name="Service Object Groups", - permissions=["nautobot_firewall_models.view_serviceobjectgroup"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:serviceobjectgroup_add", - permissions=["nautobot_firewall_models.add_serviceobjectgroup"], - ), - ], - ), - ], - ), - NavMenuGroup( - name="User", - weight=200, - items=[ - NavMenuItem( - link="plugins:nautobot_firewall_models:userobject_list", - name="User Objects", - permissions=["nautobot_firewall_models.view_userobject"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:userobject_add", - permissions=["nautobot_firewall_models.add_userobject"], - ), - ], - ), - NavMenuItem( - link="plugins:nautobot_firewall_models:userobjectgroup_list", - name="User Object Groups", - permissions=["nautobot_firewall_models.view_userobjectgroup"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:userobjectgroup_add", - permissions=["nautobot_firewall_models.add_userobjectgroup"], - ), - ], - ), - ], - ), - NavMenuGroup( - name="Zone", - weight=200, - items=[ - NavMenuItem( - link="plugins:nautobot_firewall_models:zone_list", - name="Zones", - permissions=["nautobot_firewall_models.view_zone"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:zone_add", - permissions=["nautobot_firewall_models.add_zone"], - ), - ], - ), - ], - ), - NavMenuGroup( - name="Policy", - weight=200, - items=[ - NavMenuItem( - link="plugins:nautobot_firewall_models:policyrule_list", - name="Policy Rules", - permissions=["nautobot_firewall_models.view_policyrule"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:policyrule_add", - permissions=["nautobot_firewall_models.add_policyrule"], - ), - ], - ), - NavMenuItem( - link="plugins:nautobot_firewall_models:policy_list", - name="Policies", - permissions=["nautobot_firewall_models.view_policy"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:policy_add", - permissions=["nautobot_firewall_models.add_policy"], - ), - ], - ), - ], - ), - NavMenuGroup( - name="NAT Policy", - weight=200, - items=[ - NavMenuItem( - link="plugins:nautobot_firewall_models:natpolicyrule_list", - name="NAT Policy Rules", - permissions=["nautobot_firewall_models.view_natpolicyrule"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:natpolicyrule_add", - permissions=["nautobot_firewall_models.add_natpolicyrule"], - ), - ], - ), - NavMenuItem( - link="plugins:nautobot_firewall_models:natpolicy_list", - name="NAT Policies", - permissions=["nautobot_firewall_models.view_natpolicy"], - buttons=[ - NavMenuAddButton( - link="plugins:nautobot_firewall_models:natpolicy_add", - permissions=["nautobot_firewall_models.add_natpolicy"], - ), - ], - ), - ], - ), - NavMenuGroup( - name="Capirca", - weight=200, - items=[ - NavMenuItem( - link="plugins:nautobot_firewall_models:capircapolicy_list", - name="Capirca Policy Rules", - permissions=["nautobot_firewall_models.view_capircapolicy"], - ), - ], - ), - ], + name="Apps", + groups=(NavMenuGroup(name="Nautobot Firewall Models", items=tuple(items)),), ), ) diff --git a/nautobot_firewall_models/tables.py b/nautobot_firewall_models/tables.py index a3ce556..b9e8744 100644 --- a/nautobot_firewall_models/tables.py +++ b/nautobot_firewall_models/tables.py @@ -1,346 +1,38 @@ -"""Table Views for Firewall Models.""" +"""Tables for nautobot_firewall_models.""" import django_tables2 as tables from nautobot.apps.tables import BaseTable, ButtonsColumn, ToggleColumn -from nautobot.extras.tables import StatusTableMixin from nautobot_firewall_models import models -class IPRangeTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - start_address = tables.Column(linkify=True) - vrf = tables.LinkColumn() - actions = ButtonsColumn(models.IPRange, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.IPRange - fields = ("pk", "start_address", "end_address", "vrf", "size", "description", "status") - - -class FQDNTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - name = tables.Column(linkify=True) - actions = ButtonsColumn(models.FQDN, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.FQDN - fields = ("pk", "name", "description", "ip_addresses", "status") - - -class AddressObjectTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - name = tables.Column(linkify=True) - actions = ButtonsColumn(models.AddressObject, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.AddressObject - fields = ("pk", "name", "description", "ip_address", "ip_range", "prefix", "fqdn", "status") - - -class AddressObjectGroupTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - name = tables.Column(linkify=True) - actions = ButtonsColumn(models.AddressObjectGroup, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.AddressObjectGroup - fields = ("pk", "name", "description", "address_objects", "status") - - -class ApplicationObjectTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - name = tables.Column(linkify=True) - actions = ButtonsColumn(models.ApplicationObject, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.ApplicationObject - fields = ("pk", "name", "description", "category", "subcategory", "technology", "risk", "default_type") - - -class ApplicationObjectGroupTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - name = tables.Column(linkify=True) - application_objects = tables.ManyToManyColumn(linkify_item=True) - actions = ButtonsColumn(models.ApplicationObjectGroup, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.ApplicationObjectGroup - fields = ("pk", "name", "description", "application_objects") - - -class ServiceObjectTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - name = tables.Column(linkify=True) - actions = ButtonsColumn(models.ServiceObject, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.ServiceObject - fields = ("pk", "name", "port", "ip_protocol", "description", "status") - - -class ServiceObjectGroupTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - name = tables.Column(linkify=True) - actions = ButtonsColumn(models.ServiceObjectGroup, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.ServiceObjectGroup - fields = ("pk", "name", "description", "service_objects", "status") - - -class UserObjectTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - username = tables.Column(linkify=True) - actions = ButtonsColumn(models.UserObject, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.UserObject - fields = ("pk", "username", "name", "status") - - -class UserObjectGroupTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - name = tables.Column(linkify=True) - actions = ButtonsColumn(models.UserObjectGroup, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.UserObjectGroup - fields = ("pk", "name", "description", "user_objects", "status") - - -class ZoneTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - name = tables.Column(linkify=True) - actions = ButtonsColumn(models.Zone, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.Zone - fields = ("pk", "name", "vrfs", "interfaces", "description", "status") - - -# TODO: refactor -class PolicyRuleTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - name = tables.LinkColumn() - actions = ButtonsColumn(models.PolicyRule, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.PolicyRule - fields = ( - # pylint: disable=duplicate-code - "pk", - "name", - "source_users", - "source_user_groups", - "source_addresses", - "source_address_groups", - "source_zone", - "source_services", - "source_service_groups", - "destination_addresses", - "destination_address_groups", - "destination_zone", - "destination_services", - "destination_service_groups", - "applications", - "application_groups", - "action", - "description", - "request_id", - "log", - "status", - ) - default_columns = ( - "pk", - "name", - "source_users", - "source_user_groups", - "source_addresses", - "source_address_groups", - "source_zone", - "source_services", - "source_service_groups", - "destination_addresses", - "destination_address_groups", - "destination_zone", - "destination_services", - "destination_service_groups", - "applications", - "application_groups", - "action", - "log", - "status", - ) - - -class PolicyTable(StatusTableMixin, BaseTable): +class IPRangeTable(BaseTable): + # pylint: disable=R0903 """Table for list view.""" pk = ToggleColumn() name = tables.Column(linkify=True) - actions = ButtonsColumn(models.Policy, buttons=("edit", "delete")) - assigned_devices = tables.ManyToManyColumn(linkify_item=True) - assigned_dynamic_groups = tables.ManyToManyColumn(linkify_item=True) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.Policy - fields = ("pk", "name", "description", "policy_rules", "assigned_devices", "assigned_dynamic_groups", "status") - - -# TODO: refactor -class NATPolicyRuleTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - name = tables.LinkColumn() - actions = ButtonsColumn(models.NATPolicyRule, buttons=("edit", "delete")) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.NATPolicyRule - fields = ( - # pylint: disable=duplicate-code - "pk", - "name", - "source_zone", - "destination_zone", - "original_source_addresses", - "original_source_address_groups", - "original_source_services", - "original_source_service_groups", - "translated_source_addresses", - "translated_source_address_groups", - "translated_source_services", - "translated_source_service_groups", - "original_destination_addresses", - "original_destination_address_groups", - "original_destination_services", - "original_destination_service_groups", - "translated_destination_addresses", - "translated_destination_address_groups", - "translated_destination_services", - "translated_destination_service_groups", - "remark", - "request_id", - "description", - "log", - "status", - ) - default_columns = ( - # pylint: disable=duplicate-code - "pk", - "name", - "source_zone", - "destination_zone", - "original_source_addresses", - "original_source_address_groups", - "original_source_services", - "original_source_service_groups", - "translated_source_addresses", - "translated_source_address_groups", - "translated_source_services", - "translated_source_service_groups", - "original_destination_addresses", - "original_destination_address_groups", - "original_destination_services", - "original_destination_service_groups", - "translated_destination_addresses", - "translated_destination_address_groups", - "translated_destination_services", - "translated_destination_service_groups", - "remark", - "log", - "status", - ) - - -class NATPolicyTable(StatusTableMixin, BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - name = tables.Column(linkify=True) - nat_policy_rules = tables.ManyToManyColumn(verbose_name="NAT policy rules", linkify_item=True) - actions = ButtonsColumn(models.NATPolicy, buttons=("edit", "delete")) - assigned_devices = tables.ManyToManyColumn(linkify_item=True) - assigned_dynamic_groups = tables.ManyToManyColumn(linkify_item=True) + actions = ButtonsColumn( + models.IPRange, + # Option for modifying the default action buttons on each row: + # buttons=("changelog", "edit", "delete"), + # Option for modifying the pk for the action buttons: + pk_field="pk", + ) class Meta(BaseTable.Meta): """Meta attributes.""" - model = models.NATPolicy + model = models.IPRange fields = ( "pk", "name", "description", - "nat_policy_rules", - "assigned_devices", - "assigned_dynamic_groups", - "status", ) - -class CapircaPolicyTable(BaseTable): - """Table for list view.""" - - pk = ToggleColumn() - device = tables.TemplateColumn( - template_code="""{{ record.device }} """ - ) - - class Meta(BaseTable.Meta): - """Meta attributes.""" - - model = models.CapircaPolicy - fields = ("pk", "device") + # Option for modifying the columns that show up in the list view by default: + # default_columns = ( + # "pk", + # "name", + # "description", + # ) diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/iprange_retrieve.html b/nautobot_firewall_models/templates/nautobot_firewall_models/iprange_retrieve.html index 178f200..33be024 100644 --- a/nautobot_firewall_models/templates/nautobot_firewall_models/iprange_retrieve.html +++ b/nautobot_firewall_models/templates/nautobot_firewall_models/iprange_retrieve.html @@ -1,36 +1,26 @@ + {% extends 'generic/object_retrieve.html' %} {% load helpers %} {% block content_left_page %}
- IP Range + IPRange
- - - - - - - - - - + + - - - - - - - - - - + +
Description{{ object.description|placeholder }}
Start Address{{ object.start_address }}
End Address{{ object.end_address }}Name + {{ object.name }} +
Size{{ object.size }}
VRF{{ object.vrf|placeholder }}
Status{{ object.get_status_display }}Description + {{ object.description|placeholder }} +
-{% endblock %} \ No newline at end of file +{% endblock content_left_page %} + diff --git a/nautobot_firewall_models/tests/fixtures.py b/nautobot_firewall_models/tests/fixtures.py index 51b7d37..deaa542 100644 --- a/nautobot_firewall_models/tests/fixtures.py +++ b/nautobot_firewall_models/tests/fixtures.py @@ -1,439 +1,10 @@ -"""Create basic objects for use in test class setup.""" +"""Create fixtures for tests.""" -# ruff: noqa: F403, F405 -from django.contrib.contenttypes.models import ContentType -from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer, Platform -from nautobot.extras.models import DynamicGroup, Job, Role -from nautobot.extras.models.statuses import Status -from nautobot.ipam.models import VRF, Namespace, Prefix -from nautobot.ipam.models import IPAddress as IPAddr -from nautobot.tenancy.models import Tenant, TenantGroup +from nautobot_firewall_models.models import IPRange -from nautobot_firewall_models.models import * # pylint: disable=unused-wildcard-import, wildcard-import - -def create_ip_range(): - """Creates 3 IPRange objects.""" - status = Status.objects.get(name="Active") - vrf, _ = VRF.objects.get_or_create(name="random_vrf") - IPRange.objects.get_or_create(start_address="192.168.0.1", end_address="192.168.0.10", vrf=None, status=status) - IPRange.objects.get_or_create(start_address="192.168.0.1", end_address="192.168.0.10", vrf=vrf, status=status) - return IPRange.objects.get_or_create(start_address="192.168.0.11", end_address="192.168.0.20", status=status)[0] - - -def create_fqdn(): - """Creates 3 FQDN objects.""" - status = Status.objects.get(name="Active") - FQDN.objects.get_or_create(name="test.dev", status=status) - FQDN.objects.get_or_create(name="test.uat", status=status) - return FQDN.objects.get_or_create(name="test.prod", status=status)[0] - - -def create_addr_obj(): - """Creates 3 of all objects.""" - # Core Models - status = Status.objects.get(name="Active") - namespace, _ = Namespace.objects.get_or_create(name="global") - prefix, _ = Prefix.objects.get_or_create(network="10.0.0.0", prefix_length=24, namespace=namespace, status=status) - ip_address, _ = IPAddr.objects.get_or_create(address="10.0.0.1", status=status, parent=prefix) - - # Plugin Models - ip_range = create_ip_range() - fqdn = create_fqdn() - addr_obj1, _ = AddressObject.objects.get_or_create(name="printer", ip_range=ip_range, status=status) - addr_obj2, _ = AddressObject.objects.get_or_create(name="voice", ip_address=ip_address, status=status) - addr_obj3, _ = AddressObject.objects.get_or_create(name="storage", prefix=prefix, status=status) - addr_obj4, _ = AddressObject.objects.get_or_create(name="server", fqdn=fqdn, status=status) - return addr_obj1, addr_obj2, addr_obj3, addr_obj4 - - -def create_addr_group(): - """Creates 3 of all objects.""" - status = Status.objects.get(name="Active") - addr_obj1, addr_obj2, addr_obj3, addr_obj4 = create_addr_obj() - addr_grp1, _ = AddressObjectGroup.objects.get_or_create(name="addr group1", status=status) - addr_grp1.address_objects.set([addr_obj1, addr_obj2]) - addr_grp2, _ = AddressObjectGroup.objects.get_or_create(name="addr group2", status=status) - addr_grp2.address_objects.set([addr_obj3, addr_obj4]) - addr_grp3, _ = AddressObjectGroup.objects.get_or_create(name="addr group3", status=status) - addr_grp3.address_objects.set([addr_obj1, addr_obj2, addr_obj3, addr_obj4]) - return addr_grp1, addr_grp2, addr_grp3 - - -def create_svc_obj(): - """Creates 3 of all objects.""" - status = Status.objects.get(name="Active") - svc_obj1, _ = ServiceObject.objects.get_or_create(name="PGSQL", port="5432", ip_protocol="TCP", status=status) - svc_obj2, _ = ServiceObject.objects.get_or_create(name="SSH", port="22", ip_protocol="TCP", status=status) - svc_obj3, _ = ServiceObject.objects.get_or_create(name="DNS", port="53", ip_protocol="TCP", status=status) - src_svc, _ = ServiceObject.objects.get_or_create(name="Source HTTPS", port="443", ip_protocol="TCP", status=status) - return svc_obj1, svc_obj2, svc_obj3, src_svc - - -def create_svc_group(): - """Creates 3 of all objects.""" - svc_obj1, svc_obj2, svc_obj3, _ = create_svc_obj() - status = Status.objects.get(name="Active") - svc_grp1, _ = ServiceObjectGroup.objects.get_or_create(name="svc group1", status=status) - svc_grp1.service_objects.set([svc_obj1]) - svc_grp2, _ = ServiceObjectGroup.objects.get_or_create(name="svc group2", status=status) - svc_grp2.service_objects.set([svc_obj2, svc_obj3]) - svc_grp3, _ = ServiceObjectGroup.objects.get_or_create(name="svc group3", status=status) - svc_grp3.service_objects.set([svc_obj1, svc_obj2, svc_obj3]) - return svc_grp1, svc_grp2, svc_grp3 - - -def create_user_obj(): - """Creates 3 of all objects.""" - status = Status.objects.get(name="Active") - usr_obj1, _ = UserObject.objects.get_or_create(username="user1", name="Bob", status=status) - usr_obj2, _ = UserObject.objects.get_or_create(username="user2", name="Fred", status=status) - usr_obj3, _ = UserObject.objects.get_or_create(username="user3", name="Tom", status=status) - return usr_obj1, usr_obj2, usr_obj3 - - -def create_user_group(): - """Creates 3 of all objects.""" - status = Status.objects.get(name="Active") - usr_obj1, usr_obj2, usr_obj3 = create_user_obj() - usr_grp1, _ = UserObjectGroup.objects.get_or_create(name="usr group1", status=status) - usr_grp1.user_objects.set([usr_obj1]) - usr_grp2, _ = UserObjectGroup.objects.get_or_create(name="usr group2", status=status) - usr_grp2.user_objects.set([usr_obj1, usr_obj2]) - usr_grp3, _ = UserObjectGroup.objects.get_or_create(name="usr group3", status=status) - usr_grp3.user_objects.set([usr_obj1, usr_obj2, usr_obj3]) - return usr_grp1, usr_grp2, usr_grp3 - - -def create_zone(): - """Creates 3 of all objects.""" - status = Status.objects.get(name="Active") - vrf, _ = VRF.objects.get_or_create(name="global") - zone1, _ = Zone.objects.get_or_create(name="WAN", status=status) - zone1.vrfs.set([vrf]) - zone2, _ = Zone.objects.get_or_create(name="LAN", status=status) - zone3, _ = Zone.objects.get_or_create(name="DMZ", status=status) - return zone1, zone2, zone3 - - -def create_app_obj(): - """Creates 3 of all objects.""" - status = Status.objects.get(name="Active") - app1, _ = ApplicationObject.objects.get_or_create( - name="app1", - category="web", - subcategory="streaming", - default_type="443", - default_ip_protocol="TCP", - status=status, - risk=3, - description="some description", - ) - app2, _ = ApplicationObject.objects.get_or_create( - name="app2", - category="web", - subcategory="streaming", - default_type="443", - default_ip_protocol="TCP", - status=status, - risk=2, - description="some description", - ) - app3, _ = ApplicationObject.objects.get_or_create( - name="app3", - category="web", - subcategory="streaming", - default_type="443", - default_ip_protocol="TCP", - status=status, - risk=1, - description="some description", - ) - return app1, app2, app3 - - -def create_app_group(): - """Creates 3 of all objects.""" - app1, app2, app3 = create_app_obj() - status = Status.objects.get(name="Active") - app_grp1, _ = ApplicationObjectGroup.objects.get_or_create( - name="streaming", description="some description", status=status - ) - app_grp1.application_objects.set([app1]) - app_grp2, _ = ApplicationObjectGroup.objects.get_or_create( - name="gaming", description="some description", status=status - ) - app_grp2.application_objects.set([app3, app2]) - app_grp3, _ = ApplicationObjectGroup.objects.get_or_create( - name="news", description="some description", status=status - ) - app_grp3.application_objects.set([app1, app2, app3]) - return app_grp1, app_grp2, app_grp3 - - -def create_policy_rule(): # pylint: disable=too-many-locals - """Creates 3 of all objects.""" - app1, app2, app3 = create_app_obj() - app_grp1, app_grp2, app_grp3 = create_app_group() - usr_obj1, usr_obj2, usr_obj3 = create_user_obj() - usr_grp1, usr_grp2, usr_grp3 = create_user_group() - svc_obj1, svc_obj2, svc_obj3, src_svc = create_svc_obj() - svc_grp1, svc_grp2, svc_grp3 = create_svc_group() - zone1, zone2, _ = create_zone() - addr_obj1, addr_obj2, addr_obj3, addr_obj4 = create_addr_obj() - addr_grp1, addr_grp2, addr_grp3 = create_addr_group() - status = Status.objects.get(name="Active") - pol_rule1, _ = PolicyRule.objects.get_or_create( - action="deny", - log=True, - name="Policy Rule 1", - status=status, - request_id="req1", - index=10, - description="some description", - ) - pol_rule1.source_users.set([usr_obj1]) - pol_rule1.source_user_groups.set([usr_grp1]) - pol_rule1.source_addresses.set([addr_obj1]) - pol_rule1.source_address_groups.set([addr_grp1]) - pol_rule1.source_services.set([src_svc]) - pol_rule1.destination_addresses.set([addr_obj4]) - pol_rule1.destination_address_groups.set([addr_grp3]) - pol_rule1.destination_services.set([svc_obj1]) - pol_rule1.destination_service_groups.set([svc_grp1]) - pol_rule1.applications.set([app1]) - pol_rule1.application_groups.set([app_grp1]) - pol_rule2, _ = PolicyRule.objects.get_or_create( - source_zone=zone1, - destination_zone=zone2, - action="allow", - log=True, - name="Policy Rule 2", - status=status, - request_id="req2", - index=20, - description="some description", - ) - pol_rule2.source_users.set([usr_obj1, usr_obj2]) - pol_rule2.source_user_groups.set([usr_grp1, usr_grp2]) - pol_rule2.source_addresses.set([addr_obj1, addr_obj2]) - pol_rule2.source_address_groups.set([addr_grp1, addr_grp2]) - pol_rule2.destination_addresses.set([addr_obj4]) - pol_rule2.destination_address_groups.set([addr_grp3]) - pol_rule2.destination_services.set([svc_obj1, svc_obj2]) - pol_rule2.destination_service_groups.set([svc_grp1, svc_grp2]) - pol_rule2.applications.set([app2]) - pol_rule2.application_groups.set([app_grp2]) - pol_rule3, _ = PolicyRule.objects.get_or_create( - source_zone=zone1, - destination_zone=zone2, - action="drop", - log=True, - name="Policy Rule 3", - status=status, - request_id="req3", - index=30, - description="some description", - ) - pol_rule3.source_users.set([usr_obj1, usr_obj2, usr_obj3]) - pol_rule3.source_user_groups.set([usr_grp1, usr_grp2, usr_grp3]) - pol_rule3.source_addresses.set([addr_obj1, addr_obj2, addr_obj3]) - pol_rule3.source_address_groups.set([addr_grp1, addr_grp2]) - pol_rule3.destination_addresses.set([addr_obj4]) - pol_rule3.destination_address_groups.set([addr_grp3]) - pol_rule3.destination_services.set([svc_obj1, svc_obj2, svc_obj3]) - pol_rule3.destination_service_groups.set([svc_grp1, svc_grp2, svc_grp3]) - pol_rule3.applications.set([app2, app3]) - pol_rule3.application_groups.set([app_grp1, app_grp2, app_grp3]) - pol_rule4, _ = PolicyRule.objects.get_or_create( - name="END OF ACCESS LIST", action="remark", log=False, request_id="req4", index=99 - ) - pol_rule5, _ = PolicyRule.objects.get_or_create( - name="DENY ALL", action="deny", log=False, request_id="req5", index=100 - ) - return pol_rule1, pol_rule2, pol_rule3, pol_rule4, pol_rule5 - - -def create_policy(): - """Creates 3 of all objects.""" - pol_rule1, pol_rule2, pol_rule3, pol_rule4, pol_rule5 = create_policy_rule() - status = Status.objects.get(name="Active") - tenant_group, _ = TenantGroup.objects.get_or_create(name="ABC Holding Corp") - tenant1, _ = Tenant.objects.get_or_create(name="ABC LLC", tenant_group=tenant_group) - tenant2, _ = Tenant.objects.get_or_create(name="XYZ LLC") - pol1, _ = Policy.objects.get_or_create(name="Policy 1", status=status) - pol1.policy_rules.set([pol_rule1]) - pol2, _ = Policy.objects.get_or_create(name="Policy 2", status=status, tenant=tenant2) - pol2.policy_rules.set([pol_rule1, pol_rule2]) - pol3, _ = Policy.objects.get_or_create(name="Policy 3", status=status, tenant=tenant1) - pol3.policy_rules.set([pol_rule1, pol_rule2, pol_rule3, pol_rule4, pol_rule5]) - return pol1, pol2, pol3 - - -def create_natpolicy_rule(): # pylint: disable=too-many-locals - """Creates 3 of all objects.""" - status = Status.objects.get(name="Active") - namespace, _ = Namespace.objects.get_or_create(name="global") - addr_obj1, addr_obj2, addr_obj3, addr_obj4 = create_addr_obj() - # Nat policies - nat_orig_dest_service, _ = ServiceObject.objects.get_or_create( - name="HTTP", port="80", ip_protocol="TCP", status=status - ) - nat_trans_dest_service, _ = ServiceObject.objects.get_or_create( - name="HTTP (alt)", port="8080", ip_protocol="TCP", status=status - ) - original_source_prefix, _ = Prefix.objects.get_or_create( - network="10.100.0.0", prefix_length=24, status=status, namespace=namespace - ) - original_source, _ = AddressObject.objects.get_or_create(name="nat-original-source", prefix=original_source_prefix) - translated_source_prefix, _ = Prefix.objects.get_or_create( - network="10.200.0.0", prefix_length=24, status=status, namespace=namespace - ) - translated_source, _ = AddressObject.objects.get_or_create( - name="nat-translated-source", prefix=translated_source_prefix - ) - destination_prefix, _ = Prefix.objects.get_or_create( - network="192.168.0.0", prefix_length=24, status=status, namespace=namespace - ) - destination, _ = AddressObject.objects.get_or_create(name="nat-destination", prefix=destination_prefix) - nat_policy_rule_1_1, _ = NATPolicyRule.objects.get_or_create( - name="NAT Policy Rule 1.1", log=True, request_id="req1" - ) - nat_policy_rule_1_1.original_source_addresses.add(original_source) - nat_policy_rule_1_1.translated_source_addresses.add(translated_source) - nat_policy_rule_1_1.original_destination_addresses.add(destination) - nat_policy_rule_1_1.translated_destination_addresses.add(destination) - nat_policy_rule_1_1.original_destination_services.add(nat_orig_dest_service) - nat_policy_rule_1_1.translated_destination_services.add(nat_trans_dest_service) - - nat_policy_rule_1_2, _ = NATPolicyRule.objects.get_or_create( - name="END OF NAT POLICY", request_id="req2", remark=True, log=True - ) - - nat_policy_rule_2_1, _ = NATPolicyRule.objects.get_or_create( - name="NAT Policy Rule 2.1", log=True, request_id="req3" - ) - nat_policy_rule_2_1.original_source_addresses.set([addr_obj1, addr_obj2]) - nat_policy_rule_2_1.translated_source_addresses.add(translated_source) - nat_policy_rule_2_1.original_destination_addresses.add(destination) - nat_policy_rule_2_1.original_destination_services.add(nat_orig_dest_service) - - nat_policy_rule_3_1, _ = NATPolicyRule.objects.get_or_create( - name="NAT Policy Rule 3.1", log=True, request_id="req4" - ) - nat_policy_rule_3_1.original_source_addresses.set([addr_obj3, addr_obj4]) - nat_policy_rule_3_1.translated_source_addresses.add(translated_source) - nat_policy_rule_3_1.original_destination_addresses.add(destination) - nat_policy_rule_3_1.original_destination_services.add(nat_orig_dest_service) - return nat_policy_rule_1_1, nat_policy_rule_1_2, nat_policy_rule_2_1, nat_policy_rule_3_1 - - -def create_natpolicy(): - """Creates 3 of all objects.""" - nat_policy_rule_1_1, nat_policy_rule_1_2, nat_policy_rule_2_1, nat_policy_rule_3_1 = create_natpolicy_rule() - nat_policy_1, _ = NATPolicy.objects.get_or_create(name="NAT Policy 1") - nat_policy_2, _ = NATPolicy.objects.get_or_create(name="NAT Policy 2") - nat_policy_3, _ = NATPolicy.objects.get_or_create(name="NAT Policy 3") - nat_policy_1.nat_policy_rules.add(nat_policy_rule_1_1) - nat_policy_1.nat_policy_rules.add(nat_policy_rule_1_2) - nat_policy_2.nat_policy_rules.add(nat_policy_rule_2_1) - nat_policy_2.nat_policy_rules.add(nat_policy_rule_3_1) - return nat_policy_1, nat_policy_2, nat_policy_3 - - -def assign_policies(): # pylint: disable=too-many-locals - """Creates 3 of all objects.""" - status = Status.objects.get(name="Active") - nat_policy_1, nat_policy_2, nat_policy_3 = create_natpolicy() - pol1, pol2, pol3 = create_policy() - # Mapping policies to devices - loc_type, _ = LocationType.objects.get_or_create(name="site") - site1, _ = Location.objects.get_or_create(name="DFW02", location_type=loc_type, status=status) - site2, _ = Location.objects.get_or_create(name="HOU02", location_type=loc_type, status=status) - jun_manufacturer, _ = Manufacturer.objects.get_or_create(name="Juniper") - jun_platform, _ = Platform.objects.get_or_create(name="Juniper", network_driver="srx") - jun_dev_type, _ = DeviceType.objects.get_or_create(manufacturer=jun_manufacturer, model="SRX300") - palo_manufacturer, _ = Manufacturer.objects.get_or_create(name="Palo Alto") - palo_platform, _ = Platform.objects.get_or_create(name="Palo Alto", network_driver="paloalto") - palo_dev_type, _ = DeviceType.objects.get_or_create(manufacturer=palo_manufacturer, model="PA-3020") - dev_role, _ = Role.objects.get_or_create(name="WAN") - dev_role.content_types.add(ContentType.objects.get_for_model(Device)) - dev1, _ = Device.objects.get_or_create( - name="DFW02-WAN00", - role=dev_role, - device_type=jun_dev_type, - location=site1, - status=status, - platform=jun_platform, - ) - Device.objects.get_or_create( - name="DFW02-WAN01", - role=dev_role, - device_type=jun_dev_type, - location=site1, - status=status, - platform=jun_platform, - ) - dev2, _ = Device.objects.get_or_create( - name="HOU02-WAN00", - role=dev_role, - device_type=palo_dev_type, - location=site2, - status=status, - platform=palo_platform, - ) - dynamic_group, _ = DynamicGroup.objects.get_or_create( - name="North Texas", content_type=ContentType.objects.get_for_model(Device) - ) - dynamic_group.filter = {"location": ["DFW02"]} - dynamic_group.validated_save() - PolicyDeviceM2M.objects.get_or_create(policy=pol1, device=dev1, weight=150) - PolicyDeviceM2M.objects.get_or_create(policy=pol2, device=dev1, weight=200) - PolicyDeviceM2M.objects.get_or_create(policy=pol1, device=dev2) - PolicyDynamicGroupM2M.objects.get_or_create(policy=pol3, dynamic_group=dynamic_group, weight=1000) - NATPolicyDeviceM2M.objects.get_or_create(nat_policy=nat_policy_1, device=dev1, weight=150) - NATPolicyDeviceM2M.objects.get_or_create(nat_policy=nat_policy_2, device=dev1, weight=200) - NATPolicyDeviceM2M.objects.get_or_create(nat_policy=nat_policy_1, device=dev2) - NATPolicyDynamicGroupM2M.objects.get_or_create(nat_policy=nat_policy_3, dynamic_group=dynamic_group, weight=1000) - - -def create_capirca_env(): - """Create objects that are Capirca Ready.""" # pylint: disable=too-many-locals, too-many-statements - assign_policies() - namespace, _ = Namespace.objects.get_or_create(name="global") - status = Status.objects.get(name="Active") - zoneall, _ = Zone.objects.get_or_create(name="all", status=status) - - pol_rule1 = PolicyRule.objects.get(name="Policy Rule 1") - pol_rule1.source_zone = Zone.objects.get(name="DMZ") - pol_rule1.destination_zone = Zone.objects.get(name="WAN") - pol_rule1.validated_save() - - pol_rule4 = PolicyRule.objects.get(name="END OF ACCESS LIST") - pol_rule4.source_zone = zoneall - pol_rule4.destination_zone = zoneall - pol_rule4.validated_save() - - pol_rule5 = PolicyRule.objects.get(name="DENY ALL") - pol_rule5.source_zone = zoneall - pol_rule5.destination_zone = zoneall - pol_rule5.validated_save() - - ip_address, _ = IPAddr.objects.get_or_create( - address="10.0.0.100", status=status, parent=Prefix.objects.get(network="10.0.0.0", namespace=namespace) - ) - prefix, _ = Prefix.objects.get_or_create(network="10.1.0.0", prefix_length=24, status=status, namespace=namespace) - - addr_obj1 = AddressObject.objects.get(name="printer") - addr_obj1.ip_range = None - addr_obj1.ip_address = ip_address - addr_obj1.validated_save() - - addr_obj4 = AddressObject.objects.get(name="server") - addr_obj4.fqdn = None - addr_obj4.prefix = prefix - addr_obj4.validated_save() - - job = Job.objects.get(name="Generate FW Config via Capirca.") - job.enabled = True - job.validated_save() +def create_iprange(): + """Fixture to create necessary number of IPRange for tests.""" + IPRange.objects.create(name="Test One") + IPRange.objects.create(name="Test Two") + IPRange.objects.create(name="Test Three") diff --git a/nautobot_firewall_models/tests/test_api_views.py b/nautobot_firewall_models/tests/test_api_views.py new file mode 100644 index 0000000..81a1a6d --- /dev/null +++ b/nautobot_firewall_models/tests/test_api_views.py @@ -0,0 +1,27 @@ +"""Unit tests for nautobot_firewall_models.""" + +from nautobot.apps.testing import APIViewTestCases + +from nautobot_firewall_models import models +from nautobot_firewall_models.tests import fixtures + + +class IPRangeAPIViewTest(APIViewTestCases.APIViewTestCase): + # pylint: disable=too-many-ancestors + """Test the API viewsets for IPRange.""" + + model = models.IPRange + create_data = [ + { + "name": "Test Model 1", + "description": "test description", + }, + { + "name": "Test Model 2", + }, + ] + bulk_update_data = {"description": "Test Bulk Update"} + + @classmethod + def setUpTestData(cls): + fixtures.create_iprange() diff --git a/nautobot_firewall_models/tests/test_filter_iprange.py b/nautobot_firewall_models/tests/test_filter_iprange.py new file mode 100644 index 0000000..3a4519c --- /dev/null +++ b/nautobot_firewall_models/tests/test_filter_iprange.py @@ -0,0 +1,28 @@ +"""Test IPRange Filter.""" + +from django.test import TestCase + +from nautobot_firewall_models import filters, models +from nautobot_firewall_models.tests import fixtures + + +class IPRangeFilterTestCase(TestCase): + """IPRange Filter Test Case.""" + + queryset = models.IPRange.objects.all() + filterset = filters.IPRangeFilterSet + + @classmethod + def setUpTestData(cls): + """Setup test data for IPRange Model.""" + fixtures.create_iprange() + + def test_q_search_name(self): + """Test using Q search with name of IPRange.""" + params = {"q": "Test One"} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) + + def test_q_invalid(self): + """Test using invalid Q search for IPRange.""" + params = {"q": "test-five"} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0) diff --git a/nautobot_firewall_models/tests/test_form_iprange.py b/nautobot_firewall_models/tests/test_form_iprange.py new file mode 100644 index 0000000..e0bcdd8 --- /dev/null +++ b/nautobot_firewall_models/tests/test_form_iprange.py @@ -0,0 +1,33 @@ +"""Test iprange forms.""" + +from django.test import TestCase + +from nautobot_firewall_models import forms + + +class IPRangeTest(TestCase): + """Test IPRange forms.""" + + def test_specifying_all_fields_success(self): + form = forms.IPRangeForm( + data={ + "name": "Development", + "description": "Development Testing", + } + ) + self.assertTrue(form.is_valid()) + self.assertTrue(form.save()) + + def test_specifying_only_required_success(self): + form = forms.IPRangeForm( + data={ + "name": "Development", + } + ) + self.assertTrue(form.is_valid()) + self.assertTrue(form.save()) + + def test_validate_name_iprange_is_required(self): + form = forms.IPRangeForm(data={"description": "Development Testing"}) + self.assertFalse(form.is_valid()) + self.assertIn("This field is required.", form.errors["name"]) diff --git a/nautobot_firewall_models/tests/test_model_iprange.py b/nautobot_firewall_models/tests/test_model_iprange.py new file mode 100644 index 0000000..9fe39b1 --- /dev/null +++ b/nautobot_firewall_models/tests/test_model_iprange.py @@ -0,0 +1,22 @@ +"""Test IPRange.""" + +from django.test import TestCase + +from nautobot_firewall_models import models + + +class TestIPRange(TestCase): + """Test IPRange.""" + + def test_create_iprange_only_required(self): + """Create with only required fields, and validate null description and __str__.""" + iprange = models.IPRange.objects.create(name="Development") + self.assertEqual(iprange.name, "Development") + self.assertEqual(iprange.description, "") + self.assertEqual(str(iprange), "Development") + + def test_create_iprange_all_fields_success(self): + """Create IPRange with all fields.""" + iprange = models.IPRange.objects.create(name="Development", description="Development Test") + self.assertEqual(iprange.name, "Development") + self.assertEqual(iprange.description, "Development Test") diff --git a/nautobot_firewall_models/tests/test_views.py b/nautobot_firewall_models/tests/test_views.py new file mode 100644 index 0000000..55f78aa --- /dev/null +++ b/nautobot_firewall_models/tests/test_views.py @@ -0,0 +1,28 @@ +"""Unit tests for views.""" + +from nautobot.apps.testing import ViewTestCases + +from nautobot_firewall_models import models +from nautobot_firewall_models.tests import fixtures + + +class IPRangeViewTest(ViewTestCases.PrimaryObjectViewTestCase): + # pylint: disable=too-many-ancestors + """Test the IPRange views.""" + + model = models.IPRange + bulk_edit_data = {"description": "Bulk edit views"} + form_data = { + "name": "Test 1", + "description": "Initial model", + } + csv_data = ( + "name", + "Test csv1", + "Test csv2", + "Test csv3", + ) + + @classmethod + def setUpTestData(cls): + fixtures.create_iprange() diff --git a/nautobot_firewall_models/urls.py b/nautobot_firewall_models/urls.py index 6b9c8bf..683e1a5 100644 --- a/nautobot_firewall_models/urls.py +++ b/nautobot_firewall_models/urls.py @@ -3,34 +3,19 @@ from django.templatetags.static import static from django.urls import path from django.views.generic import RedirectView -from nautobot.core.views.routers import NautobotUIViewSetRouter +from nautobot.apps.urls import NautobotUIViewSetRouter + + +from nautobot_firewall_models import views -from nautobot_firewall_models import viewsets router = NautobotUIViewSetRouter() -router.register("address-object", viewsets.AddressObjectUIViewSet) -router.register("address-object-group", viewsets.AddressObjectGroupUIViewSet) -router.register("application-object", viewsets.ApplicationObjectUIViewSet) -router.register("application-object-group", viewsets.ApplicationObjectGroupUIViewSet) -router.register("capirca-policy", viewsets.CapircaPolicyUIViewSet) -router.register("capirca-policy-device", viewsets.CapircaPolicyDeviceUIViewSet, basename="capircapolicy_devicedetail") -router.register("fqdn", viewsets.FQDNUIViewSet) -router.register("ip-range", viewsets.IPRangeUIViewSet) -router.register("nat-policy", viewsets.NATPolicyUIViewSet) -router.register("nat-policy-rule", viewsets.NATPolicyRuleUIViewSet) -router.register("policy", viewsets.PolicyUIViewSet) -router.register("policy-rule", viewsets.PolicyRuleUIViewSet) -router.register("service-object", viewsets.ServiceObjectUIViewSet) -router.register("service-object-group", viewsets.ServiceObjectGroupUIViewSet) -router.register("user-object", viewsets.UserObjectUIViewSet) -router.register("user-object-group", viewsets.UserObjectGroupUIViewSet) -router.register("zone", viewsets.ZoneUIViewSet) + +router.register("iprange", views.IPRangeUIViewSet) + urlpatterns = [ - path( - "docs/", - RedirectView.as_view(url=static("nautobot_firewall_models/docs/index.html")), - name="docs", - ), + path("docs/", RedirectView.as_view(url=static("nautobot_firewall_models/docs/index.html")), name="docs"), ] + urlpatterns += router.urls diff --git a/nautobot_firewall_models/views.py b/nautobot_firewall_models/views.py new file mode 100644 index 0000000..1a2b679 --- /dev/null +++ b/nautobot_firewall_models/views.py @@ -0,0 +1,19 @@ +"""Views for nautobot_firewall_models.""" + +from nautobot.apps.views import NautobotUIViewSet + +from nautobot_firewall_models import filters, forms, models, tables +from nautobot_firewall_models.api import serializers + + +class IPRangeUIViewSet(NautobotUIViewSet): + """ViewSet for IPRange views.""" + + bulk_update_form_class = forms.IPRangeBulkEditForm + filterset_class = filters.IPRangeFilterSet + filterset_form_class = forms.IPRangeFilterForm + form_class = forms.IPRangeForm + lookup_field = "pk" + queryset = models.IPRange.objects.all() + serializer_class = serializers.IPRangeSerializer + table_class = tables.IPRangeTable diff --git a/poetry.lock b/poetry.lock index 621a742..d7321b2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,25 +1,14 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. -[[package]] -name = "absl-py" -version = "2.1.0" -description = "Abseil Python Common Libraries, see https://github.com/abseil/abseil-py." -optional = false -python-versions = ">=3.7" -files = [ - {file = "absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff"}, - {file = "absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308"}, -] - [[package]] name = "amqp" -version = "5.2.0" +version = "5.3.1" description = "Low-level AMQP client for Python (fork of amqplib)." optional = false python-versions = ">=3.6" files = [ - {file = "amqp-5.2.0-py3-none-any.whl", hash = "sha256:827cb12fb0baa892aad844fd95258143bce4027fdac4fccddbc43330fd281637"}, - {file = "amqp-5.2.0.tar.gz", hash = "sha256:a1ecff425ad063ad42a486c902807d1482311481c8ad95a72694b2975e75f7fd"}, + {file = "amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2"}, + {file = "amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432"}, ] [package.dependencies] @@ -85,21 +74,18 @@ wrapt = [ [[package]] name = "asttokens" -version = "2.4.1" +version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, - {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, + {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, + {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, ] -[package.dependencies] -six = ">=1.12.0" - [package.extras] -astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] -test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] +astroid = ["astroid (>=2,<4)"] +test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "astunparse" @@ -118,30 +104,30 @@ wheel = ">=0.23.0,<1.0" [[package]] name = "async-timeout" -version = "4.0.3" +version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, ] [[package]] name = "attrs" -version = "24.2.0" +version = "24.3.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, + {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, + {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, ] [package.extras] benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] @@ -231,24 +217,6 @@ files = [ {file = "billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f"}, ] -[[package]] -name = "capirca" -version = "2.0.9" -description = "Capirca" -optional = false -python-versions = ">=3.6" -files = [ - {file = "capirca-2.0.9-py3-none-any.whl", hash = "sha256:cf333ac9315f0d61890710f412f6b113950095ff71580530887b48b267fe2a6f"}, - {file = "capirca-2.0.9.tar.gz", hash = "sha256:48e33e5d06f3a877ff49fe7f3bea94757f8e421d3e2112a7bdc2d02c257ac987"}, -] - -[package.dependencies] -absl-py = "*" -mock = "*" -ply = "*" -PyYAML = "*" -six = "*" - [[package]] name = "celery" version = "5.3.6" @@ -307,13 +275,13 @@ zstd = ["zstandard (==0.22.0)"] [[package]] name = "certifi" -version = "2024.8.30" +version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] @@ -397,127 +365,114 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.4.0" +version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.7" files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, + {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, + {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] [[package]] name = "click" -version = "8.1.7" +version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] @@ -700,38 +655,38 @@ dev = ["polib"] [[package]] name = "cryptography" -version = "43.0.1" +version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, - {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, - {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, - {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, - {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, - {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, - {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, + {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, + {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, + {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, + {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, + {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, + {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, + {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] [package.dependencies] @@ -744,7 +699,7 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] @@ -786,13 +741,13 @@ profile = ["gprof2dot (>=2022.7.29)"] [[package]] name = "django" -version = "4.2.16" +version = "4.2.17" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" files = [ - {file = "Django-4.2.16-py3-none-any.whl", hash = "sha256:1ddc333a16fc139fd253035a1606bb24261951bbc3a6ca256717fa06cc41a898"}, - {file = "Django-4.2.16.tar.gz", hash = "sha256:6f1616c2786c408ce86ab7e10f792b8f15742f7b7b7460243929cb371e7f1dad"}, + {file = "Django-4.2.17-py3-none-any.whl", hash = "sha256:3a93350214ba25f178d4045c0786c61573e7dbfa3c509b3551374f1e11ba8de0"}, + {file = "Django-4.2.17.tar.gz", hash = "sha256:6b56d834cc94c8b21a8f4e775064896be3b4a4ca387f2612d4406a5927cd2fdc"}, ] [package.dependencies] @@ -1091,6 +1046,23 @@ Django = ">=3.2" [package.extras] tablib = ["tablib"] +[[package]] +name = "django-tables2" +version = "2.7.5" +description = "Table/data-grid framework for Django" +optional = false +python-versions = ">=3.9" +files = [ + {file = "django_tables2-2.7.5-py3-none-any.whl", hash = "sha256:d9338937797207ffb6f481be2125c5ec3a0bb1858d409c672cc25fc5d654cb22"}, + {file = "django_tables2-2.7.5.tar.gz", hash = "sha256:fb5dcaa09379cf3947598ec7e1bd5f26ed63aafdee3b23963446763bbeac37bf"}, +] + +[package.dependencies] +django = ">=4.2" + +[package.extras] +tablib = ["tablib"] + [[package]] name = "django-taggit" version = "5.0.1" @@ -1211,13 +1183,13 @@ sidecar = ["drf-spectacular-sidecar"] [[package]] name = "drf-spectacular-sidecar" -version = "2024.7.1" +version = "2024.12.1" description = "Serve self-contained distribution builds of Swagger UI and Redoc with Django" optional = false python-versions = ">=3.6" files = [ - {file = "drf_spectacular_sidecar-2024.7.1-py3-none-any.whl", hash = "sha256:5dc8b38ad153e90b328152674c7959bf114bf86360a617a5a4516e135cb832bc"}, - {file = "drf_spectacular_sidecar-2024.7.1.tar.gz", hash = "sha256:beb992d6ece806a2d422ad626983e2472c0a5550de9647a7ed6764716a5abdfe"}, + {file = "drf_spectacular_sidecar-2024.12.1-py3-none-any.whl", hash = "sha256:e30821d150d29294f3be2018aab31b55cd724158e9e690b51a215264751aa8c7"}, + {file = "drf_spectacular_sidecar-2024.12.1.tar.gz", hash = "sha256:6be31df38bcf95681224b6550faa9344ee6dd5360dcf2b44afcc3f7460385613"}, ] [package.dependencies] @@ -1273,13 +1245,13 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "gitdb" -version = "4.0.11" +version = "4.0.12" description = "Git Object Database" optional = false python-versions = ">=3.7" files = [ - {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, - {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, + {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, + {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, ] [package.dependencies] @@ -1287,20 +1259,20 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.43" +version = "3.1.44" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ - {file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"}, - {file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"}, + {file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"}, + {file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"] +doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] [[package]] @@ -1575,32 +1547,32 @@ colors = ["colorama (>=0.4.6)"] [[package]] name = "jedi" -version = "0.19.1" +version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" files = [ - {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, - {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, ] [package.dependencies] -parso = ">=0.8.3,<0.9.0" +parso = ">=0.8.4,<0.9.0" [package.extras] docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] [[package]] name = "jinja2" -version = "3.1.4" +version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [package.dependencies] @@ -2012,36 +1984,20 @@ files = [ griffe = ">=0.49" mkdocstrings = ">=0.25" -[[package]] -name = "mock" -version = "5.1.0" -description = "Rolling backport of unittest.mock for all Pythons" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mock-5.1.0-py3-none-any.whl", hash = "sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744"}, - {file = "mock-5.1.0.tar.gz", hash = "sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d"}, -] - -[package.extras] -build = ["blurb", "twine", "wheel"] -docs = ["sphinx"] -test = ["pytest", "pytest-cov"] - [[package]] name = "nautobot" -version = "2.3.6" +version = "2.3.16" description = "Source of truth and network automation platform." optional = false python-versions = "<3.13,>=3.8" files = [ - {file = "nautobot-2.3.6-py3-none-any.whl", hash = "sha256:ff8cc2ab4964d1ea47e65607cdb0c7756a575b3521c5895e0aa5439b1b851f42"}, - {file = "nautobot-2.3.6.tar.gz", hash = "sha256:3a0aaf289b9b9a28084369122cd8f60f070e1f9857402fc85fde9830fa80d5bf"}, + {file = "nautobot-2.3.16-py3-none-any.whl", hash = "sha256:60a1043c97ca0c6575c01ee7b92d28da761843d449d6ad1f038ba2dafeefcaf3"}, + {file = "nautobot-2.3.16.tar.gz", hash = "sha256:92aed5dfbf457f52f47b96191103dd327981b0173bc8f813dc03a6c929cda45b"}, ] [package.dependencies] celery = ">=5.3.6,<5.4.0" -Django = ">=4.2.16,<4.3.0" +Django = ">=4.2.17,<4.3.0" django-ajax-tables = ">=1.1.1,<1.2.0" django-celery-beat = ">=2.6.0,<2.7.0" django-celery-results = ">=2.5.1,<2.6.0" @@ -2056,7 +2012,10 @@ django-prometheus = ">=2.3.1,<2.4.0" django-redis = ">=5.4.0,<5.5.0" django-silk = ">=5.1.0,<5.2.0" django-structlog = {version = ">=8.1.0,<9.0.0", extras = ["celery"]} -django-tables2 = ">=2.7.0,<2.8.0" +django-tables2 = [ + {version = "2.7.0", markers = "python_version < \"3.9\""}, + {version = ">=2.7.4,<2.8.0", markers = "python_version >= \"3.9\""}, +] django-taggit = ">=5.0.0,<5.1.0" django-timezone-field = ">=7.0,<7.1" django-tree-queries = ">=0.19.0,<0.20.0" @@ -2068,17 +2027,18 @@ emoji = ">=2.12.1,<2.13.0" GitPython = ">=3.1.43,<3.2.0" graphene-django = ">=2.16.0,<2.17.0" graphene-django-optimizer = ">=0.8.0,<0.9.0" -Jinja2 = ">=3.1.4,<3.2.0" +Jinja2 = ">=3.1.5,<3.2.0" jsonschema = ">=4.7.0,<5.0.0" +kombu = ">=5.4.2,<5.5.0" Markdown = ">=3.6,<3.7" MarkupSafe = ">=2.1.5,<2.2.0" netaddr = ">=1.3.0,<1.4.0" netutils = ">=1.6.0,<2.0.0" -nh3 = ">=0.2.15,<0.3.0" +nh3 = ">=0.2.20,<0.3.0" packaging = ">=23.1" Pillow = ">=10.3.0,<10.4.0" prometheus-client = ">=0.20.0,<0.21.0" -psycopg2-binary = ">=2.9.9,<2.10.0" +psycopg2-binary = ">=2.9.10,<2.10.0" python-slugify = ">=8.0.3,<8.1.0" pyuwsgi = ">=2.0.26,<2.1.0" PyYAML = ">=6.0.2,<6.1.0" @@ -2086,9 +2046,9 @@ social-auth-app-django = ">=5.4.2,<5.5.0" svgwrite = ">=1.4.2,<1.5.0" [package.extras] -all = ["django-auth-ldap (>=4.8.0,<4.9.0)", "django-storages (==1.14.3)", "mysqlclient (>=2.2.3,<2.3.0)", "napalm (>=4.1.0,<6.0.0)", "social-auth-core[saml] (>=4.5.3,<4.6.0)"] +all = ["django-auth-ldap (>=4.8.0,<4.9.0)", "django-storages (==1.14.3)", "mysqlclient (>=2.2.6,<2.3.0)", "napalm (>=4.1.0,<6.0.0)", "social-auth-core[saml] (>=4.5.3,<4.6.0)"] ldap = ["django-auth-ldap (>=4.8.0,<4.9.0)"] -mysql = ["mysqlclient (>=2.2.3,<2.3.0)"] +mysql = ["mysqlclient (>=2.2.6,<2.3.0)"] napalm = ["napalm (>=4.1.0,<6.0.0)"] remote-storage = ["django-storages (==1.14.3)"] sso = ["social-auth-core[saml] (>=4.5.3,<4.6.0)"] @@ -2109,13 +2069,13 @@ nicer-shell = ["ipython"] [[package]] name = "netutils" -version = "1.10.0" +version = "1.11.0" description = "Common helper functions useful in network automation." optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "netutils-1.10.0-py3-none-any.whl", hash = "sha256:19b8cc3d2cf567a986f916c90f298d241af03a71c62ec6d38d6dc3395347670b"}, - {file = "netutils-1.10.0.tar.gz", hash = "sha256:f457fb85cb622e89aa0403fb2128c50986f7ce38d93a5873981727d088619793"}, + {file = "netutils-1.11.0-py3-none-any.whl", hash = "sha256:863674eb7dce2b85972d52079b4884fb30e498ccf1dd581abc28b4d69bfdf0cd"}, + {file = "netutils-1.11.0.tar.gz", hash = "sha256:1631152256db1623675d9087d4327b2f4633d294f758518742a974e868a50ae8"}, ] [package.extras] @@ -2123,27 +2083,35 @@ optionals = ["jsonschema (>=4.17.3,<5.0.0)", "napalm (>=4.0.0,<5.0.0)"] [[package]] name = "nh3" -version = "0.2.18" -description = "Python bindings to the ammonia HTML sanitization library." -optional = false -python-versions = "*" -files = [ - {file = "nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86"}, - {file = "nh3-0.2.18-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811"}, - {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200"}, - {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164"}, - {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189"}, - {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad"}, - {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b"}, - {file = "nh3-0.2.18-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307"}, - {file = "nh3-0.2.18-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f"}, - {file = "nh3-0.2.18-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe"}, - {file = "nh3-0.2.18-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a"}, - {file = "nh3-0.2.18-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50"}, - {file = "nh3-0.2.18-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204"}, - {file = "nh3-0.2.18-cp37-abi3-win32.whl", hash = "sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be"}, - {file = "nh3-0.2.18-cp37-abi3-win_amd64.whl", hash = "sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844"}, - {file = "nh3-0.2.18.tar.gz", hash = "sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4"}, +version = "0.2.20" +description = "Python binding to Ammonia HTML sanitizer Rust crate" +optional = false +python-versions = ">=3.8" +files = [ + {file = "nh3-0.2.20-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e1061a4ab6681f6bdf72b110eea0c4e1379d57c9de937db3be4202f7ad6043db"}, + {file = "nh3-0.2.20-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb4254b1dac4a1ee49919a5b3f1caf9803ea8dada1816d9e8289e63d3cd0dd9a"}, + {file = "nh3-0.2.20-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ae9cbd713524cdb81e64663d0d6aae26f678db9f2cd9db0bf162606f1f9f20c"}, + {file = "nh3-0.2.20-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e1f7370b4e14cc03f5ae141ef30a1caf81fa5787711f80be9081418dd9eb79d2"}, + {file = "nh3-0.2.20-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:ac4d27dc836a476efffc6eb661994426b8b805c951b29c9cf2ff36bc9ad58bc5"}, + {file = "nh3-0.2.20-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4fd2e9248725ebcedac3997a8d3da0d90a12a28c9179c6ba51f1658938ac30d0"}, + {file = "nh3-0.2.20-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f7d564871833ddbe54df3aa59053b1110729d3a800cb7628ae8f42adb3d75208"}, + {file = "nh3-0.2.20-cp313-cp313t-win32.whl", hash = "sha256:d2a176fd4306b6f0f178a3f67fac91bd97a3a8d8fafb771c9b9ef675ba5c8886"}, + {file = "nh3-0.2.20-cp313-cp313t-win_amd64.whl", hash = "sha256:6ed834c68452a600f517dd3e1534dbfaff1f67f98899fecf139a055a25d99150"}, + {file = "nh3-0.2.20-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:76e2f603b30c02ff6456b233a83fc377dedab6a50947b04e960a6b905637b776"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:181063c581defe683bd4bb78188ac9936d208aebbc74c7f7c16b6a32ae2ebb38"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:231addb7643c952cd6d71f1c8702d703f8fe34afcb20becb3efb319a501a12d7"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1b9a8340a0aab991c68a5ca938d35ef4a8a3f4bf1b455da8855a40bee1fa0ace"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10317cd96fe4bbd4eb6b95f3920b71c902157ad44fed103fdcde43e3b8ee8be6"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8698db4c04b140800d1a1cd3067fda399e36e1e2b8fc1fe04292a907350a3e9b"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eb04b9c3deb13c3a375ea39fd4a3c00d1f92e8fb2349f25f1e3e4506751774b"}, + {file = "nh3-0.2.20-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92f3f1c4f47a2c6f3ca7317b1d5ced05bd29556a75d3a4e2715652ae9d15c05d"}, + {file = "nh3-0.2.20-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ddefa9fd6794a87e37d05827d299d4b53a3ec6f23258101907b96029bfef138a"}, + {file = "nh3-0.2.20-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ce3731c8f217685d33d9268362e5b4f770914e922bba94d368ab244a59a6c397"}, + {file = "nh3-0.2.20-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:09f037c02fc2c43b211ff1523de32801dcfb0918648d8e651c36ef890f1731ec"}, + {file = "nh3-0.2.20-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:813f1c8012dd64c990514b795508abb90789334f76a561fa0fd4ca32d2275330"}, + {file = "nh3-0.2.20-cp38-abi3-win32.whl", hash = "sha256:47b2946c0e13057855209daeffb45dc910bd0c55daf10190bb0b4b60e2999784"}, + {file = "nh3-0.2.20-cp38-abi3-win_amd64.whl", hash = "sha256:da87573f03084edae8eb87cfe811ec338606288f81d333c07d2a9a0b9b976c0b"}, + {file = "nh3-0.2.20.tar.gz", hash = "sha256:9705c42d7ff88a0bea546c82d7fe5e59135e3d3f057e485394f491248a1f8ed5"}, ] [[package]] @@ -2164,13 +2132,13 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "packaging" -version = "24.1" +version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] @@ -2352,17 +2320,6 @@ docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-a test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.11.2)"] -[[package]] -name = "ply" -version = "3.11" -description = "Python Lex & Yacc" -optional = false -python-versions = "*" -files = [ - {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, - {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, -] - [[package]] name = "prometheus-client" version = "0.20.0" @@ -2409,83 +2366,79 @@ wcwidth = "*" [[package]] name = "psycopg2-binary" -version = "2.9.9" +version = "2.9.10" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, + {file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-win32.whl", hash = "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5"}, ] [[package]] @@ -2537,13 +2490,13 @@ files = [ [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] @@ -2646,13 +2599,13 @@ pylint = ">=1.7" [[package]] name = "pymdown-extensions" -version = "10.11.2" +version = "10.14" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.11.2-py3-none-any.whl", hash = "sha256:41cdde0a77290e480cf53892f5c5e50921a7ee3e5cd60ba91bf19837b33badcf"}, - {file = "pymdown_extensions-10.11.2.tar.gz", hash = "sha256:bc8847ecc9e784a098efd35e20cba772bc5a1b529dfcef9dc1972db9021a1049"}, + {file = "pymdown_extensions-10.14-py3-none-any.whl", hash = "sha256:202481f716cc8250e4be8fce997781ebf7917701b59652458ee47f2401f818b5"}, + {file = "pymdown_extensions-10.14.tar.gz", hash = "sha256:741bd7c4ff961ba40b7528d32284c53bc436b8b1645e8e37c3e57770b8700a34"}, ] [package.dependencies] @@ -2660,7 +2613,7 @@ markdown = ">=3.6" pyyaml = "*" [package.extras] -extra = ["pygments (>=2.12)"] +extra = ["pygments (>=2.19.1)"] [[package]] name = "python-crontab" @@ -2756,58 +2709,66 @@ files = [ [[package]] name = "pyuwsgi" -version = "2.0.26" +version = "2.0.28.post1" description = "The uWSGI server" optional = false python-versions = "*" files = [ - {file = "pyuwsgi-2.0.26-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fe6149521f6545548452ae66d670be7ece962045952d07af7fdd156409771d4"}, - {file = "pyuwsgi-2.0.26-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ce17f4a114e0ca53686748e7a4556e62c7a0edc8a6033e076eb3bc4db5489f"}, - {file = "pyuwsgi-2.0.26-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:244c81e050e240ebc8a1c455db8aefc6b5c9f4582551b34905092da0e03415a3"}, - {file = "pyuwsgi-2.0.26-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596c55b36b72fbb04b6e024578e8f9867185b6b07f50fcead75e71b6534154e7"}, - {file = "pyuwsgi-2.0.26-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a9e82434e640eb5c7ce0845c3cfd6711088cff3b0265d98e6a34216972eec07"}, - {file = "pyuwsgi-2.0.26-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:df91af8b7dfd573683ccecfa646d447e4a23be7dc84329c0633e70faa9431ba8"}, - {file = "pyuwsgi-2.0.26-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ffa63939802f29873a8b92e437fd9e17fccde0a80004260a2abe8ad71b42bd1d"}, - {file = "pyuwsgi-2.0.26-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:910642f91877e8dac6d0286c79688543d0d573e43e78d030d998faee8ca49bdb"}, - {file = "pyuwsgi-2.0.26-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b20664df152f225d56f6ba810347d47efa126d9ce751e6e5fbad07a12d7cdae2"}, - {file = "pyuwsgi-2.0.26-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:35aeca4b60e883796f0c2fa4b78fa34e5f31947b317b7d63526aa68a31036467"}, - {file = "pyuwsgi-2.0.26-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46f95c39b8713121536e5f0c4292eab30adcb8dabd088641fd34b5641d4fd81d"}, - {file = "pyuwsgi-2.0.26-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85f8a3b09b41177496e5ed84aa9e0e3f815ac53422d37f72eec7933609f742e9"}, - {file = "pyuwsgi-2.0.26-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9b9bed61cf18002ed7e9cef81c42e6b825c01b7c8d983f2cff223f905987e64"}, - {file = "pyuwsgi-2.0.26-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0bc7cb23a35b7da5ead97b2e9ea2894a74c96d9864782789526e66fdfcf5f91"}, - {file = "pyuwsgi-2.0.26-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb69885e3f02a7538137197a8618766cda7e29bd7da005fce699e6385215a786"}, - {file = "pyuwsgi-2.0.26-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c7bb17f907f7e165d45b676939c9641dbf4d7c8c532caaa704006c6be69c3b60"}, - {file = "pyuwsgi-2.0.26-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:01aac38118cd35adb141a7ddbd721845c3b895a18d6cfddca3a237e0da5e5fc7"}, - {file = "pyuwsgi-2.0.26-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ead0863e90397be562ca9816b2e0704f8e59e734ab158eb467db333814af704e"}, - {file = "pyuwsgi-2.0.26-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf4e2828ca8c213f91673ff960a29ebbc037d743e57b2758ea5a76dd13c0b01c"}, - {file = "pyuwsgi-2.0.26-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d48accc82c82d637be27ebb153c17a2773758df582602d7f8c1702e9dcf8ea3a"}, - {file = "pyuwsgi-2.0.26-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c87bd20906bceebf0bfb3fc4f25b12ca3943aafeaf0dc289df9144d4ee41f9c5"}, - {file = "pyuwsgi-2.0.26-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5f14b676f8aacd79a2106695657c212b4469cd98f1624dc0473e1e0e695bcc9"}, - {file = "pyuwsgi-2.0.26-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:70aec45ba640742df86e0f1a0baa01964b162d11f24ee70d56146bd6331462f9"}, - {file = "pyuwsgi-2.0.26-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c8ded279e1c4da7273f25d658979071520ae97712650f70ea50715618cb51910"}, - {file = "pyuwsgi-2.0.26-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:288f34589e7d76e70f4ebaccfeb34b76cefa661f41302b38722f305a22310e1f"}, - {file = "pyuwsgi-2.0.26-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:524c5620aff07c72f9ef6526712f92a06ff9741c0c3ea7b46284045de1b8db40"}, - {file = "pyuwsgi-2.0.26-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9f5f0c377d9efc04d12937e79567628c4c1f72c0991f45c6dbb76541ca1b683"}, - {file = "pyuwsgi-2.0.26-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db26c9ca688fb19e1716c7ab8e09811a9bd431576432ca626d89e9ebbef96fc1"}, - {file = "pyuwsgi-2.0.26-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:489ff6e77fcbe14ca7a853916388ddb4bd4e087dd243abef0b7a4732563401ba"}, - {file = "pyuwsgi-2.0.26-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3d07829faf43b5981487130a929eb8521aefdf39dd723c50e2e168362dafafdc"}, - {file = "pyuwsgi-2.0.26-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d9e21e147f851a77ab893a27466a166a52450558c88ce885974c2e63f6e3c298"}, - {file = "pyuwsgi-2.0.26-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7cfa8b9524bccca9052c6fd2682ec2fc744a9397eaf5febde8e60334fac4313a"}, - {file = "pyuwsgi-2.0.26-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a13acc603cca50510dd2b0772f398bad1bcfaa5c418069becba016edac22ac4"}, - {file = "pyuwsgi-2.0.26-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:544e2f791912dc5f38cb1159eae8ba037cdd472b40e2fcfc0ea7fb973aaebaaa"}, - {file = "pyuwsgi-2.0.26-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c807ae36058dd7ae2653451081536c10886c74fc06aa1bf7a28cc2f0c815307"}, - {file = "pyuwsgi-2.0.26-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:af04d0ff93a89c65369c8b0b10d07394046e7d78694fad998b316aa7f8ca3e1d"}, - {file = "pyuwsgi-2.0.26-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:fb1ba584cd07339f2adad5eb33ab51854330748426a63fbd58cb39031ffb7498"}, - {file = "pyuwsgi-2.0.26-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:709d3704dcb9e85576a31a1c04782fe17df6a577a6eaf07dd2b7c34982ca905f"}, - {file = "pyuwsgi-2.0.26-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a81548e59852276af18d4404512cfcbaf20c40f38f345bb80b1a87f1dc7285c"}, - {file = "pyuwsgi-2.0.26-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bb840399ff19b3e2308e33b2cd37143bc55b1666e99b38370660b95127b081aa"}, - {file = "pyuwsgi-2.0.26-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115cf76fe6358a7863e4f28d9dfd2fa51f1f198a7259ee5fec2e7cb52d0060bf"}, - {file = "pyuwsgi-2.0.26-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fd517e4336ebae0493063a031da0e6a53ff436dc5cd551b57f387d38459085c"}, - {file = "pyuwsgi-2.0.26-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14769dd0c41bc531dba7fc8fe202a11d984d93f251896ffcc74daa0476eda966"}, - {file = "pyuwsgi-2.0.26-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:312575bcc9513dfd7d76d993b7753d18b9a36e54cffca6b72ad2e1397636c7fd"}, - {file = "pyuwsgi-2.0.26-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3d839d584276784f660168b89209c28ce9a1cb14f8911322f21d919c2566e49d"}, - {file = "pyuwsgi-2.0.26-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ea8e3e197f54902b34c5648880bc3c111ad4f677a7350203d1052606cc04119b"}, - {file = "pyuwsgi-2.0.26.tar.gz", hash = "sha256:c7f167545939764a1c6fcd0f861023f641ca09f9806f1f4b7e48b9ea2682db8e"}, + {file = "pyuwsgi-2.0.28.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:611e6585a51b3a1f9619e1069dcdc1b8bf37ad7aa16b271fce2ca3e1440fc548"}, + {file = "pyuwsgi-2.0.28.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c2caedcc6fd0cd217b65ab863a51e18032b3ce81316d0a079652ed43ed8ba68"}, + {file = "pyuwsgi-2.0.28.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9382286b9cf94b5d826388cda7097235b0f1348c7549c8b71100ecfc8d74c58"}, + {file = "pyuwsgi-2.0.28.post1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30798db4c0c9b36a12fd831cfe621c69569e226d177b3c28c6a191e2a819604f"}, + {file = "pyuwsgi-2.0.28.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a4fbc27ccbe24cffb65ce89fbb7cf2e8a0af625b7706179786a810688cefd7f"}, + {file = "pyuwsgi-2.0.28.post1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1289847ba14ae2ba4f918c57e9d257ecd82a43f6c7a026e155577596c6304f1"}, + {file = "pyuwsgi-2.0.28.post1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:91bdc9fcd1e13088ef57f7e58e427c9539e9d2c6d75157f55b5d17ef599e61ee"}, + {file = "pyuwsgi-2.0.28.post1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a1b0a27f1b9c63a1ac9788a068e5905b8ebedb1b460b9256ac85d1318fd6a9a6"}, + {file = "pyuwsgi-2.0.28.post1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:36d9c628067fc1c58534b800aacbb7499813b214cabe9128fb5ba79ad32ff9b9"}, + {file = "pyuwsgi-2.0.28.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:219032fe8bb8306cb05e7bc509ff134c853cfcbd7d809a867d5b8ecd589bef5d"}, + {file = "pyuwsgi-2.0.28.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1573ac212201ccbfea00b93d88fde89205d3c2f0a4d0c973058aa0b8745d4a2d"}, + {file = "pyuwsgi-2.0.28.post1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6105bd14fa2e61505dc116574ba79f3f6e0f425f206d3bec2337463457167aba"}, + {file = "pyuwsgi-2.0.28.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44bfebc678b2dde10382197b9199ce546b699a672e05e139a3827efb121e704f"}, + {file = "pyuwsgi-2.0.28.post1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e4e4775246f2ab079ea4bcf69d70441ffe81eba82a88eb4da6ae9debf334511f"}, + {file = "pyuwsgi-2.0.28.post1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e227836984735fdfa26d3be4927a6ae060ad8a0d28ef4a6adcd4f47fbfd7e876"}, + {file = "pyuwsgi-2.0.28.post1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf4f03d8b74d9754efd8cc6b0566258ccc1123a8c2fe49a11835242a6fb27efc"}, + {file = "pyuwsgi-2.0.28.post1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9390e8a2297186ede814d5863a06fa98b91295c813fdd3d08fe1357793476486"}, + {file = "pyuwsgi-2.0.28.post1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06a0294910de374ff43032b41333f7f9b62d59308b7f3eea29bc64d78fb93613"}, + {file = "pyuwsgi-2.0.28.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64b40503a87621e79efe4306b77595a0cbcb69afa4f3428a85e4e8ac46068d88"}, + {file = "pyuwsgi-2.0.28.post1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1157f49bfb360c1883feec82553adbed1e4a447e5ced66f36525a92f0e46397e"}, + {file = "pyuwsgi-2.0.28.post1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941d44a67760dc173c0f8318f8b7bd1ef7927533d6efb4641b2ea9e934f09981"}, + {file = "pyuwsgi-2.0.28.post1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a90b72a25ba1a34299f4958237a12f18154b373a9a0a93a6267af5e8798c1ee"}, + {file = "pyuwsgi-2.0.28.post1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2c29fdf5baaff9d717aedb63e7b78b90bec561afc099f952db44699adcb0c575"}, + {file = "pyuwsgi-2.0.28.post1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e8fd6f2116d2afce52965c940a96a939e9d9c7409f3e19ba445a25e33779f08"}, + {file = "pyuwsgi-2.0.28.post1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd67086df87921bc8837ef018cd7cf02834136f4735811375c1e17b776b40"}, + {file = "pyuwsgi-2.0.28.post1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f27ee3445eab37fabbe3afc6af68220c8f0bd28c5228d6a2ec7886d080614ad4"}, + {file = "pyuwsgi-2.0.28.post1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdd7ec17f795049ef346dc10efbfce6c1a9500f3f39262c86107c70b9a83cb9"}, + {file = "pyuwsgi-2.0.28.post1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4563fa76d64a2c309eb902511a1fcdce66865a03785c61494db5c53136ffa7a"}, + {file = "pyuwsgi-2.0.28.post1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32838668eab84ac6847876d9da72768552fca556e09a1fe3a63facb976bd12ad"}, + {file = "pyuwsgi-2.0.28.post1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9feafdfa73a632767a0cb1bafd285e36fba925b9c9ddd2b3311f2963c917c9f"}, + {file = "pyuwsgi-2.0.28.post1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:20d23ddbf28a831543d586c9e5727c9c28d7be7bd7cb853c7db2e0c529f605bf"}, + {file = "pyuwsgi-2.0.28.post1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c0b1b09bdf836a6cacf35e2bb06f6ad4b7dff6c6ad48895e1b1c0b0d19bbbfcc"}, + {file = "pyuwsgi-2.0.28.post1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:735861b77e607e133f9905a5e0ba6505ef6788df1cfdb6af06e6dfef07dfa9af"}, + {file = "pyuwsgi-2.0.28.post1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dbe7d0bb8184ef6c9bec1a3593a02bd27de0348807c97533069a7bca2603d19"}, + {file = "pyuwsgi-2.0.28.post1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4fcf8a76ad40af8c3c1a1e98f11be03e999e4957f48da6a180f5b8fc40b963c4"}, + {file = "pyuwsgi-2.0.28.post1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c969668db4599b6a5c927ad4bc1d698ea8c57fb10a943b51402fe80a97cdfc9"}, + {file = "pyuwsgi-2.0.28.post1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:5b6bec34ca5b200dfc8b10c74b0a9e3da051747f5faa300f681bbb46cf573fd0"}, + {file = "pyuwsgi-2.0.28.post1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:8dc521dd980ee431d4e6fc9a018ad1df4840c4551f281a598878083f3d155243"}, + {file = "pyuwsgi-2.0.28.post1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:1e6afc8916098ca35119dc0c619f29ef572f72c6f4425f29604c17397ae313b2"}, + {file = "pyuwsgi-2.0.28.post1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b2f118d7211e4afb7e8248cd380c1eac3e87604c1143bbccac1679c3a3642e22"}, + {file = "pyuwsgi-2.0.28.post1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:327800fa58bfba5d6f2bd19331e812e0e1250aaa681874f5b92998f68e6bab75"}, + {file = "pyuwsgi-2.0.28.post1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b351e76c9a18dc3e8a635994a98571b9ca7f10e371a0085ae3c8cd95f3db66cd"}, + {file = "pyuwsgi-2.0.28.post1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29c6f658625f2470d905276987aec898a690eee33f49c9c3961ca9d912abd046"}, + {file = "pyuwsgi-2.0.28.post1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9436ac4450730e1bcca4591ebb781eabcfcf93462114e199b951118032144323"}, + {file = "pyuwsgi-2.0.28.post1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:0b1c131288b6b473e39c5b02e63c34b64422665749dcadee41ef98d64b5db5d0"}, + {file = "pyuwsgi-2.0.28.post1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a8a932c9d34b2f0007eabf04728ffae7be8bfb87a044daee50a82b6796ad5b34"}, + {file = "pyuwsgi-2.0.28.post1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f55071012ce5d728ac428be78c3710e986acc02dcfc1d72f3a76541b85e25cfb"}, + {file = "pyuwsgi-2.0.28.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fa60c4fb1e483c079d1f3767863bc41884f6322f0744ac5d6398b0e505e9d21e"}, + {file = "pyuwsgi-2.0.28.post1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad20990917364f8f9b3fc62424edc6d9a992146bc5e15d37b1bbc9c3934ddf9b"}, + {file = "pyuwsgi-2.0.28.post1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:167d05f635c46287ee8709b09f422fbc16310acad3c9bd100a267679c061fe62"}, + {file = "pyuwsgi-2.0.28.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2300dba7037089a23b0eb4d2b037391be4d89b73e597671d0f23903a529cd552"}, + {file = "pyuwsgi-2.0.28.post1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f61adc1ab15da41675af36256feccd0cafb797c703499930fd5b3b381b0b6273"}, + {file = "pyuwsgi-2.0.28.post1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dc85ba45af7a7b89e3a722cf9cbba724fabb2c3b5caf3c25d67286ab329df97c"}, + {file = "pyuwsgi-2.0.28.post1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:81e59789af3b568ed1c4484b4bac9e4b79810f4774d8d3ab9056eb3f500e1e94"}, + {file = "pyuwsgi-2.0.28.post1.tar.gz", hash = "sha256:3b85217fd489d623512066ffed0cfc4c95bd3321655e9c6ea13cf6c7f064c9b0"}, ] [[package]] @@ -2888,13 +2849,13 @@ pyyaml = "*" [[package]] name = "redis" -version = "5.1.1" +version = "5.2.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" files = [ - {file = "redis-5.1.1-py3-none-any.whl", hash = "sha256:f8ea06b7482a668c6475ae202ed8d9bcaa409f6e87fb77ed1043d912afd62e24"}, - {file = "redis-5.1.1.tar.gz", hash = "sha256:f6c997521fedbae53387307c5d0bf784d9acc28d9f1d058abeac566ec4dbed72"}, + {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, + {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, ] [package.dependencies] @@ -2921,105 +2882,105 @@ rpds-py = ">=0.7.0" [[package]] name = "regex" -version = "2024.9.11" +version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" files = [ - {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"}, - {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"}, - {file = "regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a"}, - {file = "regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0"}, - {file = "regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1"}, - {file = "regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9"}, - {file = "regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"}, - {file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"}, - {file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8"}, - {file = "regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8"}, - {file = "regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd"}, - {file = "regex-2024.9.11-cp38-cp38-win32.whl", hash = "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771"}, - {file = "regex-2024.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35"}, - {file = "regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142"}, - {file = "regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919"}, - {file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, + {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, + {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, + {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, + {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, + {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, + {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, + {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, + {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, + {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, + {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, + {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, + {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, + {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, ] [[package]] @@ -3063,114 +3024,114 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"] [[package]] name = "rpds-py" -version = "0.20.0" +version = "0.20.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, - {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"}, - {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"}, - {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"}, - {file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"}, - {file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"}, - {file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"}, - {file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"}, - {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"}, - {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"}, - {file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"}, - {file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"}, - {file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"}, - {file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"}, - {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"}, - {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"}, - {file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"}, - {file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"}, - {file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"}, - {file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"}, - {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"}, - {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"}, - {file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"}, - {file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"}, - {file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"}, - {file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"}, - {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"}, - {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"}, - {file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"}, - {file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"}, - {file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"}, - {file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"}, - {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"}, - {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"}, - {file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"}, - {file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"}, - {file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"}, - {file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"}, - {file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"}, + {file = "rpds_py-0.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad"}, + {file = "rpds_py-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14511a539afee6f9ab492b543060c7491c99924314977a55c98bfa2ee29ce78c"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ccb8ac2d3c71cda472b75af42818981bdacf48d2e21c36331b50b4f16930163"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c142b88039b92e7e0cb2552e8967077e3179b22359e945574f5e2764c3953dcf"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f19169781dddae7478a32301b499b2858bc52fc45a112955e798ee307e294977"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13c56de6518e14b9bf6edde23c4c39dac5b48dcf04160ea7bce8fca8397cdf86"}, + {file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:925d176a549f4832c6f69fa6026071294ab5910e82a0fe6c6228fce17b0706bd"}, + {file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:78f0b6877bfce7a3d1ff150391354a410c55d3cdce386f862926a4958ad5ab7e"}, + {file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3dd645e2b0dcb0fd05bf58e2e54c13875847687d0b71941ad2e757e5d89d4356"}, + {file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4f676e21db2f8c72ff0936f895271e7a700aa1f8d31b40e4e43442ba94973899"}, + {file = "rpds_py-0.20.1-cp310-none-win32.whl", hash = "sha256:648386ddd1e19b4a6abab69139b002bc49ebf065b596119f8f37c38e9ecee8ff"}, + {file = "rpds_py-0.20.1-cp310-none-win_amd64.whl", hash = "sha256:d9ecb51120de61e4604650666d1f2b68444d46ae18fd492245a08f53ad2b7711"}, + {file = "rpds_py-0.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:762703bdd2b30983c1d9e62b4c88664df4a8a4d5ec0e9253b0231171f18f6d75"}, + {file = "rpds_py-0.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0b581f47257a9fce535c4567782a8976002d6b8afa2c39ff616edf87cbeff712"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:842c19a6ce894493563c3bd00d81d5100e8e57d70209e84d5491940fdb8b9e3a"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42cbde7789f5c0bcd6816cb29808e36c01b960fb5d29f11e052215aa85497c93"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c8e9340ce5a52f95fa7d3b552b35c7e8f3874d74a03a8a69279fd5fca5dc751"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ba6f89cac95c0900d932c9efb7f0fb6ca47f6687feec41abcb1bd5e2bd45535"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a916087371afd9648e1962e67403c53f9c49ca47b9680adbeef79da3a7811b0"}, + {file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:200a23239781f46149e6a415f1e870c5ef1e712939fe8fa63035cd053ac2638e"}, + {file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:58b1d5dd591973d426cbb2da5e27ba0339209832b2f3315928c9790e13f159e8"}, + {file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6b73c67850ca7cae0f6c56f71e356d7e9fa25958d3e18a64927c2d930859b8e4"}, + {file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d8761c3c891cc51e90bc9926d6d2f59b27beaf86c74622c8979380a29cc23ac3"}, + {file = "rpds_py-0.20.1-cp311-none-win32.whl", hash = "sha256:cd945871335a639275eee904caef90041568ce3b42f402c6959b460d25ae8732"}, + {file = "rpds_py-0.20.1-cp311-none-win_amd64.whl", hash = "sha256:7e21b7031e17c6b0e445f42ccc77f79a97e2687023c5746bfb7a9e45e0921b84"}, + {file = "rpds_py-0.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:36785be22066966a27348444b40389f8444671630063edfb1a2eb04318721e17"}, + {file = "rpds_py-0.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:142c0a5124d9bd0e2976089484af5c74f47bd3298f2ed651ef54ea728d2ea42c"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbddc10776ca7ebf2a299c41a4dde8ea0d8e3547bfd731cb87af2e8f5bf8962d"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15a842bb369e00295392e7ce192de9dcbf136954614124a667f9f9f17d6a216f"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be5ef2f1fc586a7372bfc355986226484e06d1dc4f9402539872c8bb99e34b01"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbcf360c9e3399b056a238523146ea77eeb2a596ce263b8814c900263e46031a"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecd27a66740ffd621d20b9a2f2b5ee4129a56e27bfb9458a3bcc2e45794c96cb"}, + {file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0b937b2a1988f184a3e9e577adaa8aede21ec0b38320d6009e02bd026db04fa"}, + {file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6889469bfdc1eddf489729b471303739bf04555bb151fe8875931f8564309afc"}, + {file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:19b73643c802f4eaf13d97f7855d0fb527fbc92ab7013c4ad0e13a6ae0ed23bd"}, + {file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3c6afcf2338e7f374e8edc765c79fbcb4061d02b15dd5f8f314a4af2bdc7feb5"}, + {file = "rpds_py-0.20.1-cp312-none-win32.whl", hash = "sha256:dc73505153798c6f74854aba69cc75953888cf9866465196889c7cdd351e720c"}, + {file = "rpds_py-0.20.1-cp312-none-win_amd64.whl", hash = "sha256:8bbe951244a838a51289ee53a6bae3a07f26d4e179b96fc7ddd3301caf0518eb"}, + {file = "rpds_py-0.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6ca91093a4a8da4afae7fe6a222c3b53ee4eef433ebfee4d54978a103435159e"}, + {file = "rpds_py-0.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b9c2fe36d1f758b28121bef29ed1dee9b7a2453e997528e7d1ac99b94892527c"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f009c69bc8c53db5dfab72ac760895dc1f2bc1b62ab7408b253c8d1ec52459fc"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6740a3e8d43a32629bb9b009017ea5b9e713b7210ba48ac8d4cb6d99d86c8ee8"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32b922e13d4c0080d03e7b62991ad7f5007d9cd74e239c4b16bc85ae8b70252d"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe00a9057d100e69b4ae4a094203a708d65b0f345ed546fdef86498bf5390982"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49fe9b04b6fa685bd39237d45fad89ba19e9163a1ccaa16611a812e682913496"}, + {file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa7ac11e294304e615b43f8c441fee5d40094275ed7311f3420d805fde9b07b4"}, + {file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aa97af1558a9bef4025f8f5d8c60d712e0a3b13a2fe875511defc6ee77a1ab7"}, + {file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:483b29f6f7ffa6af845107d4efe2e3fa8fb2693de8657bc1849f674296ff6a5a"}, + {file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37fe0f12aebb6a0e3e17bb4cd356b1286d2d18d2e93b2d39fe647138458b4bcb"}, + {file = "rpds_py-0.20.1-cp313-none-win32.whl", hash = "sha256:a624cc00ef2158e04188df5e3016385b9353638139a06fb77057b3498f794782"}, + {file = "rpds_py-0.20.1-cp313-none-win_amd64.whl", hash = "sha256:b71b8666eeea69d6363248822078c075bac6ed135faa9216aa85f295ff009b1e"}, + {file = "rpds_py-0.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5b48e790e0355865197ad0aca8cde3d8ede347831e1959e158369eb3493d2191"}, + {file = "rpds_py-0.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3e310838a5801795207c66c73ea903deda321e6146d6f282e85fa7e3e4854804"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249280b870e6a42c0d972339e9cc22ee98730a99cd7f2f727549af80dd5a963"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e79059d67bea28b53d255c1437b25391653263f0e69cd7dec170d778fdbca95e"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b431c777c9653e569986ecf69ff4a5dba281cded16043d348bf9ba505486f36"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da584ff96ec95e97925174eb8237e32f626e7a1a97888cdd27ee2f1f24dd0ad8"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a0629ec053fc013808a85178524e3cb63a61dbc35b22499870194a63578fb9"}, + {file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fbf15aff64a163db29a91ed0868af181d6f68ec1a3a7d5afcfe4501252840bad"}, + {file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:07924c1b938798797d60c6308fa8ad3b3f0201802f82e4a2c41bb3fafb44cc28"}, + {file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4a5a844f68776a7715ecb30843b453f07ac89bad393431efbf7accca3ef599c1"}, + {file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:518d2ca43c358929bf08f9079b617f1c2ca6e8848f83c1225c88caeac46e6cbc"}, + {file = "rpds_py-0.20.1-cp38-none-win32.whl", hash = "sha256:3aea7eed3e55119635a74bbeb80b35e776bafccb70d97e8ff838816c124539f1"}, + {file = "rpds_py-0.20.1-cp38-none-win_amd64.whl", hash = "sha256:7dca7081e9a0c3b6490a145593f6fe3173a94197f2cb9891183ef75e9d64c425"}, + {file = "rpds_py-0.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b41b6321805c472f66990c2849e152aff7bc359eb92f781e3f606609eac877ad"}, + {file = "rpds_py-0.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a90c373ea2975519b58dece25853dbcb9779b05cc46b4819cb1917e3b3215b6"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16d4477bcb9fbbd7b5b0e4a5d9b493e42026c0bf1f06f723a9353f5153e75d30"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84b8382a90539910b53a6307f7c35697bc7e6ffb25d9c1d4e998a13e842a5e83"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4888e117dd41b9d34194d9e31631af70d3d526efc363085e3089ab1a62c32ed1"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5265505b3d61a0f56618c9b941dc54dc334dc6e660f1592d112cd103d914a6db"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e75ba609dba23f2c95b776efb9dd3f0b78a76a151e96f96cc5b6b1b0004de66f"}, + {file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1791ff70bc975b098fe6ecf04356a10e9e2bd7dc21fa7351c1742fdeb9b4966f"}, + {file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d126b52e4a473d40232ec2052a8b232270ed1f8c9571aaf33f73a14cc298c24f"}, + {file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c14937af98c4cc362a1d4374806204dd51b1e12dded1ae30645c298e5a5c4cb1"}, + {file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3d089d0b88996df627693639d123c8158cff41c0651f646cd8fd292c7da90eaf"}, + {file = "rpds_py-0.20.1-cp39-none-win32.whl", hash = "sha256:653647b8838cf83b2e7e6a0364f49af96deec64d2a6578324db58380cff82aca"}, + {file = "rpds_py-0.20.1-cp39-none-win_amd64.whl", hash = "sha256:fa41a64ac5b08b292906e248549ab48b69c5428f3987b09689ab2441f267d04d"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a07ced2b22f0cf0b55a6a510078174c31b6d8544f3bc00c2bcee52b3d613f74"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:68cb0a499f2c4a088fd2f521453e22ed3527154136a855c62e148b7883b99f9a"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa3060d885657abc549b2a0f8e1b79699290e5d83845141717c6c90c2df38311"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95f3b65d2392e1c5cec27cff08fdc0080270d5a1a4b2ea1d51d5f4a2620ff08d"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2cc3712a4b0b76a1d45a9302dd2f53ff339614b1c29603a911318f2357b04dd2"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d4eea0761e37485c9b81400437adb11c40e13ef513375bbd6973e34100aeb06"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f5179583d7a6cdb981151dd349786cbc318bab54963a192692d945dd3f6435d"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fbb0ffc754490aff6dabbf28064be47f0f9ca0b9755976f945214965b3ace7e"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:a94e52537a0e0a85429eda9e49f272ada715506d3b2431f64b8a3e34eb5f3e75"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:92b68b79c0da2a980b1c4197e56ac3dd0c8a149b4603747c4378914a68706979"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:93da1d3db08a827eda74356f9f58884adb254e59b6664f64cc04cdff2cc19b0d"}, + {file = "rpds_py-0.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:754bbed1a4ca48479e9d4182a561d001bbf81543876cdded6f695ec3d465846b"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ca449520e7484534a2a44faf629362cae62b660601432d04c482283c47eaebab"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9c4cb04a16b0f199a8c9bf807269b2f63b7b5b11425e4a6bd44bd6961d28282c"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63804105143c7e24cee7db89e37cb3f3941f8e80c4379a0b355c52a52b6780"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:55cd1fa4ecfa6d9f14fbd97ac24803e6f73e897c738f771a9fe038f2f11ff07c"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f8f741b6292c86059ed175d80eefa80997125b7c478fb8769fd9ac8943a16c0"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fc212779bf8411667234b3cdd34d53de6c2b8b8b958e1e12cb473a5f367c338"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ad56edabcdb428c2e33bbf24f255fe2b43253b7d13a2cdbf05de955217313e6"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a3a1e9ee9728b2c1734f65d6a1d376c6f2f6fdcc13bb007a08cc4b1ff576dc5"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e13de156137b7095442b288e72f33503a469aa1980ed856b43c353ac86390519"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:07f59760ef99f31422c49038964b31c4dfcfeb5d2384ebfc71058a7c9adae2d2"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:59240685e7da61fb78f65a9f07f8108e36a83317c53f7b276b4175dc44151684"}, + {file = "rpds_py-0.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:83cba698cfb3c2c5a7c3c6bac12fe6c6a51aae69513726be6411076185a8b24a"}, + {file = "rpds_py-0.20.1.tar.gz", hash = "sha256:e1791c4aabd117653530dccd24108fa03cc6baf21f58b950d0a73c3b3b29a350"}, ] [[package]] @@ -3212,23 +3173,23 @@ files = [ [[package]] name = "setuptools" -version = "75.1.0" +version = "75.3.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2"}, - {file = "setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538"}, + {file = "setuptools-75.3.0-py3-none-any.whl", hash = "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd"}, + {file = "setuptools-75.3.0.tar.gz", hash = "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.12.*)", "pytest-mypy"] [[package]] name = "singledispatch" @@ -3247,24 +3208,24 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] name = "smmap" -version = "5.0.1" +version = "5.0.2" description = "A pure Python implementation of a sliding window memory map manager" optional = false python-versions = ">=3.7" files = [ - {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, - {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, + {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, + {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, ] [[package]] @@ -3310,13 +3271,13 @@ saml = ["python3-saml (>=1.5.0)"] [[package]] name = "sqlparse" -version = "0.5.1" +version = "0.5.3" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" files = [ - {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"}, - {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"}, + {file = "sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca"}, + {file = "sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272"}, ] [package.extras] @@ -3408,13 +3369,43 @@ files = [ [[package]] name = "tomli" -version = "2.0.2" +version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, - {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] @@ -3586,13 +3577,13 @@ files = [ [[package]] name = "wheel" -version = "0.44.0" +version = "0.45.1" description = "A built-package format for Python" optional = false python-versions = ">=3.8" files = [ - {file = "wheel-0.44.0-py3-none-any.whl", hash = "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f"}, - {file = "wheel-0.44.0.tar.gz", hash = "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49"}, + {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, + {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, ] [package.extras] @@ -3600,81 +3591,76 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [[package]] name = "wrapt" -version = "1.16.0" +version = "1.17.0" description = "Module for decorators, wrappers and monkey patching." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, + {file = "wrapt-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0c23b8319848426f305f9cb0c98a6e32ee68a36264f45948ccf8e7d2b941f8"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ca5f060e205f72bec57faae5bd817a1560fcfc4af03f414b08fa29106b7e2d"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e185ec6060e301a7e5f8461c86fb3640a7beb1a0f0208ffde7a65ec4074931df"}, + {file = "wrapt-1.17.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb90765dd91aed05b53cd7a87bd7f5c188fcd95960914bae0d32c5e7f899719d"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:879591c2b5ab0a7184258274c42a126b74a2c3d5a329df16d69f9cee07bba6ea"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fce6fee67c318fdfb7f285c29a82d84782ae2579c0e1b385b7f36c6e8074fffb"}, + {file = "wrapt-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0698d3a86f68abc894d537887b9bbf84d29bcfbc759e23f4644be27acf6da301"}, + {file = "wrapt-1.17.0-cp310-cp310-win32.whl", hash = "sha256:69d093792dc34a9c4c8a70e4973a3361c7a7578e9cd86961b2bbf38ca71e4e22"}, + {file = "wrapt-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:f28b29dc158ca5d6ac396c8e0a2ef45c4e97bb7e65522bfc04c989e6fe814575"}, + {file = "wrapt-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74bf625b1b4caaa7bad51d9003f8b07a468a704e0644a700e936c357c17dd45a"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f2a28eb35cf99d5f5bd12f5dd44a0f41d206db226535b37b0c60e9da162c3ed"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81b1289e99cf4bad07c23393ab447e5e96db0ab50974a280f7954b071d41b489"}, + {file = "wrapt-1.17.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2939cd4a2a52ca32bc0b359015718472d7f6de870760342e7ba295be9ebaf9"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a9653131bda68a1f029c52157fd81e11f07d485df55410401f745007bd6d339"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4e4b4385363de9052dac1a67bfb535c376f3d19c238b5f36bddc95efae15e12d"}, + {file = "wrapt-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bdf62d25234290db1837875d4dceb2151e4ea7f9fff2ed41c0fde23ed542eb5b"}, + {file = "wrapt-1.17.0-cp311-cp311-win32.whl", hash = "sha256:5d8fd17635b262448ab8f99230fe4dac991af1dabdbb92f7a70a6afac8a7e346"}, + {file = "wrapt-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:92a3d214d5e53cb1db8b015f30d544bc9d3f7179a05feb8f16df713cecc2620a"}, + {file = "wrapt-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:89fc28495896097622c3fc238915c79365dd0ede02f9a82ce436b13bd0ab7569"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875d240fdbdbe9e11f9831901fb8719da0bd4e6131f83aa9f69b96d18fae7504"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ed16d95fd142e9c72b6c10b06514ad30e846a0d0917ab406186541fe68b451"}, + {file = "wrapt-1.17.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b956061b8db634120b58f668592a772e87e2e78bc1f6a906cfcaa0cc7991c1"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:daba396199399ccabafbfc509037ac635a6bc18510ad1add8fd16d4739cdd106"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4d63f4d446e10ad19ed01188d6c1e1bb134cde8c18b0aa2acfd973d41fcc5ada"}, + {file = "wrapt-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8a5e7cc39a45fc430af1aefc4d77ee6bad72c5bcdb1322cfde852c15192b8bd4"}, + {file = "wrapt-1.17.0-cp312-cp312-win32.whl", hash = "sha256:0a0a1a1ec28b641f2a3a2c35cbe86c00051c04fffcfcc577ffcdd707df3f8635"}, + {file = "wrapt-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c34f6896a01b84bab196f7119770fd8466c8ae3dfa73c59c0bb281e7b588ce7"}, + {file = "wrapt-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:714c12485aa52efbc0fc0ade1e9ab3a70343db82627f90f2ecbc898fdf0bb181"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da427d311782324a376cacb47c1a4adc43f99fd9d996ffc1b3e8529c4074d393"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba1739fb38441a27a676f4de4123d3e858e494fac05868b7a281c0a383c098f4"}, + {file = "wrapt-1.17.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e711fc1acc7468463bc084d1b68561e40d1eaa135d8c509a65dd534403d83d7b"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:140ea00c87fafc42739bd74a94a5a9003f8e72c27c47cd4f61d8e05e6dec8721"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73a96fd11d2b2e77d623a7f26e004cc31f131a365add1ce1ce9a19e55a1eef90"}, + {file = "wrapt-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0b48554952f0f387984da81ccfa73b62e52817a4386d070c75e4db7d43a28c4a"}, + {file = "wrapt-1.17.0-cp313-cp313-win32.whl", hash = "sha256:498fec8da10e3e62edd1e7368f4b24aa362ac0ad931e678332d1b209aec93045"}, + {file = "wrapt-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd136bb85f4568fffca995bd3c8d52080b1e5b225dbf1c2b17b66b4c5fa02838"}, + {file = "wrapt-1.17.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17fcf043d0b4724858f25b8826c36e08f9fb2e475410bece0ec44a22d533da9b"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4a557d97f12813dc5e18dad9fa765ae44ddd56a672bb5de4825527c847d6379"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0229b247b0fc7dee0d36176cbb79dbaf2a9eb7ecc50ec3121f40ef443155fb1d"}, + {file = "wrapt-1.17.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8425cfce27b8b20c9b89d77fb50e368d8306a90bf2b6eef2cdf5cd5083adf83f"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c900108df470060174108012de06d45f514aa4ec21a191e7ab42988ff42a86c"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e547b447073fc0dbfcbff15154c1be8823d10dab4ad401bdb1575e3fdedff1b"}, + {file = "wrapt-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:914f66f3b6fc7b915d46c1cc424bc2441841083de01b90f9e81109c9759e43ab"}, + {file = "wrapt-1.17.0-cp313-cp313t-win32.whl", hash = "sha256:a4192b45dff127c7d69b3bdfb4d3e47b64179a0b9900b6351859f3001397dabf"}, + {file = "wrapt-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4f643df3d4419ea3f856c5c3f40fec1d65ea2e89ec812c83f7767c8730f9827a"}, + {file = "wrapt-1.17.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:69c40d4655e078ede067a7095544bcec5a963566e17503e75a3a3e0fe2803b13"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f495b6754358979379f84534f8dd7a43ff8cff2558dcdea4a148a6e713a758f"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baa7ef4e0886a6f482e00d1d5bcd37c201b383f1d314643dfb0367169f94f04c"}, + {file = "wrapt-1.17.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fc931382e56627ec4acb01e09ce66e5c03c384ca52606111cee50d931a342d"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8f8909cdb9f1b237786c09a810e24ee5e15ef17019f7cecb207ce205b9b5fcce"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad47b095f0bdc5585bced35bd088cbfe4177236c7df9984b3cc46b391cc60627"}, + {file = "wrapt-1.17.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:948a9bd0fb2c5120457b07e59c8d7210cbc8703243225dbd78f4dfc13c8d2d1f"}, + {file = "wrapt-1.17.0-cp38-cp38-win32.whl", hash = "sha256:5ae271862b2142f4bc687bdbfcc942e2473a89999a54231aa1c2c676e28f29ea"}, + {file = "wrapt-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:f335579a1b485c834849e9075191c9898e0731af45705c2ebf70e0cd5d58beed"}, + {file = "wrapt-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d751300b94e35b6016d4b1e7d0e7bbc3b5e1751e2405ef908316c2a9024008a1"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7264cbb4a18dc4acfd73b63e4bcfec9c9802614572025bdd44d0721983fc1d9c"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33539c6f5b96cf0b1105a0ff4cf5db9332e773bb521cc804a90e58dc49b10578"}, + {file = "wrapt-1.17.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c30970bdee1cad6a8da2044febd824ef6dc4cc0b19e39af3085c763fdec7de33"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc7f729a72b16ee21795a943f85c6244971724819819a41ddbaeb691b2dd85ad"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6ff02a91c4fc9b6a94e1c9c20f62ea06a7e375f42fe57587f004d1078ac86ca9"}, + {file = "wrapt-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dfb7cff84e72e7bf975b06b4989477873dcf160b2fd89959c629535df53d4e0"}, + {file = "wrapt-1.17.0-cp39-cp39-win32.whl", hash = "sha256:2399408ac33ffd5b200480ee858baa58d77dd30e0dd0cab6a8a9547135f30a88"}, + {file = "wrapt-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:4f763a29ee6a20c529496a20a7bcb16a73de27f5da6a843249c7047daf135977"}, + {file = "wrapt-1.17.0-py3-none-any.whl", hash = "sha256:d2c63b93548eda58abf5188e505ffed0229bf675f7c3090f8e36ad55b8cbc371"}, + {file = "wrapt-1.17.0.tar.gz", hash = "sha256:16187aa2317c731170a88ef35e8937ae0f533c402872c1ee5e6d079fcf320801"}, ] [[package]] @@ -3720,4 +3706,4 @@ all = [] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.13" -content-hash = "929366f2abf5e7f3a78713f5a04f12be8f5766191460a0ba35c40bf224d433c0" +content-hash = "f2041fa5a92502d80e47c6a6e762583dceabb072a9c36f52abed52a7ef2da478" diff --git a/tasks.py b/tasks.py index da4c5e7..e3ff139 100644 --- a/tasks.py +++ b/tasks.py @@ -149,7 +149,7 @@ def docker_compose(context, command, **kwargs): return context.run(compose_command, env=build_env, **kwargs) -def run_command(context, command, **kwargs): +def run_command(context, command, service="nautobot", **kwargs): """Wrapper to run a command locally or inside the nautobot container.""" if is_truthy(context.nautobot_firewall_models.local): if "command_env" in kwargs: @@ -159,7 +159,7 @@ def run_command(context, command, **kwargs): } return context.run(command, **kwargs) else: - # Check if nautobot is running, no need to start another nautobot container to run a command + # Check if service is running, no need to start another container to run a command docker_compose_status = "ps --services --filter status=running" results = docker_compose(context, docker_compose_status, hide="out") @@ -169,10 +169,10 @@ def run_command(context, command, **kwargs): for key, value in command_env.items(): command_env_args += f' --env="{key}={value}"' - if "nautobot" in results.stdout: - compose_command = f"exec{command_env_args} nautobot {command}" + if service in results.stdout: + compose_command = f"exec{command_env_args} {service} {command}" else: - compose_command = f"run{command_env_args} --rm --entrypoint='{command}' nautobot" + compose_command = f"run{command_env_args} --rm --entrypoint='{command}' {service}" pty = kwargs.pop("pty", True) @@ -411,10 +411,14 @@ def shell_plus(context): run_command(context, command) -@task -def cli(context): - """Launch a bash shell inside the Nautobot container.""" - run_command(context, "bash") +@task( + help={ + "service": "Docker compose service name to launch cli in (default: nautobot).", + } +) +def cli(context, service="nautobot"): + """Launch a bash shell inside the container.""" + run_command(context, "bash", service=service) @task( @@ -736,7 +740,8 @@ def pylint(context): else: print("No migrations directory found, skipping migrations checks.") - raise Exit(code=exit_code) + if exit_code != 0: + raise Exit(code=exit_code) @task(aliases=("a",)) @@ -780,7 +785,8 @@ def ruff(context, action=None, target=None, fix=False, output_format="concise"): if not run_command(context, command, warn=True): exit_code = 1 - raise Exit(code=exit_code) + if exit_code != 0: + raise Exit(code=exit_code) @task From c57a7ecd65df53b2fd53e7b9f3e52eb0f6999342 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Tue, 7 Jan 2025 22:55:04 -0600 Subject: [PATCH 3/8] Add back in deletions from Drift Manager. Part 1 --- mkdocs.yml | 2 +- nautobot_firewall_models/api/serializers.py | 234 ++++- nautobot_firewall_models/api/urls.py | 21 +- nautobot_firewall_models/api/views.py | 162 +++- nautobot_firewall_models/filters.py | 262 +++++- nautobot_firewall_models/forms.py | 913 +++++++++++++++++++- 6 files changed, 1572 insertions(+), 22 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 2279da0..77687af 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -129,7 +129,7 @@ nav: - Contributing to the App: "dev/contributing.md" - Development Environment: "dev/dev_environment.md" - Release Checklist: "dev/release_checklist.md" - - Architecture Decision Records: "dev/arch_decision.md" + # - Architecture Decision Records: "dev/arch_decision.md" - Code Reference: - "dev/code_reference/index.md" - Package: "dev/code_reference/package.md" diff --git a/nautobot_firewall_models/api/serializers.py b/nautobot_firewall_models/api/serializers.py index 02eddeb..30dbfa6 100644 --- a/nautobot_firewall_models/api/serializers.py +++ b/nautobot_firewall_models/api/serializers.py @@ -1,6 +1,7 @@ """API serializers for nautobot_firewall_models.""" -from nautobot.apps.api import NautobotModelSerializer, TaggedModelSerializerMixin +from nautobot.apps.api import NautobotModelSerializer, TaggedModelSerializerMixin, ValidatedModelSerializer +from rest_framework import serializers from nautobot_firewall_models import models @@ -8,11 +9,238 @@ class IPRangeSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): # pylint: disable=too-many-ancestors """IPRange Serializer.""" + start_address = serializers.CharField() + end_address = serializers.CharField() + class Meta: """Meta attributes.""" model = models.IPRange fields = "__all__" - # Option for disabling write for certain fields: - # read_only_fields = [] + # Omit the UniqueTogetherValidators that would be automatically added to validate (start_address, end_address, vrf). + # This prevents vrf from being interpreted as a required field. + validators = [] + + def validate(self, data): + """Custom validate method to enforce unique constraints on IPRange model.""" + # Validate uniqueness of (start_address, end_address, vrf) since we omitted the automatically-created validator above. + start_address = data.get("start_address") + end_address = data.get("end_address") + vrf = data.get("vrf") + if not any([start_address is not None, end_address is not None, vrf is not None]): + return super().validate(data) + + # Use existing object's attributes for partial updates + if self.instance: + start_address = start_address or self.instance.start_address + end_address = end_address or self.instance.end_address + vrf = vrf or self.instance.vrf + qs = models.IPRange.objects.exclude(pk=self.instance.pk) + else: + qs = models.IPRange.objects.all() + + if vrf is not None: + if qs.filter(start_address=start_address, end_address=end_address, vrf=vrf).exists(): + raise serializers.ValidationError("The fields start_address, end_address, vrf must make a unique set.") + elif qs.filter(start_address=start_address, end_address=end_address, vrf__isnull=True).exists(): + raise serializers.ValidationError("The fields start_address, end_address must make a unique set.") + + return super().validate(data) + + +class FQDNSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """FQDN Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.FQDN + fields = "__all__" + + +class AddressObjectSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """AddressObject Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.AddressObject + fields = "__all__" + + +class AddressObjectGroupSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """AddressObjectGroup Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.AddressObjectGroup + fields = "__all__" + + +class ApplicationObjectSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """ApplicationObject Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.ApplicationObject + fields = "__all__" + + +class ApplicationObjectGroupSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """ApplicationObjectGroup Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.ApplicationObjectGroup + fields = "__all__" + + +class ServiceObjectSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """ServiceObject Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.ServiceObject + fields = "__all__" + + +class ServiceObjectGroupSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """ServiceObjectGroup Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.ServiceObjectGroup + fields = "__all__" + + +class UserObjectSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """UserObject Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.UserObject + fields = "__all__" + + +class UserObjectGroupSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """UserObjectGroup Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.UserObjectGroup + fields = "__all__" + + +class ZoneSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """Zone Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.Zone + fields = "__all__" + + +class PolicyRuleSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """PolicyRule Serializer.""" + + index = serializers.IntegerField(required=False, default=None) + + class Meta: + """Meta attributes.""" + + model = models.PolicyRule + fields = "__all__" + + +class PolicySerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """Policy Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.Policy + fields = "__all__" + + +class NATPolicyRuleSerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """PolicyRule Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.NATPolicyRule + fields = "__all__" + + +class NATPolicySerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """NATPolicy Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.NATPolicy + fields = "__all__" + + +class CapircaPolicySerializer(NautobotModelSerializer, TaggedModelSerializerMixin): + """CapircaPolicy Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.CapircaPolicy + fields = "__all__" + + +########################### +# Through Models +########################### + + +class PolicyDeviceM2MSerializer(ValidatedModelSerializer): + """PolicyDeviceM2M Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.PolicyDeviceM2M + fields = "__all__" + + +class PolicyDynamicGroupM2MSerializer(ValidatedModelSerializer): + """PolicyDynamicGroupM2M Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.PolicyDynamicGroupM2M + fields = "__all__" + + +class NATPolicyDeviceM2MSerializer(ValidatedModelSerializer): + """NATPolicyDeviceM2M Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.NATPolicyDeviceM2M + fields = "__all__" + + +class NATPolicyDynamicGroupM2MSerializer(ValidatedModelSerializer): + """NATPolicyDynamicGroupM2M Serializer.""" + + class Meta: + """Meta attributes.""" + + model = models.NATPolicyDynamicGroupM2M + fields = "__all__" \ No newline at end of file diff --git a/nautobot_firewall_models/api/urls.py b/nautobot_firewall_models/api/urls.py index eba0064..7f5d3da 100644 --- a/nautobot_firewall_models/api/urls.py +++ b/nautobot_firewall_models/api/urls.py @@ -6,6 +6,25 @@ router = OrderedDefaultRouter() # add the name of your api endpoint, usually hyphenated model name in plural, e.g. "my-model-classes" -router.register("iprange", views.IPRangeViewSet) +router.register("address-object", views.AddressObjectViewSet) +router.register("address-object-group", views.AddressObjectGroupViewSet) +router.register("application-object", views.ApplicationObjectViewSet) +router.register("application-object-group", views.ApplicationObjectGroupViewSet) +router.register("capirca-policy", views.CapircaPolicyViewSet) +router.register("fqdn", views.FQDNViewSet) +router.register("ip-range", views.IPRangeViewSet) +router.register("nat-policy-rule", views.NATPolicyRuleViewSet) +router.register("nat-policy", views.NATPolicyViewSet) +router.register("nat-policy-device-association", views.NATPolicyDeviceM2MViewSet) +router.register("nat-policy-dynamic-group-association", views.NATPolicyDynamicGroupM2MViewSet) +router.register("policy-rule", views.PolicyRuleViewSet) +router.register("policy", views.PolicyViewSet) +router.register("policy-device-association", views.PolicyDeviceM2MViewSet) +router.register("policy-dynamic-group-association", views.PolicyDynamicGroupM2MViewSet) +router.register("service-object", views.ServiceObjectViewSet) +router.register("service-object-group", views.ServiceObjectGroupViewSet) +router.register("user-object", views.UserObjectViewSet) +router.register("user-object-group", views.UserObjectGroupViewSet) +router.register("zone", views.ZoneViewSet) urlpatterns = router.urls diff --git a/nautobot_firewall_models/api/views.py b/nautobot_firewall_models/api/views.py index e66e9e6..3938c0c 100644 --- a/nautobot_firewall_models/api/views.py +++ b/nautobot_firewall_models/api/views.py @@ -1,17 +1,171 @@ """API views for nautobot_firewall_models.""" -from nautobot.apps.api import NautobotModelViewSet +from nautobot.apps.api import ModelViewSet, NautobotModelViewSet from nautobot_firewall_models import filters, models from nautobot_firewall_models.api import serializers -class IPRangeViewSet(NautobotModelViewSet): # pylint: disable=too-many-ancestors +class IPRangeViewSet(NautobotModelViewSet): """IPRange viewset.""" queryset = models.IPRange.objects.all() serializer_class = serializers.IPRangeSerializer filterset_class = filters.IPRangeFilterSet - # Option for modifying the default HTTP methods: - # http_method_names = ["get", "post", "put", "patch", "delete", "head", "options", "trace"] + +class FQDNViewSet(NautobotModelViewSet): + """FQDN viewset.""" + + queryset = models.FQDN.objects.all() + serializer_class = serializers.FQDNSerializer + filterset_class = filters.FQDNFilterSet + + +class AddressObjectViewSet(NautobotModelViewSet): + """AddressObject viewset.""" + + queryset = models.AddressObject.objects.all() + serializer_class = serializers.AddressObjectSerializer + filterset_class = filters.AddressObjectFilterSet + + +class AddressObjectGroupViewSet(NautobotModelViewSet): + """AddressObjectGroup viewset.""" + + queryset = models.AddressObjectGroup.objects.all() + serializer_class = serializers.AddressObjectGroupSerializer + filterset_class = filters.AddressObjectGroupFilterSet + + +class ApplicationObjectViewSet(NautobotModelViewSet): + """ApplicationObject viewset.""" + + queryset = models.ApplicationObject.objects.all() + serializer_class = serializers.ApplicationObjectSerializer + filterset_class = filters.ApplicationObjectFilterSet + + +class ApplicationObjectGroupViewSet(NautobotModelViewSet): + """ApplicationObjectGroup viewset.""" + + queryset = models.ApplicationObjectGroup.objects.all() + serializer_class = serializers.ApplicationObjectGroupSerializer + filterset_class = filters.ApplicationObjectGroupFilterSet + + +class ServiceObjectViewSet(NautobotModelViewSet): + """ServiceObject viewset.""" + + queryset = models.ServiceObject.objects.all() + serializer_class = serializers.ServiceObjectSerializer + filterset_class = filters.ServiceObjectFilterSet + + +class ServiceObjectGroupViewSet(NautobotModelViewSet): + """ServiceObjectGroup viewset.""" + + queryset = models.ServiceObjectGroup.objects.all() + serializer_class = serializers.ServiceObjectGroupSerializer + filterset_class = filters.ServiceObjectGroupFilterSet + + +class UserObjectViewSet(NautobotModelViewSet): + """UserObject viewset.""" + + queryset = models.UserObject.objects.all() + serializer_class = serializers.UserObjectSerializer + filterset_class = filters.UserObjectFilterSet + + +class UserObjectGroupViewSet(NautobotModelViewSet): + """UserObjectGroup viewset.""" + + queryset = models.UserObjectGroup.objects.all() + serializer_class = serializers.UserObjectGroupSerializer + filterset_class = filters.UserObjectGroupFilterSet + + +class ZoneViewSet(NautobotModelViewSet): + """Zone viewset.""" + + queryset = models.Zone.objects.all() + serializer_class = serializers.ZoneSerializer + filterset_class = filters.ZoneFilterSet + + +class PolicyRuleViewSet(NautobotModelViewSet): + """PolicyRule viewset.""" + + queryset = models.PolicyRule.objects.all() + serializer_class = serializers.PolicyRuleSerializer + filterset_class = filters.PolicyRuleFilterSet + + +class PolicyViewSet(NautobotModelViewSet): + """Policy viewset.""" + + queryset = models.Policy.objects.all() + serializer_class = serializers.PolicySerializer + filterset_class = filters.PolicyFilterSet + + +class NATPolicyRuleViewSet(NautobotModelViewSet): + """NATPolicyRule viewset.""" + + queryset = models.NATPolicyRule.objects.all() + serializer_class = serializers.NATPolicyRuleSerializer + filterset_class = filters.NATPolicyRuleFilterSet + + +class NATPolicyViewSet(NautobotModelViewSet): + """NATPolicy viewset.""" + + queryset = models.NATPolicy.objects.all() + serializer_class = serializers.NATPolicySerializer + filterset_class = filters.NATPolicyFilterSet + + +class CapircaPolicyViewSet(ModelViewSet): + """CapircaPolicy viewset.""" + + queryset = models.CapircaPolicy.objects.all() + serializer_class = serializers.CapircaPolicySerializer + filterset_class = filters.CapircaPolicyFilterSet + + +########################### +# Through Models +########################### + + +class PolicyDeviceM2MViewSet(ModelViewSet): + """PolicyDeviceM2M viewset.""" + + queryset = models.PolicyDeviceM2M.objects.all() + serializer_class = serializers.PolicyDeviceM2MSerializer + filterset_class = filters.PolicyDeviceM2MFilterSet + + +class PolicyDynamicGroupM2MViewSet(ModelViewSet): + """PolicyDynamicGroupM2M viewset.""" + + queryset = models.PolicyDynamicGroupM2M.objects.all() + serializer_class = serializers.PolicyDynamicGroupM2MSerializer + filterset_class = filters.PolicyDynamicGroupM2MFilterSet + + +class NATPolicyDeviceM2MViewSet(ModelViewSet): + """NATPolicyDeviceM2M viewset.""" + + queryset = models.NATPolicyDeviceM2M.objects.all() + serializer_class = serializers.NATPolicyDeviceM2MSerializer + filterset_class = filters.NATPolicyDeviceM2MFilterSet + + +class NATPolicyDynamicGroupM2MViewSet(ModelViewSet): + """NATPolicyDynamicGroupM2M viewset.""" + + queryset = models.NATPolicyDynamicGroupM2M.objects.all() + serializer_class = serializers.NATPolicyDynamicGroupM2MSerializer + filterset_class = filters.NATPolicyDynamicGroupM2MFilterSet diff --git a/nautobot_firewall_models/filters.py b/nautobot_firewall_models/filters.py index 8502ba3..273ff0f 100644 --- a/nautobot_firewall_models/filters.py +++ b/nautobot_firewall_models/filters.py @@ -1,17 +1,271 @@ """Filtering for nautobot_firewall_models.""" -from nautobot.apps.filters import NameSearchFilterSet, NautobotFilterSet +import django_filters +from django.contrib.contenttypes.fields import GenericRelation +from django.core.exceptions import ValidationError +from nautobot.apps.filters import ( + MultiValueCharFilter, + NaturalKeyOrPKMultipleChoiceFilter, + NautobotFilterSet, + SearchFilter, + StatusModelFilterSetMixin, +) +from nautobot.dcim.models import Device from nautobot_firewall_models import models -class IPRangeFilterSet(NautobotFilterSet, NameSearchFilterSet): # pylint: disable=too-many-ancestors +class BaseFilterSet(StatusModelFilterSetMixin, django_filters.filterset.FilterSet): + """A base class for adding the search method to models which only expose the `name` and `description` fields.""" + + q = SearchFilter( + filter_predicates={ + "name": "icontains", + "description": "icontains", + } + ) + + +class IPRangeFilterSet(BaseFilterSet, NautobotFilterSet): # pylint: disable=too-many-ancestors """Filter for IPRange.""" + start_address = MultiValueCharFilter( + method="filter_address", + label="Address", + ) + end_address = MultiValueCharFilter( + method="filter_address", + label="Address", + ) + class Meta: """Meta attributes for filter.""" model = models.IPRange - # add any fields from the model that you would like to filter your searches by using those - fields = ["id", "name", "description"] + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + def filter_address(self, queryset, name, value): # pylint: disable=unused-argument + """Filter method for start & end addresses.""" + try: + return queryset.net_in(value) + except ValidationError: + return queryset.none() + + +class FQDNFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for FQDN.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.FQDN + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class AddressObjectFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for AddressObject.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.AddressObject + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class AddressObjectGroupFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for AddressObjectGroup.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.AddressObjectGroup + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class ApplicationObjectFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for ApplicationObject.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.ApplicationObject + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class ApplicationObjectGroupFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for ApplicationObjectGroup.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.ApplicationObjectGroup + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class ServiceObjectFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for ServiceObject.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.ServiceObject + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class ServiceObjectGroupFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for ServiceObjectGroup.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.ServiceObjectGroup + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class UserObjectFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for UserObject.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.UserObject + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class UserObjectGroupFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for UserObjectGroup.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.UserObjectGroup + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class ZoneFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for Zone.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.Zone + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class PolicyRuleFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for PolicyRule.""" + + q = SearchFilter( + filter_predicates={ + "name": "icontains", + "description": "icontains", + "request_id": "icontains", + } + ) + + class Meta: + """Meta attributes for filter.""" + + model = models.PolicyRule + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class NATPolicyRuleFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for NATPolicyRule.""" + + q = SearchFilter( + filter_predicates={ + "name": "icontains", + "description": "icontains", + "request_id": "icontains", + } + ) + + class Meta: + """Meta attributes for filter.""" + + model = models.NATPolicyRule + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class PolicyFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for Policy.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.Policy + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class NATPolicyFilterSet(BaseFilterSet, NautobotFilterSet): + """Filter for NATPolicy.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.NATPolicy + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class CapircaPolicyFilterSet(NautobotFilterSet): + """Filter for CapircaPolicy.""" + + device = NaturalKeyOrPKMultipleChoiceFilter( + field_name="device", + queryset=Device.objects.all(), + to_field_name="name", + label="Schema (name or PK)", + ) + + class Meta: + """Meta attributes for filter.""" + + model = models.CapircaPolicy + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +########################### +# Through Models +########################### + + +class PolicyDeviceM2MFilterSet(NautobotFilterSet): + """Filter for PolicyDeviceM2M.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.PolicyDeviceM2M + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class PolicyDynamicGroupM2MFilterSet(NautobotFilterSet): + """Filter for PolicyDynamicGroupM2M.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.PolicyDynamicGroupM2M + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class NATPolicyDeviceM2MFilterSet(NautobotFilterSet): + """Filter for NATPolicyDeviceM2M.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.NATPolicyDeviceM2M + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] + + +class NATPolicyDynamicGroupM2MFilterSet(NautobotFilterSet): + """Filter for NATPolicyDynamicGroupM2M.""" + + class Meta: + """Meta attributes for filter.""" + + model = models.NATPolicyDynamicGroupM2M + fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] diff --git a/nautobot_firewall_models/forms.py b/nautobot_firewall_models/forms.py index bd00de7..a436914 100644 --- a/nautobot_firewall_models/forms.py +++ b/nautobot_firewall_models/forms.py @@ -1,47 +1,942 @@ """Forms for nautobot_firewall_models.""" from django import forms -from nautobot.apps.forms import NautobotBulkEditForm, NautobotFilterForm, NautobotModelForm, TagsBulkEditFormMixin +from nautobot.apps.forms import ( + DynamicModelChoiceField, + DynamicModelMultipleChoiceField, + TagFilterField, + add_blank_choice, +) +from nautobot.dcim.models import Device, Interface +from nautobot.extras.forms import ( + CustomFieldModelCSVForm, + LocalContextFilterForm, + LocalContextModelBulkEditForm, + LocalContextModelForm, + NautobotBulkEditForm, + NautobotFilterForm, + NautobotModelForm, +) +from nautobot.extras.models import DynamicGroup, Tag +from nautobot.ipam.models import VRF, IPAddress, Prefix +from nautobot.tenancy.forms import TenancyFilterForm, TenancyForm +from nautobot.tenancy.models import Tenant -from nautobot_firewall_models import models +from nautobot_firewall_models import choices, fields, models -class IPRangeForm(NautobotModelForm): # pylint: disable=too-many-ancestors +class IPRangeFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "start_address", "end_address", "vrf"] + + model = models.IPRange + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + start_address = forms.CharField(required=False, label="Starting Address") + end_address = forms.CharField(required=False, label="Ending Address") + vrf = DynamicModelChoiceField(queryset=VRF.objects.all(), label="VRF", required=False) + + +class IPRangeForm(fields.IPRangeFieldMixin, LocalContextModelForm, NautobotModelForm): """IPRange creation/edit form.""" + vrf = DynamicModelChoiceField(queryset=VRF.objects.all(), label="VRF", required=False) + class Meta: """Meta attributes.""" model = models.IPRange + fields = ["vrf", "description", "status", "tags"] + + +class IPRangeBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """IPRange bulk edit form.""" + + pk = DynamicModelMultipleChoiceField(queryset=models.IPRange.objects.all(), widget=forms.MultipleHiddenInput) + description = forms.CharField(required=False) + # start_address = forms.CharField(required=False) + # end_address = forms.CharField(required=False) + vrf = DynamicModelChoiceField(queryset=VRF.objects.all(), required=False) + + class Meta: + """Meta attributes.""" + + nullable_fields = ["description", "vrf"] + + +class FQDNFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "name"] + + model = models.FQDN + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + + +class FQDNForm(LocalContextModelForm, NautobotModelForm): + """FQDN creation/edit form.""" + + ip_addresses = DynamicModelMultipleChoiceField(queryset=IPAddress.objects.all(), required=False) + + class Meta: + """Meta attributes.""" + + model = models.FQDN + fields = ["name", "description", "ip_addresses", "status", "tags"] + + +class FQDNBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """FQDN bulk edit form.""" + + pk = DynamicModelMultipleChoiceField(queryset=models.FQDN.objects.all(), widget=forms.MultipleHiddenInput) + description = forms.CharField(required=False) + ip_addresses = DynamicModelMultipleChoiceField(queryset=IPAddress.objects.all(), required=False) + + class Meta: + """Meta attributes.""" + + nullable_fields = ["description", "ip_addresses"] + + +class AddressObjectFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "name"] + + model = models.AddressObject + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + ip_address = DynamicModelChoiceField(queryset=IPAddress.objects.all(), required=False, label="IP Address") + ip_range = DynamicModelChoiceField(queryset=models.IPRange.objects.all(), required=False, label="IP Range") + prefix = DynamicModelChoiceField(queryset=Prefix.objects.all(), required=False, label="Prefix") + fqdn = DynamicModelChoiceField(queryset=models.FQDN.objects.all(), required=False, label="FQDN") + + +class AddressObjectForm(LocalContextModelForm, NautobotModelForm): + """AddressObject creation/edit form.""" + + ip_address = DynamicModelChoiceField(queryset=IPAddress.objects.all(), required=False, label="IP Address") + ip_range = DynamicModelChoiceField(queryset=models.IPRange.objects.all(), required=False, label="IP Range") + prefix = DynamicModelChoiceField(queryset=Prefix.objects.all(), required=False, label="Prefix") + fqdn = DynamicModelChoiceField(queryset=models.FQDN.objects.all(), required=False, label="FQDN") + + class Meta: + """Meta attributes.""" + + model = models.AddressObject + fields = ["name", "description", "fqdn", "ip_range", "ip_address", "prefix", "status", "tags"] + + +class AddressObjectBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """AddressObject bulk edit form.""" + + pk = DynamicModelMultipleChoiceField(queryset=models.AddressObject.objects.all(), widget=forms.MultipleHiddenInput) + description = forms.CharField(required=False) + + class Meta: + """Meta attributes.""" + + nullable_fields = ["description", "fqdn", "ip_range", "ip_address", "prefix"] + + +class AddressObjectGroupFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "name"] + + model = models.AddressObjectGroup + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + + +class AddressObjectGroupForm(LocalContextModelForm, NautobotModelForm): + """AddressObjectGroup creation/edit form.""" + + address_objects = DynamicModelMultipleChoiceField(queryset=models.AddressObject.objects.all(), required=False) + + class Meta: + """Meta attributes.""" + + model = models.AddressObjectGroup + fields = ["name", "description", "address_objects", "status", "tags"] + + +class AddressObjectGroupBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """AddressObjectGroup bulk edit form.""" + + pk = DynamicModelMultipleChoiceField( + queryset=models.AddressObjectGroup.objects.all(), widget=forms.MultipleHiddenInput + ) + description = forms.CharField(required=False) + + class Meta: + """Meta attributes.""" + + nullable_fields = [ + "description", + ] + + +class ApplicationObjectFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "name"] + + model = models.ApplicationObject + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + category = DynamicModelChoiceField( + queryset=models.ApplicationObject.objects.all(), required=False, label="Category" + ) + + +class ApplicationObjectForm(LocalContextModelForm, NautobotModelForm): + """ApplicationObject creation/edit form.""" + + class Meta: + """Meta attributes.""" + + model = models.ApplicationObject fields = [ "name", "description", + "category", + "subcategory", + "technology", + "risk", + "default_type", + "default_ip_protocol", + "status", ] -class IPRangeBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm): # pylint: disable=too-many-ancestors - """IPRange bulk edit form.""" +class ApplicationObjectBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """ApplicationObject bulk edit form.""" - pk = forms.ModelMultipleChoiceField(queryset=models.IPRange.objects.all(), widget=forms.MultipleHiddenInput) + pk = DynamicModelMultipleChoiceField( + queryset=models.ApplicationObject.objects.all(), widget=forms.MultipleHiddenInput + ) description = forms.CharField(required=False) + risk = forms.IntegerField(required=False) + technology = forms.CharField(required=False) + category = forms.CharField(required=False) + subcategory = forms.CharField(required=False) class Meta: """Meta attributes.""" nullable_fields = [ "description", + "default_ip_protocol", + "default_type", + "technology", + "category", + "subcategory", ] -class IPRangeFilterForm(NautobotFilterForm): +class ApplicationObjectGroupFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "name"] + + model = models.ApplicationObjectGroup + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + + +class ApplicationObjectGroupForm(LocalContextModelForm, NautobotModelForm): + """ApplicationObjectGroup creation/edit form.""" + + application_objects = DynamicModelMultipleChoiceField(queryset=models.ApplicationObject.objects.all()) + + class Meta: + """Meta attributes.""" + + model = models.ApplicationObjectGroup + fields = ["name", "description", "application_objects", "status", "tags"] + + +class ApplicationObjectGroupBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """ApplicationObjectGroup bulk edit form.""" + + pk = DynamicModelMultipleChoiceField( + queryset=models.ApplicationObjectGroup.objects.all(), widget=forms.MultipleHiddenInput + ) + description = forms.CharField(required=False) + + class Meta: + """Meta attributes.""" + + nullable_fields = [ + "description", + ] + + +class ServiceObjectFilterForm(LocalContextFilterForm, NautobotFilterForm): """Filter form to filter searches.""" - model = models.IPRange field_order = ["q", "name"] + model = models.ServiceObject q = forms.CharField( required=False, label="Search", - help_text="Search within Name or Slug.", + help_text="Search within Name or Description.", ) name = forms.CharField(required=False, label="Name") + port = forms.IntegerField(required=False) + ip_protocol = forms.ChoiceField(choices=add_blank_choice(choices.IP_PROTOCOL_CHOICES), required=False) + + +class ServiceObjectForm(LocalContextModelForm, NautobotModelForm): + """ServiceObject creation/edit form.""" + + port = forms.CharField( + help_text="Must be a single integer representation of port OR single port range without spaces (e.g. 80 or 8080-8088)", + required=False, + ) + + class Meta: + """Meta attributes.""" + + model = models.ServiceObject + fields = ["name", "description", "port", "ip_protocol", "status", "tags"] + + +class ServiceObjectBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """ServiceObject bulk edit form.""" + + pk = DynamicModelMultipleChoiceField(queryset=models.ServiceObject.objects.all(), widget=forms.MultipleHiddenInput) + description = forms.CharField(required=False) + port = forms.CharField( + help_text="Must be a single integer representation of port OR single port range without spaces (e.g. 80 or 8080-8088)", + required=False, + ) + + class Meta: + """Meta attributes.""" + + nullable_fields = ["description", "port"] + + +class ServiceObjectGroupFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "name"] + + model = models.ServiceObjectGroup + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + + +class ServiceObjectGroupForm(LocalContextModelForm, NautobotModelForm): + """ServiceObjectGroup creation/edit form.""" + + service_objects = DynamicModelMultipleChoiceField(queryset=models.ServiceObject.objects.all(), required=False) + + class Meta: + """Meta attributes.""" + + model = models.ServiceObjectGroup + fields = ["name", "description", "service_objects", "status", "tags"] + + +class ServiceObjectGroupBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """ServiceObjectGroup bulk edit form.""" + + pk = DynamicModelMultipleChoiceField( + queryset=models.ServiceObjectGroup.objects.all(), widget=forms.MultipleHiddenInput + ) + description = forms.CharField(required=False) + + class Meta: + """Meta attributes.""" + + nullable_fields = [ + "description", + ] + + +class UserObjectFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "username", "name"] + + model = models.UserObject + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + username = forms.CharField(required=False, label="Username") + + +class UserObjectForm(LocalContextModelForm, NautobotModelForm): + """UserObject creation/edit form.""" + + username = forms.CharField(label="Username") + name = forms.CharField( + label="Name", + required=False, + ) + + class Meta: + """Meta attributes.""" + + model = models.UserObject + fields = ["username", "name", "status", "tags"] + + +class UserObjectBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """UserObject bulk edit form.""" + + pk = DynamicModelMultipleChoiceField(queryset=models.UserObject.objects.all(), widget=forms.MultipleHiddenInput) + name = forms.CharField(required=False) + + class Meta: + """Meta attributes.""" + + nullable_fields = [ + "name", + ] + + +class UserObjectGroupFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "name"] + + model = models.UserObjectGroup + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + + +class UserObjectGroupForm(LocalContextModelForm, NautobotModelForm): + """UserObjectGroup creation/edit form.""" + + user_objects = DynamicModelMultipleChoiceField(queryset=models.UserObject.objects.all(), required=False) + + class Meta: + """Meta attributes.""" + + model = models.UserObjectGroup + fields = ["name", "description", "user_objects", "status", "tags"] + + +class UserObjectGroupBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """UserObjectGroup bulk edit form.""" + + pk = DynamicModelMultipleChoiceField( + queryset=models.UserObjectGroup.objects.all(), widget=forms.MultipleHiddenInput + ) + description = forms.CharField(required=False) + + class Meta: + """Meta attributes.""" + + nullable_fields = [ + "description", + ] + + +class ZoneFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "name"] + + model = models.Zone + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + vrfs = DynamicModelChoiceField(queryset=VRF.objects.all(), label="VRF") + interfaces = DynamicModelChoiceField(queryset=Interface.objects.all(), label="Interface") + + +class ZoneForm(LocalContextModelForm, NautobotModelForm): + """Zone creation/edit form.""" + + vrfs = DynamicModelMultipleChoiceField(queryset=VRF.objects.all(), required=False, label="VRF") + device = DynamicModelChoiceField(queryset=Device.objects.all(), required=False) + interfaces = DynamicModelMultipleChoiceField( + queryset=Interface.objects.all(), required=False, label="Interface", query_params={"device_id": "$device"} + ) + + class Meta: + """Meta attributes.""" + + model = models.Zone + fields = ["name", "description", "vrfs", "device", "interfaces", "status", "tags"] + + +class ZoneBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """Zone bulk edit form.""" + + pk = DynamicModelMultipleChoiceField(queryset=models.Zone.objects.all(), widget=forms.MultipleHiddenInput) + description = forms.CharField(required=False) + vrfs = DynamicModelMultipleChoiceField(queryset=VRF.objects.all(), required=False, label="VRF") + interfaces = DynamicModelMultipleChoiceField(queryset=Interface.objects.all(), required=False, label="Interface") + + class Meta: + """Meta attributes.""" + + nullable_fields = ["description", "vrfs", "interfaces"] + + +class PolicyRuleFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "name"] + + model = models.PolicyRule + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + tag = TagFilterField(models.PolicyRule) + + +class PolicyRuleForm(LocalContextModelForm, NautobotModelForm): + """PolicyRule creation/edit form.""" + + tags = DynamicModelMultipleChoiceField(queryset=Tag.objects.all(), required=False) + source_users = DynamicModelMultipleChoiceField( + queryset=models.UserObject.objects.all(), label="Source User Objects", required=False + ) + source_user_groups = DynamicModelMultipleChoiceField( + queryset=models.UserObjectGroup.objects.all(), label="Source User Object Groups", required=False + ) + source_addresses = DynamicModelMultipleChoiceField( + queryset=models.AddressObject.objects.all(), label="Source Address Objects", required=False + ) + source_address_groups = DynamicModelMultipleChoiceField( + queryset=models.AddressObjectGroup.objects.all(), label="Source Address Object Groups", required=False + ) + source_zone = DynamicModelChoiceField(queryset=models.Zone.objects.all(), label="Source Zone", required=False) + source_services = DynamicModelMultipleChoiceField( + queryset=models.ServiceObject.objects.all(), label="Source Service Objects", required=False + ) + source_service_groups = DynamicModelMultipleChoiceField( + queryset=models.ServiceObjectGroup.objects.all(), label="Source Service Object Groups", required=False + ) + destination_addresses = DynamicModelMultipleChoiceField( + queryset=models.AddressObject.objects.all(), label="Destination Address Objects", required=False + ) + destination_address_groups = DynamicModelMultipleChoiceField( + queryset=models.AddressObjectGroup.objects.all(), label="Destination Address Object Groups", required=False + ) + destination_zone = DynamicModelChoiceField( + queryset=models.Zone.objects.all(), label="Destination Zone", required=False + ) + destination_services = DynamicModelMultipleChoiceField( + queryset=models.ServiceObject.objects.all(), label="Destination Service Objects", required=False + ) + destination_service_groups = DynamicModelMultipleChoiceField( + queryset=models.ServiceObjectGroup.objects.all(), label="Destination Service Object Groups", required=False + ) + applications = DynamicModelMultipleChoiceField( + queryset=models.ApplicationObject.objects.all(), label="Destination Application Objects", required=False + ) + application_groups = DynamicModelMultipleChoiceField( + queryset=models.ApplicationObjectGroup.objects.all(), + label="Destination Application Object Groups", + required=False, + ) + request_id = forms.CharField(required=False, label="Optional field for request ticket identifier.") + + class Meta: + """Meta attributes.""" + + model = models.PolicyRule + fields = ( + # pylint: disable=duplicate-code + "name", + "index", + "source_users", + "source_user_groups", + "source_addresses", + "source_address_groups", + "source_zone", + "source_services", + "source_service_groups", + "destination_addresses", + "destination_address_groups", + "destination_zone", + "destination_services", + "destination_service_groups", + "applications", + "application_groups", + "action", + "log", + "status", + "tags", + "request_id", + "description", + ) + + +# TODO: Refactor +class PolicyRuleBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """PolicyRule bulk edit form.""" + + pk = DynamicModelMultipleChoiceField(queryset=models.PolicyRule.objects.all(), widget=forms.MultipleHiddenInput) + action = forms.ChoiceField(choices=add_blank_choice(choices.ACTION_CHOICES), required=False) + log = forms.BooleanField(required=False) + description = forms.CharField(required=False) + + class Meta: + """Meta attributes.""" + + nullable_fields = ["description", "tags"] + + +class PolicyFilterForm(LocalContextFilterForm, NautobotFilterForm, TenancyFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "name", "assigned_devices", "assigned_dynamic_groups"] + + model = models.Policy + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + assigned_devices = DynamicModelChoiceField(queryset=Device.objects.all(), required=False) + assigned_dynamic_groups = DynamicModelChoiceField(queryset=DynamicGroup.objects.all(), required=False) + + +class PolicyForm(LocalContextModelForm, NautobotModelForm, TenancyForm): + """Policy creation/edit form.""" + + assigned_devices = DynamicModelMultipleChoiceField(queryset=Device.objects.all(), required=False) + assigned_dynamic_groups = DynamicModelMultipleChoiceField(queryset=DynamicGroup.objects.all(), required=False) + policy_rules = DynamicModelMultipleChoiceField(queryset=models.PolicyRule.objects.all(), required=False) + + class Meta: + """Meta attributes.""" + + model = models.Policy + fields = [ + "name", + "description", + "policy_rules", + "status", + "assigned_devices", + "assigned_dynamic_groups", + "tenant_group", + "tenant", + "tags", + ] + + +class PolicyBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """Policy bulk edit form.""" + + pk = DynamicModelMultipleChoiceField(queryset=models.Policy.objects.all(), widget=forms.MultipleHiddenInput) + description = forms.CharField(required=False) + assigned_devices = DynamicModelMultipleChoiceField(queryset=Device.objects.all(), required=False) + assigned_dynamic_groups = DynamicModelMultipleChoiceField(queryset=DynamicGroup.objects.all(), required=False) + policy_rules = DynamicModelMultipleChoiceField(queryset=models.PolicyRule.objects.all(), required=False) + tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False) + + class Meta: + """Meta attributes.""" + + nullable_fields = [ + "description", + ] + + +# NATPolicy + + +class NATPolicyRuleFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "name"] + + model = models.NATPolicyRule + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + tag = TagFilterField(models.NATPolicyRule) + + +class NATPolicyRuleForm(LocalContextModelForm, NautobotModelForm): + """NATPolicyRule creation/edit form.""" + + # Metadata + tags = DynamicModelMultipleChoiceField(queryset=Tag.objects.all(), required=False) + request_id = forms.CharField(required=False, label="Optional field for request ticket identifier.") + + # Data that can not undergo a translation + source_zone = DynamicModelChoiceField(queryset=models.Zone.objects.all(), label="Source Zone", required=False) + destination_zone = DynamicModelChoiceField( + queryset=models.Zone.objects.all(), label="Destination Zone", required=False + ) + + # Original source data + original_source_addresses = DynamicModelMultipleChoiceField( + queryset=models.AddressObject.objects.all(), label="Original Source Address Objects", required=False + ) + original_source_address_groups = DynamicModelMultipleChoiceField( + queryset=models.AddressObjectGroup.objects.all(), label="Original Source Address Object Groups", required=False + ) + original_source_services = DynamicModelMultipleChoiceField( + queryset=models.ServiceObject.objects.all(), label="Original Source Service Objects", required=False + ) + original_source_service_groups = DynamicModelMultipleChoiceField( + queryset=models.ServiceObjectGroup.objects.all(), label="Original Source Service Object Groups", required=False + ) + + # Translated source data + translated_source_addresses = DynamicModelMultipleChoiceField( + queryset=models.AddressObject.objects.all(), label="Translated Source Address Objects", required=False + ) + translated_source_address_groups = DynamicModelMultipleChoiceField( + queryset=models.AddressObjectGroup.objects.all(), + label="Translated Source Address Object Groups", + required=False, + ) + translated_source_services = DynamicModelMultipleChoiceField( + queryset=models.ServiceObject.objects.all(), label="Translated Source Service Objects", required=False + ) + translated_source_service_groups = DynamicModelMultipleChoiceField( + queryset=models.ServiceObjectGroup.objects.all(), + label="Translated Source Service Object Groups", + required=False, + ) + + # Original destination data + original_destination_addresses = DynamicModelMultipleChoiceField( + queryset=models.AddressObject.objects.all(), label="Original Destination Address Objects", required=False + ) + original_destination_address_groups = DynamicModelMultipleChoiceField( + queryset=models.AddressObjectGroup.objects.all(), + label="Original Destination Address Object Groups", + required=False, + ) + original_destination_services = DynamicModelMultipleChoiceField( + queryset=models.ServiceObject.objects.all(), label="Original Destination Service Objects", required=False + ) + original_destination_service_groups = DynamicModelMultipleChoiceField( + queryset=models.ServiceObjectGroup.objects.all(), + label="Original Destination Service Object Groups", + required=False, + ) + + # Translated destination data + translated_destination_addresses = DynamicModelMultipleChoiceField( + queryset=models.AddressObject.objects.all(), label="Translated Destination Address Objects", required=False + ) + translated_destination_address_groups = DynamicModelMultipleChoiceField( + queryset=models.AddressObjectGroup.objects.all(), + label="Translated Destination Address Object Groups", + required=False, + ) + translated_destination_services = DynamicModelMultipleChoiceField( + queryset=models.ServiceObject.objects.all(), label="Translated Destination Service Objects", required=False + ) + translated_destination_service_groups = DynamicModelMultipleChoiceField( + queryset=models.ServiceObjectGroup.objects.all(), + label="Translated Destination Service Object Groups", + required=False, + ) + + class Meta: + """Meta attributes.""" + + model = models.NATPolicyRule + fields = ( + # pylint: disable=duplicate-code + "name", + "source_zone", + "destination_zone", + "original_source_addresses", + "original_source_address_groups", + "original_source_services", + "original_source_service_groups", + "translated_source_addresses", + "translated_source_address_groups", + "translated_source_services", + "translated_source_service_groups", + "original_destination_addresses", + "original_destination_address_groups", + "original_destination_services", + "original_destination_service_groups", + "translated_destination_addresses", + "translated_destination_address_groups", + "translated_destination_services", + "translated_destination_service_groups", + "remark", + "log", + "status", + "tags", + "request_id", + "description", + ) + + +# TODO: Refactor +class NATPolicyRuleBulkEditForm(PolicyRuleBulkEditForm): + """NATPolicyRule bulk edit form.""" + + pk = DynamicModelMultipleChoiceField(queryset=models.NATPolicyRule.objects.all(), widget=forms.MultipleHiddenInput) + log = forms.BooleanField(required=False) + description = forms.CharField(required=False) + + class Meta: + """Meta attributes.""" + + nullable_fields = ["description", "tags"] + + +class NATPolicyFilterForm(NautobotFilterForm, TenancyFilterForm): + """Filter form to filter searches.""" + + field_order = ["q", "name", "assigned_devices", "assigned_dynamic_groups"] + + model = models.NATPolicy + q = forms.CharField( + required=False, + label="Search", + help_text="Search within Name or Description.", + ) + name = forms.CharField(required=False, label="Name") + assigned_devices = DynamicModelChoiceField(queryset=Device.objects.all(), required=False) + assigned_dynamic_groups = DynamicModelChoiceField(queryset=DynamicGroup.objects.all(), required=False) + + +class NATPolicyForm(LocalContextModelForm, NautobotModelForm, TenancyForm): + """NATPolicy creation/edit form.""" + + assigned_devices = DynamicModelMultipleChoiceField(queryset=Device.objects.all(), required=False) + assigned_dynamic_groups = DynamicModelMultipleChoiceField(queryset=DynamicGroup.objects.all(), required=False) + nat_policy_rules = DynamicModelMultipleChoiceField(queryset=models.NATPolicyRule.objects.all(), required=False) + + class Meta: + """Meta attributes.""" + + model = models.NATPolicy + fields = [ + "name", + "description", + "nat_policy_rules", + "status", + "assigned_devices", + "assigned_dynamic_groups", + "tenant_group", + "tenant", + "tags", + ] + + +class NATPolicyBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """NATPolicy bulk edit form.""" + + pk = DynamicModelMultipleChoiceField(queryset=models.NATPolicy.objects.all(), widget=forms.MultipleHiddenInput) + description = forms.CharField(required=False) + assigned_devices = DynamicModelMultipleChoiceField(queryset=Device.objects.all(), required=False) + assigned_dynamic_groups = DynamicModelMultipleChoiceField(queryset=DynamicGroup.objects.all(), required=False) + policy_rules = DynamicModelMultipleChoiceField(queryset=models.NATPolicyRule.objects.all(), required=False) + tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False) + + class Meta: + """Meta attributes.""" + + nullable_fields = [ + "description", + ] + + +# CapircaPolicy + + +class CapircaPolicyForm(LocalContextModelForm, NautobotModelForm): + """Filter Form for CapircaPolicy instances.""" + + device = DynamicModelChoiceField(queryset=Device.objects.all()) + + class Meta: + """Boilerplate form Meta data for compliance rule.""" + + model = models.CapircaPolicy + fields = ( + "device", + "pol", + "net", + "svc", + "cfg", + ) + + +class CapircaPolicyFilterForm(LocalContextFilterForm, NautobotFilterForm): + """Form for CapircaPolicy instances.""" + + model = models.CapircaPolicy + + q = forms.CharField(required=False, label="Search") + + +class CapircaPolicyBulkEditForm(LocalContextModelBulkEditForm, NautobotBulkEditForm): + """BulkEdit form for CapircaPolicy instances.""" + + pk = forms.ModelMultipleChoiceField(queryset=models.CapircaPolicy.objects.all(), widget=forms.MultipleHiddenInput) + + class Meta: + """Boilerplate form Meta data for CapircaPolicy.""" + + nullable_fields = [] + + +class CapircaPolicyCSVForm(CustomFieldModelCSVForm): + """CSV Form for CapircaPolicy instances.""" + + class Meta: + """Boilerplate form Meta data for CapircaPolicy.""" + + model = models.CapircaPolicy + fields = models.CapircaPolicy.csv_headers From 84b83c4f5ef11ed1b7bb15fff06c74c418d4f677 Mon Sep 17 00:00:00 2001 From: Stephen Kiely Date: Tue, 7 Jan 2025 23:37:41 -0600 Subject: [PATCH 4/8] Resolving the Drift Manager conflicts --- nautobot_firewall_models/api/serializers.py | 2 +- nautobot_firewall_models/models.py | 38 -- nautobot_firewall_models/navigation.py | 234 ++++++++- nautobot_firewall_models/tables.py | 342 +++++++++++++- .../iprange_retrieve.html | 31 +- nautobot_firewall_models/tests/fixtures.py | 443 +++++++++++++++++- .../tests/test_api_views.py | 27 -- .../tests/test_filter_iprange.py | 28 -- .../tests/test_form_iprange.py | 33 -- .../tests/test_model_iprange.py | 22 - nautobot_firewall_models/tests/test_views.py | 28 -- nautobot_firewall_models/urls.py | 24 +- nautobot_firewall_models/views.py | 19 - poetry.lock | 61 ++- 14 files changed, 1076 insertions(+), 256 deletions(-) delete mode 100644 nautobot_firewall_models/models.py delete mode 100644 nautobot_firewall_models/tests/test_api_views.py delete mode 100644 nautobot_firewall_models/tests/test_filter_iprange.py delete mode 100644 nautobot_firewall_models/tests/test_form_iprange.py delete mode 100644 nautobot_firewall_models/tests/test_model_iprange.py delete mode 100644 nautobot_firewall_models/tests/test_views.py delete mode 100644 nautobot_firewall_models/views.py diff --git a/nautobot_firewall_models/api/serializers.py b/nautobot_firewall_models/api/serializers.py index 30dbfa6..1568e3b 100644 --- a/nautobot_firewall_models/api/serializers.py +++ b/nautobot_firewall_models/api/serializers.py @@ -243,4 +243,4 @@ class Meta: """Meta attributes.""" model = models.NATPolicyDynamicGroupM2M - fields = "__all__" \ No newline at end of file + fields = "__all__" diff --git a/nautobot_firewall_models/models.py b/nautobot_firewall_models/models.py deleted file mode 100644 index cbe55cf..0000000 --- a/nautobot_firewall_models/models.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Models for Nautobot Firewall Models.""" - -# Django imports -from django.db import models - -# Nautobot imports -from nautobot.apps.models import PrimaryModel - -# from nautobot.apps.models import extras_features -# If you want to use the extras_features decorator please reference the following documentation -# https://docs.nautobot.com/projects/core/en/latest/plugins/development/#using-the-extras_features-decorator-for-graphql -# Then based on your reading you may decide to put the following decorator before the declaration of your class -# @extras_features("custom_fields", "custom_validators", "relationships", "graphql") - - -# If you want to choose a specific model to overload in your class declaration, please reference the following documentation: -# how to chose a database model: https://docs.nautobot.com/projects/core/en/stable/plugins/development/#database-models -class IPRange(PrimaryModel): # pylint: disable=too-many-ancestors - """Base model for Nautobot Firewall Models app.""" - - name = models.CharField(max_length=100, unique=True) - description = models.CharField(max_length=200, blank=True) - # additional model fields - - class Meta: - """Meta class.""" - - ordering = ["name"] - - # Option for fixing capitalization (i.e. "Snmp" vs "SNMP") - # verbose_name = "Nautobot Firewall Models" - - # Option for fixing plural name (i.e. "Chicken Tenders" vs "Chicken Tendies") - # verbose_name_plural = "Nautobot Firewall Modelss" - - def __str__(self): - """Stringify instance.""" - return self.name diff --git a/nautobot_firewall_models/navigation.py b/nautobot_firewall_models/navigation.py index 5f23939..a7bd280 100644 --- a/nautobot_firewall_models/navigation.py +++ b/nautobot_firewall_models/navigation.py @@ -1,24 +1,224 @@ """Menu items.""" -from nautobot.apps.ui import NavMenuAddButton, NavMenuGroup, NavMenuItem, NavMenuTab - -items = ( - NavMenuItem( - link="plugins:nautobot_firewall_models:iprange_list", - name="Nautobot Firewall Models", - permissions=["nautobot_firewall_models.view_iprange"], - buttons=( - NavMenuAddButton( - link="plugins:nautobot_firewall_models:iprange_add", - permissions=["nautobot_firewall_models.add_iprange"], - ), - ), - ), -) +from nautobot.core.apps import NavMenuAddButton, NavMenuGroup, NavMenuItem, NavMenuTab menu_items = ( NavMenuTab( - name="Apps", - groups=(NavMenuGroup(name="Nautobot Firewall Models", items=tuple(items)),), + name="Security", + # weight=150, + groups=[ + NavMenuGroup( + name="Address", + weight=100, + items=[ + NavMenuItem( + link="plugins:nautobot_firewall_models:fqdn_list", + name="FQDNs", + permissions=["nautobot_firewall_models.view_fqdn"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:fqdn_add", + permissions=["nautobot_firewall_models.add_fqdn"], + ), + ], + ), + NavMenuItem( + link="plugins:nautobot_firewall_models:iprange_list", + name="IP Ranges", + permissions=["nautobot_firewall_models.view_iprange"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:iprange_add", + permissions=["nautobot_firewall_models.add_iprange"], + ), + ], + ), + NavMenuItem( + link="plugins:nautobot_firewall_models:addressobject_list", + name="Address Objects", + permissions=["nautobot_firewall_models.view_addressobject"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:addressobject_add", + permissions=["nautobot_firewall_models.add_addressobject"], + ), + ], + ), + NavMenuItem( + link="plugins:nautobot_firewall_models:addressobjectgroup_list", + name="Address Object Groups", + permissions=["nautobot_firewall_models.view_addressobjectgroup"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:addressobjectgroup_add", + permissions=["nautobot_firewall_models.add_addressobjectgroup"], + ), + ], + ), + ], + ), + NavMenuGroup( + name="Service", + weight=200, + items=[ + NavMenuItem( + link="plugins:nautobot_firewall_models:applicationobject_list", + name="Applications", + permissions=["nautobot_firewall_models.view_applicationobject"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:applicationobject_add", + permissions=["nautobot_firewall_models.add_applicationobject"], + ), + ], + ), + NavMenuItem( + link="plugins:nautobot_firewall_models:applicationobjectgroup_list", + name="Application Groups", + permissions=["nautobot_firewall_models.view_applicationobjectgroup"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:applicationobjectgroup_add", + permissions=["nautobot_firewall_models.add_applicationobjectgroup"], + ), + ], + ), + NavMenuItem( + link="plugins:nautobot_firewall_models:serviceobject_list", + name="Service Objects", + permissions=["nautobot_firewall_models.view_serviceobject"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:serviceobject_add", + permissions=["nautobot_firewall_models.add_serviceobject"], + ), + ], + ), + NavMenuItem( + link="plugins:nautobot_firewall_models:serviceobjectgroup_list", + name="Service Object Groups", + permissions=["nautobot_firewall_models.view_serviceobjectgroup"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:serviceobjectgroup_add", + permissions=["nautobot_firewall_models.add_serviceobjectgroup"], + ), + ], + ), + ], + ), + NavMenuGroup( + name="User", + weight=200, + items=[ + NavMenuItem( + link="plugins:nautobot_firewall_models:userobject_list", + name="User Objects", + permissions=["nautobot_firewall_models.view_userobject"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:userobject_add", + permissions=["nautobot_firewall_models.add_userobject"], + ), + ], + ), + NavMenuItem( + link="plugins:nautobot_firewall_models:userobjectgroup_list", + name="User Object Groups", + permissions=["nautobot_firewall_models.view_userobjectgroup"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:userobjectgroup_add", + permissions=["nautobot_firewall_models.add_userobjectgroup"], + ), + ], + ), + ], + ), + NavMenuGroup( + name="Zone", + weight=200, + items=[ + NavMenuItem( + link="plugins:nautobot_firewall_models:zone_list", + name="Zones", + permissions=["nautobot_firewall_models.view_zone"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:zone_add", + permissions=["nautobot_firewall_models.add_zone"], + ), + ], + ), + ], + ), + NavMenuGroup( + name="Policy", + weight=200, + items=[ + NavMenuItem( + link="plugins:nautobot_firewall_models:policyrule_list", + name="Policy Rules", + permissions=["nautobot_firewall_models.view_policyrule"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:policyrule_add", + permissions=["nautobot_firewall_models.add_policyrule"], + ), + ], + ), + NavMenuItem( + link="plugins:nautobot_firewall_models:policy_list", + name="Policies", + permissions=["nautobot_firewall_models.view_policy"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:policy_add", + permissions=["nautobot_firewall_models.add_policy"], + ), + ], + ), + ], + ), + NavMenuGroup( + name="NAT Policy", + weight=200, + items=[ + NavMenuItem( + link="plugins:nautobot_firewall_models:natpolicyrule_list", + name="NAT Policy Rules", + permissions=["nautobot_firewall_models.view_natpolicyrule"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:natpolicyrule_add", + permissions=["nautobot_firewall_models.add_natpolicyrule"], + ), + ], + ), + NavMenuItem( + link="plugins:nautobot_firewall_models:natpolicy_list", + name="NAT Policies", + permissions=["nautobot_firewall_models.view_natpolicy"], + buttons=[ + NavMenuAddButton( + link="plugins:nautobot_firewall_models:natpolicy_add", + permissions=["nautobot_firewall_models.add_natpolicy"], + ), + ], + ), + ], + ), + NavMenuGroup( + name="Capirca", + weight=200, + items=[ + NavMenuItem( + link="plugins:nautobot_firewall_models:capircapolicy_list", + name="Capirca Policy Rules", + permissions=["nautobot_firewall_models.view_capircapolicy"], + ), + ], + ), + ], ), ) diff --git a/nautobot_firewall_models/tables.py b/nautobot_firewall_models/tables.py index b9e8744..a3ce556 100644 --- a/nautobot_firewall_models/tables.py +++ b/nautobot_firewall_models/tables.py @@ -1,38 +1,346 @@ -"""Tables for nautobot_firewall_models.""" +"""Table Views for Firewall Models.""" import django_tables2 as tables from nautobot.apps.tables import BaseTable, ButtonsColumn, ToggleColumn +from nautobot.extras.tables import StatusTableMixin from nautobot_firewall_models import models -class IPRangeTable(BaseTable): - # pylint: disable=R0903 +class IPRangeTable(StatusTableMixin, BaseTable): """Table for list view.""" pk = ToggleColumn() - name = tables.Column(linkify=True) - actions = ButtonsColumn( - models.IPRange, - # Option for modifying the default action buttons on each row: - # buttons=("changelog", "edit", "delete"), - # Option for modifying the pk for the action buttons: - pk_field="pk", - ) + start_address = tables.Column(linkify=True) + vrf = tables.LinkColumn() + actions = ButtonsColumn(models.IPRange, buttons=("edit", "delete")) class Meta(BaseTable.Meta): """Meta attributes.""" model = models.IPRange + fields = ("pk", "start_address", "end_address", "vrf", "size", "description", "status") + + +class FQDNTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.Column(linkify=True) + actions = ButtonsColumn(models.FQDN, buttons=("edit", "delete")) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.FQDN + fields = ("pk", "name", "description", "ip_addresses", "status") + + +class AddressObjectTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.Column(linkify=True) + actions = ButtonsColumn(models.AddressObject, buttons=("edit", "delete")) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.AddressObject + fields = ("pk", "name", "description", "ip_address", "ip_range", "prefix", "fqdn", "status") + + +class AddressObjectGroupTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.Column(linkify=True) + actions = ButtonsColumn(models.AddressObjectGroup, buttons=("edit", "delete")) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.AddressObjectGroup + fields = ("pk", "name", "description", "address_objects", "status") + + +class ApplicationObjectTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.Column(linkify=True) + actions = ButtonsColumn(models.ApplicationObject, buttons=("edit", "delete")) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.ApplicationObject + fields = ("pk", "name", "description", "category", "subcategory", "technology", "risk", "default_type") + + +class ApplicationObjectGroupTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.Column(linkify=True) + application_objects = tables.ManyToManyColumn(linkify_item=True) + actions = ButtonsColumn(models.ApplicationObjectGroup, buttons=("edit", "delete")) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.ApplicationObjectGroup + fields = ("pk", "name", "description", "application_objects") + + +class ServiceObjectTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.Column(linkify=True) + actions = ButtonsColumn(models.ServiceObject, buttons=("edit", "delete")) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.ServiceObject + fields = ("pk", "name", "port", "ip_protocol", "description", "status") + + +class ServiceObjectGroupTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.Column(linkify=True) + actions = ButtonsColumn(models.ServiceObjectGroup, buttons=("edit", "delete")) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.ServiceObjectGroup + fields = ("pk", "name", "description", "service_objects", "status") + + +class UserObjectTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + username = tables.Column(linkify=True) + actions = ButtonsColumn(models.UserObject, buttons=("edit", "delete")) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.UserObject + fields = ("pk", "username", "name", "status") + + +class UserObjectGroupTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.Column(linkify=True) + actions = ButtonsColumn(models.UserObjectGroup, buttons=("edit", "delete")) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.UserObjectGroup + fields = ("pk", "name", "description", "user_objects", "status") + + +class ZoneTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.Column(linkify=True) + actions = ButtonsColumn(models.Zone, buttons=("edit", "delete")) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.Zone + fields = ("pk", "name", "vrfs", "interfaces", "description", "status") + + +# TODO: refactor +class PolicyRuleTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.LinkColumn() + actions = ButtonsColumn(models.PolicyRule, buttons=("edit", "delete")) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.PolicyRule + fields = ( + # pylint: disable=duplicate-code + "pk", + "name", + "source_users", + "source_user_groups", + "source_addresses", + "source_address_groups", + "source_zone", + "source_services", + "source_service_groups", + "destination_addresses", + "destination_address_groups", + "destination_zone", + "destination_services", + "destination_service_groups", + "applications", + "application_groups", + "action", + "description", + "request_id", + "log", + "status", + ) + default_columns = ( + "pk", + "name", + "source_users", + "source_user_groups", + "source_addresses", + "source_address_groups", + "source_zone", + "source_services", + "source_service_groups", + "destination_addresses", + "destination_address_groups", + "destination_zone", + "destination_services", + "destination_service_groups", + "applications", + "application_groups", + "action", + "log", + "status", + ) + + +class PolicyTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.Column(linkify=True) + actions = ButtonsColumn(models.Policy, buttons=("edit", "delete")) + assigned_devices = tables.ManyToManyColumn(linkify_item=True) + assigned_dynamic_groups = tables.ManyToManyColumn(linkify_item=True) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.Policy + fields = ("pk", "name", "description", "policy_rules", "assigned_devices", "assigned_dynamic_groups", "status") + + +# TODO: refactor +class NATPolicyRuleTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.LinkColumn() + actions = ButtonsColumn(models.NATPolicyRule, buttons=("edit", "delete")) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.NATPolicyRule fields = ( + # pylint: disable=duplicate-code "pk", "name", + "source_zone", + "destination_zone", + "original_source_addresses", + "original_source_address_groups", + "original_source_services", + "original_source_service_groups", + "translated_source_addresses", + "translated_source_address_groups", + "translated_source_services", + "translated_source_service_groups", + "original_destination_addresses", + "original_destination_address_groups", + "original_destination_services", + "original_destination_service_groups", + "translated_destination_addresses", + "translated_destination_address_groups", + "translated_destination_services", + "translated_destination_service_groups", + "remark", + "request_id", "description", + "log", + "status", + ) + default_columns = ( + # pylint: disable=duplicate-code + "pk", + "name", + "source_zone", + "destination_zone", + "original_source_addresses", + "original_source_address_groups", + "original_source_services", + "original_source_service_groups", + "translated_source_addresses", + "translated_source_address_groups", + "translated_source_services", + "translated_source_service_groups", + "original_destination_addresses", + "original_destination_address_groups", + "original_destination_services", + "original_destination_service_groups", + "translated_destination_addresses", + "translated_destination_address_groups", + "translated_destination_services", + "translated_destination_service_groups", + "remark", + "log", + "status", ) - # Option for modifying the columns that show up in the list view by default: - # default_columns = ( - # "pk", - # "name", - # "description", - # ) + +class NATPolicyTable(StatusTableMixin, BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + name = tables.Column(linkify=True) + nat_policy_rules = tables.ManyToManyColumn(verbose_name="NAT policy rules", linkify_item=True) + actions = ButtonsColumn(models.NATPolicy, buttons=("edit", "delete")) + assigned_devices = tables.ManyToManyColumn(linkify_item=True) + assigned_dynamic_groups = tables.ManyToManyColumn(linkify_item=True) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.NATPolicy + fields = ( + "pk", + "name", + "description", + "nat_policy_rules", + "assigned_devices", + "assigned_dynamic_groups", + "status", + ) + + +class CapircaPolicyTable(BaseTable): + """Table for list view.""" + + pk = ToggleColumn() + device = tables.TemplateColumn( + template_code="""{{ record.device }} """ + ) + + class Meta(BaseTable.Meta): + """Meta attributes.""" + + model = models.CapircaPolicy + fields = ("pk", "device") diff --git a/nautobot_firewall_models/templates/nautobot_firewall_models/iprange_retrieve.html b/nautobot_firewall_models/templates/nautobot_firewall_models/iprange_retrieve.html index 33be024..3404593 100644 --- a/nautobot_firewall_models/templates/nautobot_firewall_models/iprange_retrieve.html +++ b/nautobot_firewall_models/templates/nautobot_firewall_models/iprange_retrieve.html @@ -1,24 +1,35 @@ - {% extends 'generic/object_retrieve.html' %} {% load helpers %} {% block content_left_page %}
- IPRange + IP Range
- - + + - - + + + + + + + + + + + + + + + + + +
Name - {{ object.name }} - Description{{ object.description|placeholder }}
Description - {{ object.description|placeholder }} - Start Address{{ object.start_address }}
End Address{{ object.end_address }}
Size{{ object.size }}
VRF{{ object.vrf|placeholder }}
Status{{ object.get_status_display }}
diff --git a/nautobot_firewall_models/tests/fixtures.py b/nautobot_firewall_models/tests/fixtures.py index deaa542..51b7d37 100644 --- a/nautobot_firewall_models/tests/fixtures.py +++ b/nautobot_firewall_models/tests/fixtures.py @@ -1,10 +1,439 @@ -"""Create fixtures for tests.""" +"""Create basic objects for use in test class setup.""" -from nautobot_firewall_models.models import IPRange +# ruff: noqa: F403, F405 +from django.contrib.contenttypes.models import ContentType +from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer, Platform +from nautobot.extras.models import DynamicGroup, Job, Role +from nautobot.extras.models.statuses import Status +from nautobot.ipam.models import VRF, Namespace, Prefix +from nautobot.ipam.models import IPAddress as IPAddr +from nautobot.tenancy.models import Tenant, TenantGroup +from nautobot_firewall_models.models import * # pylint: disable=unused-wildcard-import, wildcard-import -def create_iprange(): - """Fixture to create necessary number of IPRange for tests.""" - IPRange.objects.create(name="Test One") - IPRange.objects.create(name="Test Two") - IPRange.objects.create(name="Test Three") + +def create_ip_range(): + """Creates 3 IPRange objects.""" + status = Status.objects.get(name="Active") + vrf, _ = VRF.objects.get_or_create(name="random_vrf") + IPRange.objects.get_or_create(start_address="192.168.0.1", end_address="192.168.0.10", vrf=None, status=status) + IPRange.objects.get_or_create(start_address="192.168.0.1", end_address="192.168.0.10", vrf=vrf, status=status) + return IPRange.objects.get_or_create(start_address="192.168.0.11", end_address="192.168.0.20", status=status)[0] + + +def create_fqdn(): + """Creates 3 FQDN objects.""" + status = Status.objects.get(name="Active") + FQDN.objects.get_or_create(name="test.dev", status=status) + FQDN.objects.get_or_create(name="test.uat", status=status) + return FQDN.objects.get_or_create(name="test.prod", status=status)[0] + + +def create_addr_obj(): + """Creates 3 of all objects.""" + # Core Models + status = Status.objects.get(name="Active") + namespace, _ = Namespace.objects.get_or_create(name="global") + prefix, _ = Prefix.objects.get_or_create(network="10.0.0.0", prefix_length=24, namespace=namespace, status=status) + ip_address, _ = IPAddr.objects.get_or_create(address="10.0.0.1", status=status, parent=prefix) + + # Plugin Models + ip_range = create_ip_range() + fqdn = create_fqdn() + addr_obj1, _ = AddressObject.objects.get_or_create(name="printer", ip_range=ip_range, status=status) + addr_obj2, _ = AddressObject.objects.get_or_create(name="voice", ip_address=ip_address, status=status) + addr_obj3, _ = AddressObject.objects.get_or_create(name="storage", prefix=prefix, status=status) + addr_obj4, _ = AddressObject.objects.get_or_create(name="server", fqdn=fqdn, status=status) + return addr_obj1, addr_obj2, addr_obj3, addr_obj4 + + +def create_addr_group(): + """Creates 3 of all objects.""" + status = Status.objects.get(name="Active") + addr_obj1, addr_obj2, addr_obj3, addr_obj4 = create_addr_obj() + addr_grp1, _ = AddressObjectGroup.objects.get_or_create(name="addr group1", status=status) + addr_grp1.address_objects.set([addr_obj1, addr_obj2]) + addr_grp2, _ = AddressObjectGroup.objects.get_or_create(name="addr group2", status=status) + addr_grp2.address_objects.set([addr_obj3, addr_obj4]) + addr_grp3, _ = AddressObjectGroup.objects.get_or_create(name="addr group3", status=status) + addr_grp3.address_objects.set([addr_obj1, addr_obj2, addr_obj3, addr_obj4]) + return addr_grp1, addr_grp2, addr_grp3 + + +def create_svc_obj(): + """Creates 3 of all objects.""" + status = Status.objects.get(name="Active") + svc_obj1, _ = ServiceObject.objects.get_or_create(name="PGSQL", port="5432", ip_protocol="TCP", status=status) + svc_obj2, _ = ServiceObject.objects.get_or_create(name="SSH", port="22", ip_protocol="TCP", status=status) + svc_obj3, _ = ServiceObject.objects.get_or_create(name="DNS", port="53", ip_protocol="TCP", status=status) + src_svc, _ = ServiceObject.objects.get_or_create(name="Source HTTPS", port="443", ip_protocol="TCP", status=status) + return svc_obj1, svc_obj2, svc_obj3, src_svc + + +def create_svc_group(): + """Creates 3 of all objects.""" + svc_obj1, svc_obj2, svc_obj3, _ = create_svc_obj() + status = Status.objects.get(name="Active") + svc_grp1, _ = ServiceObjectGroup.objects.get_or_create(name="svc group1", status=status) + svc_grp1.service_objects.set([svc_obj1]) + svc_grp2, _ = ServiceObjectGroup.objects.get_or_create(name="svc group2", status=status) + svc_grp2.service_objects.set([svc_obj2, svc_obj3]) + svc_grp3, _ = ServiceObjectGroup.objects.get_or_create(name="svc group3", status=status) + svc_grp3.service_objects.set([svc_obj1, svc_obj2, svc_obj3]) + return svc_grp1, svc_grp2, svc_grp3 + + +def create_user_obj(): + """Creates 3 of all objects.""" + status = Status.objects.get(name="Active") + usr_obj1, _ = UserObject.objects.get_or_create(username="user1", name="Bob", status=status) + usr_obj2, _ = UserObject.objects.get_or_create(username="user2", name="Fred", status=status) + usr_obj3, _ = UserObject.objects.get_or_create(username="user3", name="Tom", status=status) + return usr_obj1, usr_obj2, usr_obj3 + + +def create_user_group(): + """Creates 3 of all objects.""" + status = Status.objects.get(name="Active") + usr_obj1, usr_obj2, usr_obj3 = create_user_obj() + usr_grp1, _ = UserObjectGroup.objects.get_or_create(name="usr group1", status=status) + usr_grp1.user_objects.set([usr_obj1]) + usr_grp2, _ = UserObjectGroup.objects.get_or_create(name="usr group2", status=status) + usr_grp2.user_objects.set([usr_obj1, usr_obj2]) + usr_grp3, _ = UserObjectGroup.objects.get_or_create(name="usr group3", status=status) + usr_grp3.user_objects.set([usr_obj1, usr_obj2, usr_obj3]) + return usr_grp1, usr_grp2, usr_grp3 + + +def create_zone(): + """Creates 3 of all objects.""" + status = Status.objects.get(name="Active") + vrf, _ = VRF.objects.get_or_create(name="global") + zone1, _ = Zone.objects.get_or_create(name="WAN", status=status) + zone1.vrfs.set([vrf]) + zone2, _ = Zone.objects.get_or_create(name="LAN", status=status) + zone3, _ = Zone.objects.get_or_create(name="DMZ", status=status) + return zone1, zone2, zone3 + + +def create_app_obj(): + """Creates 3 of all objects.""" + status = Status.objects.get(name="Active") + app1, _ = ApplicationObject.objects.get_or_create( + name="app1", + category="web", + subcategory="streaming", + default_type="443", + default_ip_protocol="TCP", + status=status, + risk=3, + description="some description", + ) + app2, _ = ApplicationObject.objects.get_or_create( + name="app2", + category="web", + subcategory="streaming", + default_type="443", + default_ip_protocol="TCP", + status=status, + risk=2, + description="some description", + ) + app3, _ = ApplicationObject.objects.get_or_create( + name="app3", + category="web", + subcategory="streaming", + default_type="443", + default_ip_protocol="TCP", + status=status, + risk=1, + description="some description", + ) + return app1, app2, app3 + + +def create_app_group(): + """Creates 3 of all objects.""" + app1, app2, app3 = create_app_obj() + status = Status.objects.get(name="Active") + app_grp1, _ = ApplicationObjectGroup.objects.get_or_create( + name="streaming", description="some description", status=status + ) + app_grp1.application_objects.set([app1]) + app_grp2, _ = ApplicationObjectGroup.objects.get_or_create( + name="gaming", description="some description", status=status + ) + app_grp2.application_objects.set([app3, app2]) + app_grp3, _ = ApplicationObjectGroup.objects.get_or_create( + name="news", description="some description", status=status + ) + app_grp3.application_objects.set([app1, app2, app3]) + return app_grp1, app_grp2, app_grp3 + + +def create_policy_rule(): # pylint: disable=too-many-locals + """Creates 3 of all objects.""" + app1, app2, app3 = create_app_obj() + app_grp1, app_grp2, app_grp3 = create_app_group() + usr_obj1, usr_obj2, usr_obj3 = create_user_obj() + usr_grp1, usr_grp2, usr_grp3 = create_user_group() + svc_obj1, svc_obj2, svc_obj3, src_svc = create_svc_obj() + svc_grp1, svc_grp2, svc_grp3 = create_svc_group() + zone1, zone2, _ = create_zone() + addr_obj1, addr_obj2, addr_obj3, addr_obj4 = create_addr_obj() + addr_grp1, addr_grp2, addr_grp3 = create_addr_group() + status = Status.objects.get(name="Active") + pol_rule1, _ = PolicyRule.objects.get_or_create( + action="deny", + log=True, + name="Policy Rule 1", + status=status, + request_id="req1", + index=10, + description="some description", + ) + pol_rule1.source_users.set([usr_obj1]) + pol_rule1.source_user_groups.set([usr_grp1]) + pol_rule1.source_addresses.set([addr_obj1]) + pol_rule1.source_address_groups.set([addr_grp1]) + pol_rule1.source_services.set([src_svc]) + pol_rule1.destination_addresses.set([addr_obj4]) + pol_rule1.destination_address_groups.set([addr_grp3]) + pol_rule1.destination_services.set([svc_obj1]) + pol_rule1.destination_service_groups.set([svc_grp1]) + pol_rule1.applications.set([app1]) + pol_rule1.application_groups.set([app_grp1]) + pol_rule2, _ = PolicyRule.objects.get_or_create( + source_zone=zone1, + destination_zone=zone2, + action="allow", + log=True, + name="Policy Rule 2", + status=status, + request_id="req2", + index=20, + description="some description", + ) + pol_rule2.source_users.set([usr_obj1, usr_obj2]) + pol_rule2.source_user_groups.set([usr_grp1, usr_grp2]) + pol_rule2.source_addresses.set([addr_obj1, addr_obj2]) + pol_rule2.source_address_groups.set([addr_grp1, addr_grp2]) + pol_rule2.destination_addresses.set([addr_obj4]) + pol_rule2.destination_address_groups.set([addr_grp3]) + pol_rule2.destination_services.set([svc_obj1, svc_obj2]) + pol_rule2.destination_service_groups.set([svc_grp1, svc_grp2]) + pol_rule2.applications.set([app2]) + pol_rule2.application_groups.set([app_grp2]) + pol_rule3, _ = PolicyRule.objects.get_or_create( + source_zone=zone1, + destination_zone=zone2, + action="drop", + log=True, + name="Policy Rule 3", + status=status, + request_id="req3", + index=30, + description="some description", + ) + pol_rule3.source_users.set([usr_obj1, usr_obj2, usr_obj3]) + pol_rule3.source_user_groups.set([usr_grp1, usr_grp2, usr_grp3]) + pol_rule3.source_addresses.set([addr_obj1, addr_obj2, addr_obj3]) + pol_rule3.source_address_groups.set([addr_grp1, addr_grp2]) + pol_rule3.destination_addresses.set([addr_obj4]) + pol_rule3.destination_address_groups.set([addr_grp3]) + pol_rule3.destination_services.set([svc_obj1, svc_obj2, svc_obj3]) + pol_rule3.destination_service_groups.set([svc_grp1, svc_grp2, svc_grp3]) + pol_rule3.applications.set([app2, app3]) + pol_rule3.application_groups.set([app_grp1, app_grp2, app_grp3]) + pol_rule4, _ = PolicyRule.objects.get_or_create( + name="END OF ACCESS LIST", action="remark", log=False, request_id="req4", index=99 + ) + pol_rule5, _ = PolicyRule.objects.get_or_create( + name="DENY ALL", action="deny", log=False, request_id="req5", index=100 + ) + return pol_rule1, pol_rule2, pol_rule3, pol_rule4, pol_rule5 + + +def create_policy(): + """Creates 3 of all objects.""" + pol_rule1, pol_rule2, pol_rule3, pol_rule4, pol_rule5 = create_policy_rule() + status = Status.objects.get(name="Active") + tenant_group, _ = TenantGroup.objects.get_or_create(name="ABC Holding Corp") + tenant1, _ = Tenant.objects.get_or_create(name="ABC LLC", tenant_group=tenant_group) + tenant2, _ = Tenant.objects.get_or_create(name="XYZ LLC") + pol1, _ = Policy.objects.get_or_create(name="Policy 1", status=status) + pol1.policy_rules.set([pol_rule1]) + pol2, _ = Policy.objects.get_or_create(name="Policy 2", status=status, tenant=tenant2) + pol2.policy_rules.set([pol_rule1, pol_rule2]) + pol3, _ = Policy.objects.get_or_create(name="Policy 3", status=status, tenant=tenant1) + pol3.policy_rules.set([pol_rule1, pol_rule2, pol_rule3, pol_rule4, pol_rule5]) + return pol1, pol2, pol3 + + +def create_natpolicy_rule(): # pylint: disable=too-many-locals + """Creates 3 of all objects.""" + status = Status.objects.get(name="Active") + namespace, _ = Namespace.objects.get_or_create(name="global") + addr_obj1, addr_obj2, addr_obj3, addr_obj4 = create_addr_obj() + # Nat policies + nat_orig_dest_service, _ = ServiceObject.objects.get_or_create( + name="HTTP", port="80", ip_protocol="TCP", status=status + ) + nat_trans_dest_service, _ = ServiceObject.objects.get_or_create( + name="HTTP (alt)", port="8080", ip_protocol="TCP", status=status + ) + original_source_prefix, _ = Prefix.objects.get_or_create( + network="10.100.0.0", prefix_length=24, status=status, namespace=namespace + ) + original_source, _ = AddressObject.objects.get_or_create(name="nat-original-source", prefix=original_source_prefix) + translated_source_prefix, _ = Prefix.objects.get_or_create( + network="10.200.0.0", prefix_length=24, status=status, namespace=namespace + ) + translated_source, _ = AddressObject.objects.get_or_create( + name="nat-translated-source", prefix=translated_source_prefix + ) + destination_prefix, _ = Prefix.objects.get_or_create( + network="192.168.0.0", prefix_length=24, status=status, namespace=namespace + ) + destination, _ = AddressObject.objects.get_or_create(name="nat-destination", prefix=destination_prefix) + nat_policy_rule_1_1, _ = NATPolicyRule.objects.get_or_create( + name="NAT Policy Rule 1.1", log=True, request_id="req1" + ) + nat_policy_rule_1_1.original_source_addresses.add(original_source) + nat_policy_rule_1_1.translated_source_addresses.add(translated_source) + nat_policy_rule_1_1.original_destination_addresses.add(destination) + nat_policy_rule_1_1.translated_destination_addresses.add(destination) + nat_policy_rule_1_1.original_destination_services.add(nat_orig_dest_service) + nat_policy_rule_1_1.translated_destination_services.add(nat_trans_dest_service) + + nat_policy_rule_1_2, _ = NATPolicyRule.objects.get_or_create( + name="END OF NAT POLICY", request_id="req2", remark=True, log=True + ) + + nat_policy_rule_2_1, _ = NATPolicyRule.objects.get_or_create( + name="NAT Policy Rule 2.1", log=True, request_id="req3" + ) + nat_policy_rule_2_1.original_source_addresses.set([addr_obj1, addr_obj2]) + nat_policy_rule_2_1.translated_source_addresses.add(translated_source) + nat_policy_rule_2_1.original_destination_addresses.add(destination) + nat_policy_rule_2_1.original_destination_services.add(nat_orig_dest_service) + + nat_policy_rule_3_1, _ = NATPolicyRule.objects.get_or_create( + name="NAT Policy Rule 3.1", log=True, request_id="req4" + ) + nat_policy_rule_3_1.original_source_addresses.set([addr_obj3, addr_obj4]) + nat_policy_rule_3_1.translated_source_addresses.add(translated_source) + nat_policy_rule_3_1.original_destination_addresses.add(destination) + nat_policy_rule_3_1.original_destination_services.add(nat_orig_dest_service) + return nat_policy_rule_1_1, nat_policy_rule_1_2, nat_policy_rule_2_1, nat_policy_rule_3_1 + + +def create_natpolicy(): + """Creates 3 of all objects.""" + nat_policy_rule_1_1, nat_policy_rule_1_2, nat_policy_rule_2_1, nat_policy_rule_3_1 = create_natpolicy_rule() + nat_policy_1, _ = NATPolicy.objects.get_or_create(name="NAT Policy 1") + nat_policy_2, _ = NATPolicy.objects.get_or_create(name="NAT Policy 2") + nat_policy_3, _ = NATPolicy.objects.get_or_create(name="NAT Policy 3") + nat_policy_1.nat_policy_rules.add(nat_policy_rule_1_1) + nat_policy_1.nat_policy_rules.add(nat_policy_rule_1_2) + nat_policy_2.nat_policy_rules.add(nat_policy_rule_2_1) + nat_policy_2.nat_policy_rules.add(nat_policy_rule_3_1) + return nat_policy_1, nat_policy_2, nat_policy_3 + + +def assign_policies(): # pylint: disable=too-many-locals + """Creates 3 of all objects.""" + status = Status.objects.get(name="Active") + nat_policy_1, nat_policy_2, nat_policy_3 = create_natpolicy() + pol1, pol2, pol3 = create_policy() + # Mapping policies to devices + loc_type, _ = LocationType.objects.get_or_create(name="site") + site1, _ = Location.objects.get_or_create(name="DFW02", location_type=loc_type, status=status) + site2, _ = Location.objects.get_or_create(name="HOU02", location_type=loc_type, status=status) + jun_manufacturer, _ = Manufacturer.objects.get_or_create(name="Juniper") + jun_platform, _ = Platform.objects.get_or_create(name="Juniper", network_driver="srx") + jun_dev_type, _ = DeviceType.objects.get_or_create(manufacturer=jun_manufacturer, model="SRX300") + palo_manufacturer, _ = Manufacturer.objects.get_or_create(name="Palo Alto") + palo_platform, _ = Platform.objects.get_or_create(name="Palo Alto", network_driver="paloalto") + palo_dev_type, _ = DeviceType.objects.get_or_create(manufacturer=palo_manufacturer, model="PA-3020") + dev_role, _ = Role.objects.get_or_create(name="WAN") + dev_role.content_types.add(ContentType.objects.get_for_model(Device)) + dev1, _ = Device.objects.get_or_create( + name="DFW02-WAN00", + role=dev_role, + device_type=jun_dev_type, + location=site1, + status=status, + platform=jun_platform, + ) + Device.objects.get_or_create( + name="DFW02-WAN01", + role=dev_role, + device_type=jun_dev_type, + location=site1, + status=status, + platform=jun_platform, + ) + dev2, _ = Device.objects.get_or_create( + name="HOU02-WAN00", + role=dev_role, + device_type=palo_dev_type, + location=site2, + status=status, + platform=palo_platform, + ) + dynamic_group, _ = DynamicGroup.objects.get_or_create( + name="North Texas", content_type=ContentType.objects.get_for_model(Device) + ) + dynamic_group.filter = {"location": ["DFW02"]} + dynamic_group.validated_save() + PolicyDeviceM2M.objects.get_or_create(policy=pol1, device=dev1, weight=150) + PolicyDeviceM2M.objects.get_or_create(policy=pol2, device=dev1, weight=200) + PolicyDeviceM2M.objects.get_or_create(policy=pol1, device=dev2) + PolicyDynamicGroupM2M.objects.get_or_create(policy=pol3, dynamic_group=dynamic_group, weight=1000) + NATPolicyDeviceM2M.objects.get_or_create(nat_policy=nat_policy_1, device=dev1, weight=150) + NATPolicyDeviceM2M.objects.get_or_create(nat_policy=nat_policy_2, device=dev1, weight=200) + NATPolicyDeviceM2M.objects.get_or_create(nat_policy=nat_policy_1, device=dev2) + NATPolicyDynamicGroupM2M.objects.get_or_create(nat_policy=nat_policy_3, dynamic_group=dynamic_group, weight=1000) + + +def create_capirca_env(): + """Create objects that are Capirca Ready.""" # pylint: disable=too-many-locals, too-many-statements + assign_policies() + namespace, _ = Namespace.objects.get_or_create(name="global") + status = Status.objects.get(name="Active") + zoneall, _ = Zone.objects.get_or_create(name="all", status=status) + + pol_rule1 = PolicyRule.objects.get(name="Policy Rule 1") + pol_rule1.source_zone = Zone.objects.get(name="DMZ") + pol_rule1.destination_zone = Zone.objects.get(name="WAN") + pol_rule1.validated_save() + + pol_rule4 = PolicyRule.objects.get(name="END OF ACCESS LIST") + pol_rule4.source_zone = zoneall + pol_rule4.destination_zone = zoneall + pol_rule4.validated_save() + + pol_rule5 = PolicyRule.objects.get(name="DENY ALL") + pol_rule5.source_zone = zoneall + pol_rule5.destination_zone = zoneall + pol_rule5.validated_save() + + ip_address, _ = IPAddr.objects.get_or_create( + address="10.0.0.100", status=status, parent=Prefix.objects.get(network="10.0.0.0", namespace=namespace) + ) + prefix, _ = Prefix.objects.get_or_create(network="10.1.0.0", prefix_length=24, status=status, namespace=namespace) + + addr_obj1 = AddressObject.objects.get(name="printer") + addr_obj1.ip_range = None + addr_obj1.ip_address = ip_address + addr_obj1.validated_save() + + addr_obj4 = AddressObject.objects.get(name="server") + addr_obj4.fqdn = None + addr_obj4.prefix = prefix + addr_obj4.validated_save() + + job = Job.objects.get(name="Generate FW Config via Capirca.") + job.enabled = True + job.validated_save() diff --git a/nautobot_firewall_models/tests/test_api_views.py b/nautobot_firewall_models/tests/test_api_views.py deleted file mode 100644 index 81a1a6d..0000000 --- a/nautobot_firewall_models/tests/test_api_views.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Unit tests for nautobot_firewall_models.""" - -from nautobot.apps.testing import APIViewTestCases - -from nautobot_firewall_models import models -from nautobot_firewall_models.tests import fixtures - - -class IPRangeAPIViewTest(APIViewTestCases.APIViewTestCase): - # pylint: disable=too-many-ancestors - """Test the API viewsets for IPRange.""" - - model = models.IPRange - create_data = [ - { - "name": "Test Model 1", - "description": "test description", - }, - { - "name": "Test Model 2", - }, - ] - bulk_update_data = {"description": "Test Bulk Update"} - - @classmethod - def setUpTestData(cls): - fixtures.create_iprange() diff --git a/nautobot_firewall_models/tests/test_filter_iprange.py b/nautobot_firewall_models/tests/test_filter_iprange.py deleted file mode 100644 index 3a4519c..0000000 --- a/nautobot_firewall_models/tests/test_filter_iprange.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Test IPRange Filter.""" - -from django.test import TestCase - -from nautobot_firewall_models import filters, models -from nautobot_firewall_models.tests import fixtures - - -class IPRangeFilterTestCase(TestCase): - """IPRange Filter Test Case.""" - - queryset = models.IPRange.objects.all() - filterset = filters.IPRangeFilterSet - - @classmethod - def setUpTestData(cls): - """Setup test data for IPRange Model.""" - fixtures.create_iprange() - - def test_q_search_name(self): - """Test using Q search with name of IPRange.""" - params = {"q": "Test One"} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) - - def test_q_invalid(self): - """Test using invalid Q search for IPRange.""" - params = {"q": "test-five"} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0) diff --git a/nautobot_firewall_models/tests/test_form_iprange.py b/nautobot_firewall_models/tests/test_form_iprange.py deleted file mode 100644 index e0bcdd8..0000000 --- a/nautobot_firewall_models/tests/test_form_iprange.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Test iprange forms.""" - -from django.test import TestCase - -from nautobot_firewall_models import forms - - -class IPRangeTest(TestCase): - """Test IPRange forms.""" - - def test_specifying_all_fields_success(self): - form = forms.IPRangeForm( - data={ - "name": "Development", - "description": "Development Testing", - } - ) - self.assertTrue(form.is_valid()) - self.assertTrue(form.save()) - - def test_specifying_only_required_success(self): - form = forms.IPRangeForm( - data={ - "name": "Development", - } - ) - self.assertTrue(form.is_valid()) - self.assertTrue(form.save()) - - def test_validate_name_iprange_is_required(self): - form = forms.IPRangeForm(data={"description": "Development Testing"}) - self.assertFalse(form.is_valid()) - self.assertIn("This field is required.", form.errors["name"]) diff --git a/nautobot_firewall_models/tests/test_model_iprange.py b/nautobot_firewall_models/tests/test_model_iprange.py deleted file mode 100644 index 9fe39b1..0000000 --- a/nautobot_firewall_models/tests/test_model_iprange.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Test IPRange.""" - -from django.test import TestCase - -from nautobot_firewall_models import models - - -class TestIPRange(TestCase): - """Test IPRange.""" - - def test_create_iprange_only_required(self): - """Create with only required fields, and validate null description and __str__.""" - iprange = models.IPRange.objects.create(name="Development") - self.assertEqual(iprange.name, "Development") - self.assertEqual(iprange.description, "") - self.assertEqual(str(iprange), "Development") - - def test_create_iprange_all_fields_success(self): - """Create IPRange with all fields.""" - iprange = models.IPRange.objects.create(name="Development", description="Development Test") - self.assertEqual(iprange.name, "Development") - self.assertEqual(iprange.description, "Development Test") diff --git a/nautobot_firewall_models/tests/test_views.py b/nautobot_firewall_models/tests/test_views.py deleted file mode 100644 index 55f78aa..0000000 --- a/nautobot_firewall_models/tests/test_views.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Unit tests for views.""" - -from nautobot.apps.testing import ViewTestCases - -from nautobot_firewall_models import models -from nautobot_firewall_models.tests import fixtures - - -class IPRangeViewTest(ViewTestCases.PrimaryObjectViewTestCase): - # pylint: disable=too-many-ancestors - """Test the IPRange views.""" - - model = models.IPRange - bulk_edit_data = {"description": "Bulk edit views"} - form_data = { - "name": "Test 1", - "description": "Initial model", - } - csv_data = ( - "name", - "Test csv1", - "Test csv2", - "Test csv3", - ) - - @classmethod - def setUpTestData(cls): - fixtures.create_iprange() diff --git a/nautobot_firewall_models/urls.py b/nautobot_firewall_models/urls.py index 683e1a5..5a4ec77 100644 --- a/nautobot_firewall_models/urls.py +++ b/nautobot_firewall_models/urls.py @@ -5,14 +5,26 @@ from django.views.generic import RedirectView from nautobot.apps.urls import NautobotUIViewSetRouter - -from nautobot_firewall_models import views - +from nautobot_firewall_models import viewsets router = NautobotUIViewSetRouter() - -router.register("iprange", views.IPRangeUIViewSet) - +router.register("address-object", viewsets.AddressObjectUIViewSet) +router.register("address-object-group", viewsets.AddressObjectGroupUIViewSet) +router.register("application-object", viewsets.ApplicationObjectUIViewSet) +router.register("application-object-group", viewsets.ApplicationObjectGroupUIViewSet) +router.register("capirca-policy", viewsets.CapircaPolicyUIViewSet) +router.register("capirca-policy-device", viewsets.CapircaPolicyDeviceUIViewSet, basename="capircapolicy_devicedetail") +router.register("fqdn", viewsets.FQDNUIViewSet) +router.register("ip-range", viewsets.IPRangeUIViewSet) +router.register("nat-policy", viewsets.NATPolicyUIViewSet) +router.register("nat-policy-rule", viewsets.NATPolicyRuleUIViewSet) +router.register("policy", viewsets.PolicyUIViewSet) +router.register("policy-rule", viewsets.PolicyRuleUIViewSet) +router.register("service-object", viewsets.ServiceObjectUIViewSet) +router.register("service-object-group", viewsets.ServiceObjectGroupUIViewSet) +router.register("user-object", viewsets.UserObjectUIViewSet) +router.register("user-object-group", viewsets.UserObjectGroupUIViewSet) +router.register("zone", viewsets.ZoneUIViewSet) urlpatterns = [ path("docs/", RedirectView.as_view(url=static("nautobot_firewall_models/docs/index.html")), name="docs"), diff --git a/nautobot_firewall_models/views.py b/nautobot_firewall_models/views.py deleted file mode 100644 index 1a2b679..0000000 --- a/nautobot_firewall_models/views.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Views for nautobot_firewall_models.""" - -from nautobot.apps.views import NautobotUIViewSet - -from nautobot_firewall_models import filters, forms, models, tables -from nautobot_firewall_models.api import serializers - - -class IPRangeUIViewSet(NautobotUIViewSet): - """ViewSet for IPRange views.""" - - bulk_update_form_class = forms.IPRangeBulkEditForm - filterset_class = filters.IPRangeFilterSet - filterset_form_class = forms.IPRangeFilterForm - form_class = forms.IPRangeForm - lookup_field = "pk" - queryset = models.IPRange.objects.all() - serializer_class = serializers.IPRangeSerializer - table_class = tables.IPRangeTable diff --git a/poetry.lock b/poetry.lock index d7321b2..19c6ed0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,15 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "absl-py" +version = "2.1.0" +description = "Abseil Python Common Libraries, see https://github.com/abseil/abseil-py." +optional = false +python-versions = ">=3.7" +files = [ + {file = "absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff"}, + {file = "absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308"}, +] [[package]] name = "amqp" @@ -217,6 +228,24 @@ files = [ {file = "billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f"}, ] +[[package]] +name = "capirca" +version = "2.0.9" +description = "Capirca" +optional = false +python-versions = ">=3.6" +files = [ + {file = "capirca-2.0.9-py3-none-any.whl", hash = "sha256:cf333ac9315f0d61890710f412f6b113950095ff71580530887b48b267fe2a6f"}, + {file = "capirca-2.0.9.tar.gz", hash = "sha256:48e33e5d06f3a877ff49fe7f3bea94757f8e421d3e2112a7bdc2d02c257ac987"}, +] + +[package.dependencies] +absl-py = "*" +mock = "*" +ply = "*" +PyYAML = "*" +six = "*" + [[package]] name = "celery" version = "5.3.6" @@ -1984,6 +2013,22 @@ files = [ griffe = ">=0.49" mkdocstrings = ">=0.25" +[[package]] +name = "mock" +version = "5.1.0" +description = "Rolling backport of unittest.mock for all Pythons" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mock-5.1.0-py3-none-any.whl", hash = "sha256:18c694e5ae8a208cdb3d2c20a993ca1a7b0efa258c247a1e565150f477f83744"}, + {file = "mock-5.1.0.tar.gz", hash = "sha256:5e96aad5ccda4718e0a229ed94b2024df75cc2d55575ba5762d31f5767b8767d"}, +] + +[package.extras] +build = ["blurb", "twine", "wheel"] +docs = ["sphinx"] +test = ["pytest", "pytest-cov"] + [[package]] name = "nautobot" version = "2.3.16" @@ -2320,6 +2365,17 @@ docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-a test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.11.2)"] +[[package]] +name = "ply" +version = "3.11" +description = "Python Lex & Yacc" +optional = false +python-versions = "*" +files = [ + {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, + {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, +] + [[package]] name = "prometheus-client" version = "0.20.0" @@ -2418,7 +2474,6 @@ files = [ {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, - {file = "psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, @@ -3706,4 +3761,4 @@ all = [] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.13" -content-hash = "f2041fa5a92502d80e47c6a6e762583dceabb072a9c36f52abed52a7ef2da478" +content-hash = "929366f2abf5e7f3a78713f5a04f12be8f5766191460a0ba35c40bf224d433c0" From 0c7d497b58fa70342d27754ed66fe9c533851f69 Mon Sep 17 00:00:00 2001 From: michalis1 Date: Wed, 15 Jan 2025 19:34:36 +0200 Subject: [PATCH 5/8] remove filter_address method from IPRangeFilterSet --- nautobot_firewall_models/filters.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/nautobot_firewall_models/filters.py b/nautobot_firewall_models/filters.py index 273ff0f..b12ea7a 100644 --- a/nautobot_firewall_models/filters.py +++ b/nautobot_firewall_models/filters.py @@ -2,7 +2,6 @@ import django_filters from django.contrib.contenttypes.fields import GenericRelation -from django.core.exceptions import ValidationError from nautobot.apps.filters import ( MultiValueCharFilter, NaturalKeyOrPKMultipleChoiceFilter, @@ -30,11 +29,9 @@ class IPRangeFilterSet(BaseFilterSet, NautobotFilterSet): # pylint: disable=too """Filter for IPRange.""" start_address = MultiValueCharFilter( - method="filter_address", label="Address", ) end_address = MultiValueCharFilter( - method="filter_address", label="Address", ) @@ -45,13 +42,6 @@ class Meta: fields = [i.name for i in model._meta.get_fields() if not isinstance(i, GenericRelation)] - def filter_address(self, queryset, name, value): # pylint: disable=unused-argument - """Filter method for start & end addresses.""" - try: - return queryset.net_in(value) - except ValidationError: - return queryset.none() - class FQDNFilterSet(BaseFilterSet, NautobotFilterSet): """Filter for FQDN.""" From 48ce77fe1bd85db9ee8e82ae7b7af97c30fe47de Mon Sep 17 00:00:00 2001 From: michalis1 Date: Wed, 15 Jan 2025 19:48:12 +0200 Subject: [PATCH 6/8] remove filter_address method from IPRangeFilterSet --- changes/269.removed | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/269.removed diff --git a/changes/269.removed b/changes/269.removed new file mode 100644 index 0000000..d9da7ce --- /dev/null +++ b/changes/269.removed @@ -0,0 +1 @@ +Removed the filter_address method from IPRangeFilterSet to resolve issues with searching for IPRange objects through the GUI and API. \ No newline at end of file From 05be2ad7dccb664992a9d6bd6c860873e01eb617 Mon Sep 17 00:00:00 2001 From: Justin Drew <2396364+jdrew82@users.noreply.github.com> Date: Thu, 16 Jan 2025 08:55:06 -0600 Subject: [PATCH 7/8] build: Bump to 2.2.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cb5c0fa..5b38aa6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nautobot-firewall-models" -version = "2.2.1a0" +version = "2.2.1" description = "Nautobot app to model firewall objects." authors = ["Network to Code, LLC "] license = "Apache-2.0" From b49f2defee9e05b1f256f9ff54c608a7da820e18 Mon Sep 17 00:00:00 2001 From: Justin Drew <2396364+jdrew82@users.noreply.github.com> Date: Thu, 16 Jan 2025 08:55:20 -0600 Subject: [PATCH 8/8] =?UTF-8?q?docs:=20=F0=9F=93=9D=20Update=20release=20n?= =?UTF-8?q?otes=20for=202.2.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/admin/release_notes/version_2.2.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/admin/release_notes/version_2.2.md b/docs/admin/release_notes/version_2.2.md index e364209..8c5f1d8 100644 --- a/docs/admin/release_notes/version_2.2.md +++ b/docs/admin/release_notes/version_2.2.md @@ -26,3 +26,14 @@ This release adds support for Python 3.12. ### Housekeeping - [#281](https://github.com/nautobot/nautobot-app-firewall-models/issues/281) - Changed model_class_name in .cookiecutter.json to a valid model to help with drift management. + + +## [v2.2.1 (2025-01-16)](https://github.com/nautobot/nautobot-app-firewall-models/releases/tag/v2.2.1) + +### Removed + +- [#269](https://github.com/nautobot/nautobot-app-firewall-models/issues/269) - Removed the filter_address method from IPRangeFilterSet to resolve issues with searching for IPRange objects through the GUI and API. + +### Housekeeping + +- [#1](https://github.com/nautobot/nautobot-app-firewall-models/issues/1) - Rebaked from the cookie `nautobot-app-v2.4.1`.