diff --git a/README.md b/README.md index 9d0ae25fc..794b1363e 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ The Nautobot SSoT app builds atop the [DiffSync](https://github.com/networktocod This Nautobot application framework includes the following integrations: - Cisco ACI +- Bootstrap +- Citrix ADM - Arista CloudVision - Device42 - Cisco DNA Center diff --git a/changes/599.added b/changes/599.added new file mode 100644 index 000000000..c497bf5e6 --- /dev/null +++ b/changes/599.added @@ -0,0 +1 @@ +Added Citrix ADM integration. \ No newline at end of file diff --git a/changes/599.fixed b/changes/599.fixed new file mode 100644 index 000000000..235edad4b --- /dev/null +++ b/changes/599.fixed @@ -0,0 +1 @@ +Fixed Bootstrap signals that are using create_or_update_custom_field() to pass apps. This was done to correct bug causing Nautobot to crash during startup. \ No newline at end of file diff --git a/changes/599.housekeeping b/changes/599.housekeeping new file mode 100644 index 000000000..545b24b0d --- /dev/null +++ b/changes/599.housekeeping @@ -0,0 +1 @@ +Consolidated repeat function, parse_hostname_for_role(), from DNA Center and Citrix integrations as SSoT utility function. \ No newline at end of file diff --git a/development/creds.example.env b/development/creds.example.env index 62e9e3e20..49ec37daa 100644 --- a/development/creds.example.env +++ b/development/creds.example.env @@ -31,6 +31,9 @@ NAUTOBOT_ARISTACV_CVP_TOKEN="changeme" NAUTOBOT_SSOT_DEVICE42_PASSWORD="changeme" +NAUTOBOT_SSOT_CITRIX_ADM_USERNAME="admin" +NAUTOBOT_SSOT_CITRIX_ADM_PASSWORD="changeme" + NAUTOBOT_SSOT_INFOBLOX_PASSWORD="changeme" # ACI Credentials. Append friendly name to the end to identify each APIC. diff --git a/development/development.env b/development/development.env index b9d4f462f..a0712c105 100644 --- a/development/development.env +++ b/development/development.env @@ -84,6 +84,9 @@ NAUTOBOT_DNAC_SSOT_DNA_CENTER_IMPORT_MERAKIS="False" NAUTOBOT_DNAC_SSOT_DNA_CENTER_UPDATE_LOCATIONS="True" NAUTOBOT_DNAC_SSOT_DNA_CENTER_SHOW_FAILURES="True" +NAUTOBOT_SSOT_ENABLE_CITRIX_ADM="False" +NAUTOBOT_SSOT_CITRIX_ADM_UPDATE_SITES="True" + NAUTOBOT_SSOT_ENABLE_INFOBLOX="False" NAUTOBOT_SSOT_INFOBLOX_DEFAULT_STATUS="Active" NAUTOBOT_SSOT_INFOBLOX_ENABLE_SYNC_TO_INFOBLOX="True" diff --git a/development/nautobot_config.py b/development/nautobot_config.py index 7210ce596..3072ee16b 100644 --- a/development/nautobot_config.py +++ b/development/nautobot_config.py @@ -219,9 +219,11 @@ "vrf": True, "prefix": True, }, + "citrix_adm_update_sites": is_truthy(os.getenv("NAUTOBOT_SSOT_CITRIX_ADM_UPDATE_SITES", "true")), "enable_aci": is_truthy(os.getenv("NAUTOBOT_SSOT_ENABLE_ACI")), "enable_aristacv": is_truthy(os.getenv("NAUTOBOT_SSOT_ENABLE_ARISTACV")), "enable_bootstrap": is_truthy(os.getenv("NAUTOBOT_SSOT_ENABLE_BOOTSTRAP", "false")), + "enable_citrix_adm": is_truthy(os.getenv("NAUTOBOT_SSOT_ENABLE_CITRIX_ADM")), "enable_device42": is_truthy(os.getenv("NAUTOBOT_SSOT_ENABLE_DEVICE42")), "enable_dna_center": is_truthy(os.getenv("NAUTOBOT_SSOT_ENABLE_DNA_CENTER")), "enable_infoblox": is_truthy(os.getenv("NAUTOBOT_SSOT_ENABLE_INFOBLOX")), diff --git a/docs/admin/integrations/citrix_adm_setup.md b/docs/admin/integrations/citrix_adm_setup.md new file mode 100644 index 000000000..9612732cc --- /dev/null +++ b/docs/admin/integrations/citrix_adm_setup.md @@ -0,0 +1,52 @@ +# Citrix ADM Integration Setup + +This guide will walk you through the steps to set up Citrix ADM integration with the `nautobot_ssot` app. + +## Prerequisites + +Before configuring the integration, please ensure, that the `nautobot-ssot` app was [installed with the Citrix ADM integration extra dependencies](../install.md#install-guide). + +```shell +pip install nautobot-ssot[citrix-adm] +``` + +## Configuration + +Access to your Citrix ADM instance is defined using the [ExternalIntegration](https://docs.nautobot.com/projects/core/en/stable/user-guide/platform-functionality/externalintegration/) model which allows you to utilize this integration with multiple instances concurrently. Please bear in mind that it will synchronize all data 1:1 with the specified instance to match exactly, meaning it will delete data missing from an instance. Each ExternalIntegration must specify a SecretsGroup with Secrets that contain the Citrix ADM Username and Password to authenticate with. You can find Secrets and SecretsGroups available under the Secrets menu. + +![Citrix ADM Username](../../images/citrix_adm_username.png) + +![Citrix ADM Password](../../images/citrix_adm_password.png) + +![Citrix ADM SecretsGroup](../../images/citrix_adm_secretsgroup.png) + +The Secrets Group linked to the Citrix ADM ExternalIntegration must contain Secrets defined as per the below: + +| Access Type | Secret Type | +| ----------- | ----------- | +| HTTP(S) | Username | +| HTTP(S) | Password | + +Once the SecretsGroup is created you'll need to create the ExternalIntegration. You'll find this under the Extensibility menu. + +![Citrix ADM ExternalIntegration](../../images/citrix_adm_externalintegration.png) + +> The only required portions are the Name, Remote URL, Verify SSL, and Secrets Group. + +When utilizing multiple SSoT integrations that contain differing Locations you might want to ensure that your existing Locations aren't updated by another integration. You can control whether these updates are made with the `citrix_adm_update_sites` setting in your `nautobot_config.py` file. + +| Configuration Variable | Type | Usage | Default | +| --------------------------------------------------- | ------- | ---------------------------------------------------------- | -------------------- | +| citrix_adm_update_sites | boolean | Whether to update loaded Datacenter Locations. | True | + +Below is an example snippet from `nautobot_config.py` that demonstrates how to enable and configure the Citrix ADM integration: + +```python +PLUGINS_CONFIG = { + "nautobot_ssot": { + "enable_citrix_adm": is_truthy(os.getenv("NAUTOBOT_SSOT_ENABLE_CITRIX_ADM", "true")), + "citrix_adm_update_sites": os.getenv("NAUTOBOT_SSOT_CITRIX_ADM_UPDATE_SITES", "true"), + } +``` + +Once the integration has been enabled you can find instructions on using it in the [Usage instructions](../../user/integrations/citrix_adm.md#usage). diff --git a/docs/admin/integrations/index.md b/docs/admin/integrations/index.md index e8c23e277..597394a74 100644 --- a/docs/admin/integrations/index.md +++ b/docs/admin/integrations/index.md @@ -4,6 +4,7 @@ This Nautobot app supports the following integrations: - [Cisco ACI](./aci_setup.md) - [Bootstrap](./bootstrap_setup.md) +- [Citrix ADM](./citrix_adm_setup.md) - [Arista CloudVision](./aristacv_setup.md) - [Device42](./device42_setup.md) - [Cisco DNA Center](./dna_center_setup.md) diff --git a/docs/images/citrix_adm_dashboard.png b/docs/images/citrix_adm_dashboard.png new file mode 100644 index 000000000..bd53c24b4 Binary files /dev/null and b/docs/images/citrix_adm_dashboard.png differ diff --git a/docs/images/citrix_adm_detail-view.png b/docs/images/citrix_adm_detail-view.png new file mode 100644 index 000000000..22b117bb8 Binary files /dev/null and b/docs/images/citrix_adm_detail-view.png differ diff --git a/docs/images/citrix_adm_enabled_job.png b/docs/images/citrix_adm_enabled_job.png new file mode 100644 index 000000000..bb8baf608 Binary files /dev/null and b/docs/images/citrix_adm_enabled_job.png differ diff --git a/docs/images/citrix_adm_externalintegration.png b/docs/images/citrix_adm_externalintegration.png new file mode 100644 index 000000000..a58015b51 Binary files /dev/null and b/docs/images/citrix_adm_externalintegration.png differ diff --git a/docs/images/citrix_adm_job_form.png b/docs/images/citrix_adm_job_form.png new file mode 100644 index 000000000..8ac9fa244 Binary files /dev/null and b/docs/images/citrix_adm_job_form.png differ diff --git a/docs/images/citrix_adm_job_list.png b/docs/images/citrix_adm_job_list.png new file mode 100644 index 000000000..017ee76a7 Binary files /dev/null and b/docs/images/citrix_adm_job_list.png differ diff --git a/docs/images/citrix_adm_job_settings.png b/docs/images/citrix_adm_job_settings.png new file mode 100644 index 000000000..7569a0d0a Binary files /dev/null and b/docs/images/citrix_adm_job_settings.png differ diff --git a/docs/images/citrix_adm_password.png b/docs/images/citrix_adm_password.png new file mode 100644 index 000000000..0dafe76b5 Binary files /dev/null and b/docs/images/citrix_adm_password.png differ diff --git a/docs/images/citrix_adm_secretsgroup.png b/docs/images/citrix_adm_secretsgroup.png new file mode 100644 index 000000000..c0fc6f685 Binary files /dev/null and b/docs/images/citrix_adm_secretsgroup.png differ diff --git a/docs/images/citrix_adm_username.png b/docs/images/citrix_adm_username.png new file mode 100644 index 000000000..516d142cc Binary files /dev/null and b/docs/images/citrix_adm_username.png differ diff --git a/docs/user/integrations/citrix_adm.md b/docs/user/integrations/citrix_adm.md new file mode 100644 index 000000000..dd7077553 --- /dev/null +++ b/docs/user/integrations/citrix_adm.md @@ -0,0 +1,61 @@ +# Citrix ADM SSoT Integration + +The Citrix ADM SSoT integration is built as part of the [Nautobot Single Source of Truth (SSoT)](https://github.com/nautobot/nautobot-app-ssot) app. The SSoT app enables Nautobot to be the aggregation point for data coming from multiple systems of record (SoR). + +From Citrix ADM into Nautobot, it synchronizes the following objects: + +| Citrix ADM | Nautobot | +| ----------------------- | ---------------------------- | +| Datacenter | Location* | +| Devices | Devices | +| Hardwares | DeviceTypes | +| OSVersions | SoftwareVersions | +| Ports | Interfaces | +| Prefixes | Prefixes | +| IP Addresses | IP Addresses | + +## Usage + +Once the app is installed and configured, you will be able to perform an inventory ingestion from an individual or multiple Citrix ADM instances into Nautobot. From the Nautobot SSoT Dashboard view (`/plugins/ssot/`), Citrix ADM will show as a Data Source. + +![Dashboard View](../../images/citrix_adm_dashboard.png) + +From the Dashboard, you can also view more information about the App by clicking on the `Citrix ADM to Nautobot` link and see the Detail view. This view will show the mappings of Citrix ADM objects to Nautobot objects, the sync history, and other configuration details for the App: + +![Detail View](../../images/citrix_adm_detail-view.png) + +In order to utilize this integration you must first enable the Job. You can find the available installed Jobs under Jobs -> Jobs: + +![Job List](../../images/citrix_adm_job_list.png) + +To enable the Job you must click on the orange pencil icon to the right of the `Citrix ADM to Nautobot` Job. You will be presented with the settings for the Job as shown below: + +![Job Settings](../../images/citrix_adm_job_settings.png) + +You'll need to check the `Enabled` checkbox and then the `Update` button at the bottom of the page. You will then see that the play button next to the Job changes to blue and becomes functional, linking to the Job form. + +![Enabled Job](../../images/citrix_adm_enabled_job.png) + +Once the Job is enabled, you'll need to manually create a few objects in Nautobot to use with the Job. First, you'll need to create the Secrets, SecretsGroup, and ExternalIntegration as detailed in the [Citrix ADM Configuration](../../admin/integrations/citrix_adm_setup.md#configuration) instructions. + +> You can utilize multiple Citrix ADM Controllers with this integration as long as you specify a unique Tenant per Controller. The failure to use differing Tenants will have the Devices, Prefixes, and IPAddresses potentially removed if they are non-existent on the additional Controller. Locations should remain unaffected. + +With those configured, you will then need to define a LocationType to use for the imported Networks. With those created, you can run the Job to start the synchronization: + +![Job Form](../../images/citrix_adm_job_form.png) + +If you wish to just test the synchronization but not have any data created in Nautobot you'll want to select the `Dryrun` toggle. Clicking the `Debug` toggle will enable more verbose logging to inform you of what is occuring behind the scenes. After those toggles there are also dropdowns that allow you to specify the Citrix ADM instance(s) to synchronize with and to define the LocationType to use for the imported Datacenters from those instances. In addition, there are also some optional settings on the Job form: + +- Should the LocationType that you specify for the imported Networks require a parent Location to be assigned, you can define this parent one of two ways: + +1. The Parent Location field allows you to define a singular Location that will be assigned as the parent for all imported Datacenter Locations. + +2. The Location Mapping field allows you to define a dictionary of Location mappings. This feature is intended for specifying parent Locations for the Datacenter Locations in Citrix ADM. This is useful if this information is missing from Citrix ADM but required for Nautobot or to allow you to change the information as it's imported to match information from another System of Record. The expected pattern for this field is `{"": {"parent": ""}}`. + +In addition, the ability to assign Roles to your imported Devices as provided with the Hostname Mapping field. This field allows you to specify a list of tuples containing a regular expression pattern to match against Device hostnames and the Role to assign if matched. Ex: [(".*INT-LB.*", "Internal Load-Balancer")] + +- Finally there is an option to specify a Tenant to be assigned to the imported Devices, Prefixes, and IPAddresses. This is handy for cases where you have multiple Citrix ADM instances that are used by differing business units. + +Running this Job will redirect you to a `Nautobot Job Result` view. + +Once the Job has finished you can click on the `SSoT Sync Details` button at the top right of the Job Result page to see detailed information about the data that was synchronized from Citrix ADM and the outcome of the sync Job. diff --git a/docs/user/integrations/index.md b/docs/user/integrations/index.md index 961403d24..a1e469a73 100644 --- a/docs/user/integrations/index.md +++ b/docs/user/integrations/index.md @@ -3,8 +3,9 @@ This Nautobot app supports the following integrations: - [Cisco ACI](./aci.md) -- [Arista CloudVision](./aristacv.md) - [Bootstrap](./bootstrap.md) +- [Arista CloudVision](./aristacv.md) +- [Citrix ADM](./citrix_adm.md) - [Device42](./device42.md) - [Cisco DNA Center](./dna_center.md) - [Infoblox](./infoblox.md) diff --git a/mkdocs.yml b/mkdocs.yml index 2bac83284..307da2b32 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -109,8 +109,9 @@ nav: - Integrations: - "user/integrations/index.md" - Cisco ACI: "user/integrations/aci.md" - - Arista CloudVision: "user/integrations/aristacv.md" - Bootstrap: "user/integrations/bootstrap.md" + - Citrix ADM: "user/integrations/citrix_adm.md" + - Arista CloudVision: "user/integrations/aristacv.md" - Device42: "user/integrations/device42.md" - DNA Center: "user/integrations/dna_center.md" - Infoblox: "user/integrations/infoblox.md" @@ -127,8 +128,9 @@ nav: - Integrations Installation: - "admin/integrations/index.md" - Cisco ACI: "admin/integrations/aci_setup.md" - - Arista CloudVision: "admin/integrations/aristacv_setup.md" + - Citrix ADM: "admin/integrations/citrix_adm_setup.md" - Bootstrap: "admin/integrations/bootstrap_setup.md" + - Arista CloudVision: "admin/integrations/aristacv_setup.md" - Device42: "admin/integrations/device42_setup.md" - DNA Center: "admin/integrations/dna_center_setup.md" - Infoblox: "admin/integrations/infoblox_setup.md" diff --git a/nautobot_ssot/__init__.py b/nautobot_ssot/__init__.py index b769fbee3..61912d187 100644 --- a/nautobot_ssot/__init__.py +++ b/nautobot_ssot/__init__.py @@ -18,6 +18,7 @@ "nautobot_ssot_aci", "nautobot_ssot_aristacv", "nautobot_ssot_bootstrap", + "nautobot_ssot_citrix_adm", "nautobot_ssot_device42", "nautobot_ssot_dna_center", "nautobot_ssot_infoblox", @@ -83,6 +84,7 @@ class NautobotSSOTAppConfig(NautobotAppConfig): "aristacv_role_mappings": {}, "aristacv_site_mappings": {}, "aristacv_verify": True, + "citrix_adm_update_sites": True, "device42_host": "", "device42_username": "", "device42_password": "", @@ -102,6 +104,7 @@ class NautobotSSOTAppConfig(NautobotAppConfig): "enable_aristacv": False, "enable_device42": False, "enable_dna_center": False, + "enable_citrix_adm": False, "enable_infoblox": False, "enable_ipfabric": False, "enable_servicenow": False, diff --git a/nautobot_ssot/integrations/bootstrap/signals.py b/nautobot_ssot/integrations/bootstrap/signals.py index 5248fec53..9f57f844e 100644 --- a/nautobot_ssot/integrations/bootstrap/signals.py +++ b/nautobot_ssot/integrations/bootstrap/signals.py @@ -95,11 +95,13 @@ def nautobot_database_ready_callback(sender, *, apps, **kwargs): # pylint: disa print(f"Unable to find ValidatedSoftwareLCM model from Device Lifecycle Management App. {err}") sync_custom_field, _ = create_or_update_custom_field( + apps, key="last_synced_from_sor", field_type=CustomFieldTypeChoices.TYPE_DATE, label="Last sync from System of Record", ) sor_custom_field, _ = create_or_update_custom_field( + apps, key="system_of_record", field_type=CustomFieldTypeChoices.TYPE_TEXT, label="System of Record", diff --git a/nautobot_ssot/integrations/citrix_adm/__init__.py b/nautobot_ssot/integrations/citrix_adm/__init__.py new file mode 100644 index 000000000..7f3762e58 --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/__init__.py @@ -0,0 +1 @@ +"""Base module for Citrix ADM integration.""" diff --git a/nautobot_ssot/integrations/citrix_adm/constants.py b/nautobot_ssot/integrations/citrix_adm/constants.py new file mode 100644 index 000000000..4e8400a31 --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/constants.py @@ -0,0 +1,3 @@ +"""Constants for use within Nautobot SSoT for Citrix ADM.""" + +DEVICETYPE_MAP = {"nsvpx": "NetScaler ADC VPX"} diff --git a/nautobot_ssot/integrations/citrix_adm/diffsync/__init__.py b/nautobot_ssot/integrations/citrix_adm/diffsync/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nautobot_ssot/integrations/citrix_adm/diffsync/adapters/__init__.py b/nautobot_ssot/integrations/citrix_adm/diffsync/adapters/__init__.py new file mode 100644 index 000000000..2b5b50055 --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/diffsync/adapters/__init__.py @@ -0,0 +1 @@ +"""Adapter classes for loading DiffSyncModels with data from Citrix ADM or Nautobot.""" diff --git a/nautobot_ssot/integrations/citrix_adm/diffsync/adapters/citrix_adm.py b/nautobot_ssot/integrations/citrix_adm/diffsync/adapters/citrix_adm.py new file mode 100644 index 000000000..ff51d96a2 --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/diffsync/adapters/citrix_adm.py @@ -0,0 +1,329 @@ +"""Nautobot SSoT Citrix ADM Adapter for Citrix ADM SSoT plugin.""" + +import ipaddress +from decimal import Decimal +from typing import List, Optional + +from diffsync import Adapter +from diffsync.exceptions import ObjectNotFound +from nautobot.extras.choices import SecretsGroupAccessTypeChoices, SecretsGroupSecretTypeChoices +from nautobot.extras.models import ExternalIntegration, Job +from nautobot.tenancy.models import Tenant + +from nautobot_ssot.integrations.citrix_adm.constants import DEVICETYPE_MAP +from nautobot_ssot.integrations.citrix_adm.diffsync.models.citrix_adm import ( + CitrixAdmAddress, + CitrixAdmDatacenter, + CitrixAdmDevice, + CitrixAdmIPAddressOnInterface, + CitrixAdmOSVersion, + CitrixAdmPort, + CitrixAdmSubnet, +) +from nautobot_ssot.integrations.citrix_adm.utils.citrix_adm import ( + CitrixNitroClient, + parse_nsip6s, + parse_nsips, + parse_version, + parse_vlan_bindings, +) +from nautobot_ssot.utils import parse_hostname_for_role + + +class CitrixAdmAdapter(Adapter): # pylint: disable=too-many-instance-attributes + """DiffSync adapter for Citrix ADM.""" + + datacenter = CitrixAdmDatacenter + osversion = CitrixAdmOSVersion + device = CitrixAdmDevice + address = CitrixAdmAddress + prefix = CitrixAdmSubnet + port = CitrixAdmPort + ip_on_intf = CitrixAdmIPAddressOnInterface + + top_level = ["datacenter", "osversion", "device", "prefix", "address", "ip_on_intf"] + + def __init__( + self, + job: Job, + instances: List[ExternalIntegration], + sync=None, + tenant: Optional[Tenant] = None, + ): + """Initialize Citrix ADM. + + Args: + job (Job): Citrix ADM job. + instances (List[ExternalIntegration]): ExternalIntegrations defining Citrix ADM instances. + sync (object, optional): Citrix ADM DiffSync. Defaults to None. + tenant (Tenant, optional): Name of Tenant to associate Devices and IP Addresses with. + """ + super().__init__() + self.job = job + self.sync = sync + self.instances = instances + self.conn = None + self.tenant = tenant + self.adm_site_map = {} + self.adm_device_map = {} + + def create_site_map(self): + """Create mapping of ADM Datacenters to information about the Datacenter.""" + sites = self.conn.get_sites() + for site in sites: + self.adm_site_map[site["id"]] = site + + def load_site(self, site_info: dict): + """Load sites from Citrix ADM into DiffSync models. + + Args: + site_info (dict): Dictionary containing information about Datacenter to be imported. + """ + if not site_info.get("name"): + self.job.logger.error(f"Site is missing name so won't be loaded. {site_info}") + else: + site_name = site_info["name"] + if self.job.location_map and site_name in self.job.location_map: + parent_loc = self.job.location_map[site_name]["parent"] + if "name" in self.job.location_map[site_name]: + site_name = self.job.location_map[site_name]["name"] + elif self.job.parent_location: + parent_loc = self.job.parent_location.name + elif site_info.get("region"): + parent_loc = site_info["region"] + if ( + self.job.location_map + and parent_loc in self.job.location_map + and "name" in self.job.location_map[parent_loc] + ): + parent_loc = self.job.location_map[parent_loc]["name"] + else: + parent_loc = "Global" + _, loaded = self.get_or_instantiate( + self.datacenter, + ids={"name": site_name, "region": parent_loc}, + attrs={ + "latitude": float(round(Decimal(site_info["latitude"] if site_info["latitude"] else 0.0), 6)), + "longitude": float(round(Decimal(site_info["longitude"] if site_info["longitude"] else 0.0), 6)), + "uuid": None, + }, + ) + if loaded and self.job.debug: + self.job.logger.info(f"Loaded Datacenter from Citrix ADM: {site_name}") + + def load_devices(self): + """Load devices from Citrix ADM into DiffSync models.""" + devices = self.conn.get_devices() + for dev in devices: + if not dev.get("hostname"): + self.job.logger.warning(f"Device without hostname will not be loaded. {dev}") + continue + try: + found_dev = self.get(self.device, dev["hostname"]) + if found_dev: + self.job.logger.warning(f"Duplicate Device attempting to be loaded: {dev['hostname']}") + except ObjectNotFound: + site = self.adm_site_map[dev["datacenter_id"]] + self.load_site(site_info=site) + site_name = site["name"] + if ( + self.job.location_map + and site_name in self.job.location_map + and "name" in self.job.location_map[site_name] + ): + site_name = self.job.location_map[site_name]["name"] + role = parse_hostname_for_role( + hostname_map=self.job.hostname_mapping, + device_hostname=dev["hostname"], + default_role="Load-Balancer", + ) + version = parse_version(dev["version"]) + self.get_or_instantiate(self.osversion, ids={"version": version}, attrs={}) + new_dev = self.device( + name=dev["hostname"], + model=DEVICETYPE_MAP[dev["type"]] if dev["type"] in DEVICETYPE_MAP else dev["type"], + role=role, + serial=dev["serialnumber"], + site=site_name, + status="Active" if dev["instance_state"] == "Up" else "Offline", + tenant=self.tenant.name if self.tenant else None, + version=version, + uuid=None, + hanode=dev["ha_ip_address"], + ) + self.add(new_dev) + self.adm_device_map[dev["hostname"]] = dev + + def create_port_map(self): + """Create a port/vlan/ip map for each ADC instance.""" + self.job.logger.info("Retrieving NSIP and port bindings from ADC instances.") + for _, adc in self.adm_device_map.items(): + vlan_bindings = self.conn.get_vlan_bindings(adc) + nsips = self.conn.get_nsip(adc) + nsip6s = self.conn.get_nsip6(adc) + + ports = parse_vlan_bindings(vlan_bindings, adc, self.job) + ports = parse_nsips(nsips, ports, adc) + ports = parse_nsip6s(nsip6s, ports) + + self.adm_device_map[adc["hostname"]]["ports"] = ports + + def load_ports(self): + """Load ports from Citrix ADM into DiffSync models.""" + for _, adc in self.adm_device_map.items(): + for port in adc["ports"]: + try: + self.get(self.port, {"name": port["port"], "device": adc["hostname"]}) + except ObjectNotFound: + dev = self.get(self.device, adc["hostname"]) + new_port = self.add_port( + dev_name=adc["hostname"], + port_name=port["port"], + port_status="ENABLED", + description="", + ) + dev.add_child(new_port) + + def load_addresses(self): + """Load addresses from Citrix ADC instances into Diffsync models.""" + for _, adc in self.adm_device_map.items(): + for port in adc["ports"]: + if port.get("ipaddress"): + addr = f"{port['ipaddress']}/{port['netmask']}" + prefix = ipaddress.ip_interface(addr).network.with_prefixlen + self.load_prefix(prefix=prefix) + _tags = port["tags"] if port.get("tags") else [] + if len(_tags) > 1: + _tags.sort() + _primary = bool("MGMT" in _tags or "MIP" in _tags) + self.load_address( + address=addr, + prefix=prefix, + tags=_tags, + ) + self.load_address_to_interface( + address=addr, + device=adc["hostname"], + port=port["port"], + primary=_primary, + ) + + def add_port( + self, dev_name: str, port_name: str = "Management", port_status: str = "ENABLED", description: str = "" + ): + """Method to add Port DiffSync model. + + Args: + dev_name (str): Name of device port is attached to. + port_name (str, optional): Name of port to create. Defaults to "Management". + port_status (str, optional): Status of port to create. Defaults to "ENABLED". + description (str, optional): Description for port. Defaults to "". + + Returns: + CitrixAdmPort: DiffSync model for Port that was loaded. + """ + new_port = self.port( + name=port_name, + device=dev_name, + status="Active" if port_status == "ENABLED" else "Offline", + description=description, + uuid=None, + ) + self.add(new_port) + return new_port + + def load_prefix(self, prefix: str): + """Load CitrixAdmSubnet DiffSync model with specified data. + + Args: + prefix (str): Prefix to be loaded. + """ + if self.tenant: + namespace = self.tenant.name + else: + namespace = "Global" + try: + self.get(self.prefix, {"prefix": prefix, "namespace": namespace}) + except ObjectNotFound: + new_pf = self.prefix( + prefix=prefix, + namespace=namespace, + tenant=self.tenant.name if self.tenant else None, + uuid=None, + ) + self.add(new_pf) + + def load_address(self, address: str, prefix: str, tags: Optional[list] = None): + """Load CitrixAdmAddress DiffSync model with specified data. + + Args: + address (str): IP Address to be loaded. + prefix (str): Prefix that IP Address resides in. + device (str): Device that IP resides on. + port (str): Interface that IP is configured on. + primary (str): Whether the IP is primary IP for assigned device. Defaults to False. + tags (list): List of tags assigned to IP. Defaults to None. + """ + try: + self.get(self.address, {"address": address, "prefix": prefix}) + except ObjectNotFound: + new_addr = self.address( + address=address, + prefix=prefix, + tenant=self.tenant.name if self.tenant else None, + uuid=None, + tags=tags if tags else [], + ) + self.add(new_addr) + + def load_address_to_interface(self, address: str, device: str, port: str, primary: bool = False): + """Load CitrixAdmIPAddressOnInterface DiffSync model with specified data. + + Args: + address (str): IP Address in mapping. + device (str): Device that IP resides on. + port (str): Interface that IP is configured on. + primary (str): Whether the IP is primary IP for assigned device. Defaults to False. + """ + try: + self.get(self.ip_on_intf, {"address": address, "device": device, "port": port}) + except ObjectNotFound: + new_map = self.ip_on_intf(address=address, device=device, port=port, primary=primary, uuid=None) + self.add(new_map) + + def load(self): + """Load data from Citrix ADM into DiffSync models.""" + for instance in self.instances: + self.job.logger.info(f"Loading data from {instance.name}.") + if instance.secrets_group is not None: + _sg = instance.secrets_group + instance_username = _sg.get_secret_value( + access_type=SecretsGroupAccessTypeChoices.TYPE_HTTP, + secret_type=SecretsGroupSecretTypeChoices.TYPE_USERNAME, + ) + instance_password = _sg.get_secret_value( + access_type=SecretsGroupAccessTypeChoices.TYPE_HTTP, + secret_type=SecretsGroupSecretTypeChoices.TYPE_PASSWORD, + ) + self.conn = CitrixNitroClient( + base_url=instance.remote_url, + user=instance_username, + password=instance_password, + verify=instance.verify_ssl, + job=self.job, + ) + self.conn.login() + self.adm_site_map = {} + self.adm_device_map = {} + + self.create_site_map() + self.load_devices() + self.create_port_map() + self.load_ports() + self.load_addresses() + + self.conn.logout() + else: + self.job.logger.warning( + f"Missing SecretsGroup definition for {instance.name}. This must be defined so we can authenticate instance." + ) diff --git a/nautobot_ssot/integrations/citrix_adm/diffsync/adapters/nautobot.py b/nautobot_ssot/integrations/citrix_adm/diffsync/adapters/nautobot.py new file mode 100644 index 000000000..ea349a69b --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/diffsync/adapters/nautobot.py @@ -0,0 +1,209 @@ +"""Nautobot Adapter for Citrix ADM SSoT plugin.""" + +from collections import defaultdict +from typing import Optional + +from diffsync import Adapter +from diffsync.enum import DiffSyncModelFlags +from diffsync.exceptions import ObjectNotFound +from django.db.models import ProtectedError +from nautobot.dcim.models import Device as OrmDevice +from nautobot.dcim.models import Interface, Location, SoftwareVersion +from nautobot.extras.models import Job +from nautobot.ipam.models import IPAddress, IPAddressToInterface, Prefix +from nautobot.tenancy.models import Tenant + +from nautobot_ssot.integrations.citrix_adm.diffsync.models.nautobot import ( + NautobotAddress, + NautobotDatacenter, + NautobotDevice, + NautobotIPAddressOnInterface, + NautobotOSVersion, + NautobotPort, + NautobotSubnet, +) +from nautobot_ssot.integrations.citrix_adm.utils import nautobot + + +class NautobotAdapter(Adapter): # pylint: disable=too-many-instance-attributes + """DiffSync adapter for Nautobot.""" + + datacenter = NautobotDatacenter + osversion = NautobotOSVersion + device = NautobotDevice + port = NautobotPort + prefix = NautobotSubnet + address = NautobotAddress + ip_on_intf = NautobotIPAddressOnInterface + + top_level = ["datacenter", "osversion", "device", "prefix", "address", "ip_on_intf"] + + def __init__(self, job: Job, sync=None, tenant: Optional[Tenant] = None): + """Initialize Nautobot. + + Args: + job (Job): Nautobot job. + sync (object, optional): Nautobot DiffSync. Defaults to None. + tenant (Tenant, optional): Tenant to associate imported objects with. Used to filter loaded objects. + """ + super().__init__() + self.job = job + self.sync = sync + self.tenant = tenant + self.objects_to_delete = defaultdict(list) + + def load_sites(self): + """Load Sites from Nautobot into DiffSync models.""" + for site in Location.objects.filter(location_type=self.job.dc_loctype): + _, loaded = self.get_or_instantiate( + self.datacenter, + ids={"name": site.name, "region": site.parent.name if site.parent else None}, + attrs={ + "latitude": float(round(site.latitude, 6)) if site.latitude else None, + "longitude": float(round(site.longitude, 6)) if site.longitude else None, + "uuid": site.id, + }, + ) + if loaded and self.job.debug: + self.job.logger.info(f"Loaded {self.job.dc_loctype.name} {site.name} from Nautobot.") + + def load_devices(self): + """Load Devices from Nautobot into DiffSync models.""" + if self.tenant: + devices = OrmDevice.objects.select_related("device_type", "location", "status").filter(tenant=self.tenant) + else: + devices = OrmDevice.objects.select_related("device_type", "location", "status").filter( + _custom_field_data__system_of_record="Citrix ADM" + ) + for dev in devices: + if self.job.debug: + self.job.logger.info(f"Loading Device {dev.name} from Nautobot.") + hanode = dev.custom_field_data.get("ha_node") + new_dev = self.device( + name=dev.name, + model=dev.device_type.model, + role=dev.role.name, + serial=dev.serial, + site=dev.location.name, + status=dev.status.name, + tenant=dev.tenant.name if dev.tenant else "", + version=dev.software_version.version if dev.software_version else None, + uuid=dev.id, + hanode=hanode, + ) + if self.tenant: + new_dev.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST + self.add(new_dev) + + def load_software_versions(self): + """Load Citrix SoftwareVersions from Nautobot into DiffSync models.""" + for osver in SoftwareVersion.objects.filter(platform__manufacturer__name__icontains="Citrix"): + self.get_or_instantiate( + self.osversion, + ids={"version": osver.version}, + attrs={"uuid": osver.id}, + ) + + def load_ports(self): + """Load Interfaces from Nautobot into DiffSync models.""" + if self.tenant: + interfaces = Interface.objects.select_related("device", "status").filter(device__tenant=self.tenant) + else: + interfaces = Interface.objects.select_related("device", "status").filter( + device___custom_field_data__system_of_record="Citrix ADM" + ) + for intf in interfaces: + try: + dev = self.get(self.device, intf.device.name) + new_intf = self.port( + name=intf.name, + device=intf.device.name, + status=intf.status.name, + description=intf.description, + uuid=intf.id, + ) + if self.tenant: + new_intf.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST + self.add(new_intf) + dev.add_child(new_intf) + except ObjectNotFound: + self.job.logger.warning( + f"Unable to find {intf.device.name} loaded so skipping loading port {intf.name}." + ) + + def load_prefixes(self): + """Load Prefixes from Nautobot into DiffSync models.""" + if self.tenant: + prefixes = Prefix.objects.filter(tenant=self.tenant) + else: + prefixes = Prefix.objects.filter(_custom_field_data__system_of_record="Citrix ADM") + for _pf in prefixes: + new_pf = self.prefix( + prefix=str(_pf.prefix), + namespace=_pf.namespace.name, + tenant=_pf.tenant.name if _pf.tenant else None, + uuid=_pf.id, + ) + if self.tenant: + new_pf.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST + self.add(new_pf) + + def load_addresses(self): + """Load IP Addresses from Nautobot into DiffSync models.""" + if self.tenant: + addresses = IPAddress.objects.filter(tenant=self.tenant) + else: + addresses = IPAddress.objects.filter(_custom_field_data__system_of_record="Citrix ADM") + for addr in addresses: + new_ip = self.address( + address=str(addr.address), + prefix=str(addr.parent.prefix), + tenant=addr.tenant.name if addr.tenant else None, + uuid=addr.id, + tags=nautobot.get_tag_strings(addr.tags), + ) + if self.tenant: + new_ip.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST + self.add(new_ip) + for mapping in IPAddressToInterface.objects.filter(ip_address=addr): + new_mapping = self.ip_on_intf( + address=str(addr.address), + device=mapping.interface.device.name, + port=mapping.interface.name, + primary=len(addr.primary_ip4_for.all()) > 0 or len(addr.primary_ip6_for.all()) > 0, + uuid=mapping.id, + ) + if self.tenant: + new_mapping.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST + self.add(new_mapping) + + def sync_complete(self, source: Adapter, *args, **kwargs): + """Label and clean up function for DiffSync sync. + + Once the sync is complete, this function labels all imported objects and then + deletes any objects from Nautobot that need to be deleted in a specific order. + + Args: + source: The DiffSync whose data was used to update this instance. + *args: Positional arguments. + **kwargs: Keyword arguments. + """ + for grouping in ["addresses", "prefixes", "ports", "devices"]: + for nautobot_obj in self.objects_to_delete[grouping]: + try: + if self.job.debug: + self.job.logger.info(f"Deleting {nautobot_obj}.") + nautobot_obj.delete() + except ProtectedError: + self.job.logger.info(f"Deletion failed protected object: {nautobot_obj}") + self.objects_to_delete[grouping] = [] + return super().sync_complete(source, *args, **kwargs) + + def load(self): + """Load data from Nautobot into DiffSync models.""" + self.load_sites() + self.load_devices() + self.load_software_versions() + self.load_ports() + self.load_prefixes() + self.load_addresses() diff --git a/nautobot_ssot/integrations/citrix_adm/diffsync/models/__init__.py b/nautobot_ssot/integrations/citrix_adm/diffsync/models/__init__.py new file mode 100644 index 000000000..b2a1f01c7 --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/diffsync/models/__init__.py @@ -0,0 +1 @@ +"""DiffSync models and adapters for the Citrix ADM SSoT plugin.""" diff --git a/nautobot_ssot/integrations/citrix_adm/diffsync/models/base.py b/nautobot_ssot/integrations/citrix_adm/diffsync/models/base.py new file mode 100644 index 000000000..645f49b85 --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/diffsync/models/base.py @@ -0,0 +1,133 @@ +# pylint: disable=duplicate-code +"""DiffSyncModel subclasses for Nautobot-to-Citrix ADM data sync.""" + +from typing import List, Optional +from uuid import UUID + +from diffsync import DiffSyncModel +from diffsync.enum import DiffSyncModelFlags + + +class Datacenter(DiffSyncModel): + """Diffsync model for Citrix ADM datacenters.""" + + model_flags: DiffSyncModelFlags = DiffSyncModelFlags.SKIP_UNMATCHED_DST + + _modelname = "datacenter" + _identifiers = ( + "name", + "region", + ) + _attributes = ("latitude", "longitude") + + name: str + region: Optional[str] = None + latitude: Optional[float] = None + longitude: Optional[float] = None + uuid: Optional[UUID] = None + + +class OSVersion(DiffSyncModel): + """DiffSync model for Citrix ADM device OS versions.""" + + _modelname = "osversion" + _identifiers = ("version",) + _attributes = () + + version: str + + uuid: Optional[UUID] = None + + +class Device(DiffSyncModel): + """DiffSync model for Citrix ADM devices.""" + + _modelname = "device" + _identifiers = ("name",) + _attributes = ( + "model", + "role", + "serial", + "site", + "status", + "tenant", + "version", + "hanode", + ) + _children = {"port": "ports"} + + name: str + model: Optional[str] = None + role: str + serial: Optional[str] = None + site: Optional[str] = None + status: Optional[str] = None + tenant: Optional[str] = None + version: Optional[str] = None + ports: Optional[List["Port"]] = [] + hanode: Optional[str] = None + + uuid: Optional[UUID] = None + + +class Port(DiffSyncModel): + """DiffSync model for Citrix ADM device interfaces.""" + + _modelname = "port" + _identifiers = ("name", "device") + _attributes = ("status", "description") + _children = {} + + name: str + device: str + status: str + description: Optional[str] = None + + uuid: Optional[UUID] = None + + +class Subnet(DiffSyncModel): + """DiffSync model for Citrix ADM management prefixes.""" + + _modelname = "prefix" + _identifiers = ("prefix", "namespace") + _attributes = ("tenant",) + _children = {} + + prefix: str + namespace: str + tenant: Optional[str] = None + + uuid: Optional[UUID] = None + + +class Address(DiffSyncModel): + """DiffSync model for Citrix ADM IP Addresses.""" + + _modelname = "address" + _identifiers = ("address", "prefix") + _attributes = ("tenant", "tags") + _children = {} + + address: str + prefix: str + tenant: Optional[str] = None + tags: Optional[list] = None + + uuid: Optional[UUID] = None + + +class IPAddressOnInterface(DiffSyncModel): + """DiffSync model for Citrix ADM tracking IPAddress on particular Device interfaces.""" + + _modelname = "ip_on_intf" + _identifiers = ("address", "device", "port") + _attributes = ("primary",) + _children = {} + + address: str + device: str + port: str + primary: bool + + uuid: Optional[UUID] = None diff --git a/nautobot_ssot/integrations/citrix_adm/diffsync/models/citrix_adm.py b/nautobot_ssot/integrations/citrix_adm/diffsync/models/citrix_adm.py new file mode 100644 index 000000000..4eb581205 --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/diffsync/models/citrix_adm.py @@ -0,0 +1,130 @@ +"""Nautobot SSoT Citrix ADM DiffSync models for Nautobot SSoT Citrix ADM SSoT.""" + +from nautobot_ssot.integrations.citrix_adm.diffsync.models.base import ( + Address, + Datacenter, + Device, + IPAddressOnInterface, + OSVersion, + Port, + Subnet, +) + + +class CitrixAdmDatacenter(Datacenter): + """Citrix ADM implementation of Datacenter DiffSync model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create Site in Citrix ADM from Datacenter object.""" + raise NotImplementedError + + def update(self, attrs): + """Update Site in Citrix ADM from Datacenter object.""" + raise NotImplementedError + + def delete(self): + """Delete Site in Citrix ADM from Datacenter object.""" + raise NotImplementedError + + +class CitrixAdmOSVersion(OSVersion): + """Citrix ADM implementation of OSVersion DiffSync model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create OSVersion in Citrix ADM from OSVersion object.""" + raise NotImplementedError + + def update(self, attrs): + """Update OSVersion in Citrix ADM from OSVersion object.""" + raise NotImplementedError + + def delete(self): + """Delete OSVersion in Citrix ADM from OSVersion object.""" + raise NotImplementedError + + +class CitrixAdmDevice(Device): + """Citrix ADM implementation of Device DiffSync model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create Device in Citrix ADM from Device object.""" + raise NotImplementedError + + def update(self, attrs): + """Update Device in Citrix ADM from Device object.""" + raise NotImplementedError + + def delete(self): + """Delete Device in Citrix ADM from Device object.""" + raise NotImplementedError + + +class CitrixAdmPort(Port): + """Citrix ADM implementation of Port DiffSync model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create Interface in Citrix ADM from Port object.""" + raise NotImplementedError + + def update(self, attrs): + """Update Interface in Citrix ADM from Port object.""" + raise NotImplementedError + + def delete(self): + """Delete Interface in Citrix ADM from Port object.""" + raise NotImplementedError + + +class CitrixAdmSubnet(Subnet): + """Citrix ADM implementation of Subnet DiffSync model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create Prefix in Citrix ADM from Subnet object.""" + raise NotImplementedError + + def update(self, attrs): + """Update Prefix in Citrix ADM from Subnet object.""" + raise NotImplementedError + + def delete(self): + """Delete Prefix in Citrix ADM from Subnet object.""" + raise NotImplementedError + + +class CitrixAdmAddress(Address): + """Citrix ADM implementation of Address DiffSync model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create IP Address in Citrix ADM from Address object.""" + raise NotImplementedError + + def update(self, attrs): + """Update IP Address in Citrix ADM from Address object.""" + raise NotImplementedError + + def delete(self): + """Delete IP Address in Citrix ADM from Address object.""" + raise NotImplementedError + + +class CitrixAdmIPAddressOnInterface(IPAddressOnInterface): + """Citrix ADM implementation of IPAddressOnInterface DiffSync model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create IPAddressToInterface in Citrix ADM from CitrixAdmIPAddressOnInterface object.""" + raise NotImplementedError + + def update(self, attrs): + """Update IPAddressToInterface in Citrix ADM from CitrixAdmIPAddressOnInterface object.""" + raise NotImplementedError + + def delete(self): + """Delete IPAddressToInterface in Citrix ADM from CitrixAdmIPAddressOnInterface object.""" + raise NotImplementedError diff --git a/nautobot_ssot/integrations/citrix_adm/diffsync/models/nautobot.py b/nautobot_ssot/integrations/citrix_adm/diffsync/models/nautobot.py new file mode 100644 index 000000000..4adecf80f --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/diffsync/models/nautobot.py @@ -0,0 +1,346 @@ +# pylint: disable=duplicate-code +"""Nautobot DiffSync models for Citrix ADM SSoT.""" + +from datetime import datetime + +from django.conf import settings +from django.contrib.contenttypes.models import ContentType +from nautobot.dcim.models import Device as NewDevice +from nautobot.dcim.models import DeviceType, Interface, Location, Manufacturer, Platform, SoftwareVersion +from nautobot.extras.models import Role, Status, Tag +from nautobot.ipam.models import IPAddress, IPAddressToInterface, Namespace, Prefix +from nautobot.tenancy.models import Tenant + +from nautobot_ssot.integrations.citrix_adm.diffsync.models.base import ( + Address, + Datacenter, + Device, + IPAddressOnInterface, + OSVersion, + Port, + Subnet, +) + + +class NautobotDatacenter(Datacenter): + """Nautobot implementation of Citrix ADM Datacenter model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create Site in Nautobot from NautobotDatacenter object.""" + status_active = Status.objects.get(name="Active") + parent_loc = None + if adapter.job.dc_loctype.parent and ids.get("region"): + parent_loc = Location.objects.get_or_create( + name=ids["region"], location_type=adapter.job.dc_loctype.parent, status=status_active + )[0] + if Location.objects.filter(name=ids["name"]).exists(): + adapter.job.logger.warning(f"Site {ids['name']} already exists so skipping creation.") + return None + new_site = Location( + name=ids["name"], + parent=parent_loc, + status=status_active, + latitude=attrs["latitude"], + longitude=attrs["longitude"], + location_type=adapter.job.dc_loctype, + ) + new_site.validated_save() + return super().create(adapter=adapter, ids=ids, attrs=attrs) + + def update(self, attrs): + """Update Site in Nautobot from NautobotDatacenter object.""" + if not settings.PLUGINS_CONFIG.get("nautobot_ssot").get("citrix_adm_update_sites"): + self.adapter.job.logger.warning(f"Update sites setting is disabled so skipping updating {self.name}.") + return None + site = Location.objects.get(id=self.uuid) + if "latitude" in attrs: + site.latitude = attrs["latitude"] + if "longitude" in attrs: + site.longitude = attrs["longitude"] + site.validated_save() + return super().update(attrs) + + +class NautobotOSVersion(OSVersion): + """Nautobot implementation of Citrix ADM Device model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create SoftwareVersion in Nautobot from NautobotOSVersion object.""" + new_ver = SoftwareVersion( + version=ids["version"], + platform=Platform.objects.get(name="citrix.adc"), + status=Status.objects.get(name="Active"), + ) + new_ver.validated_save() + return super().create(adapter=adapter, ids=ids, attrs=attrs) + + def delete(self): + """Delete SoftwareVersion in Nautobot from NautobotOSVersion object.""" + ver = SoftwareVersion.objects.get(id=self.uuid) + super().delete() + ver.delete() + return self + + +class NautobotDevice(Device): + """Nautobot implementation of Citrix ADM Device model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create Device in Nautobot from NautobotDevice object.""" + lb_role, created = Role.objects.get_or_create(name=attrs["role"]) + if created: + lb_role.content_types.add(ContentType.objects.get_for_model(NewDevice)) + lb_dt, _ = DeviceType.objects.get_or_create( + model=attrs["model"], manufacturer=Manufacturer.objects.get(name="Citrix") + ) + citrix_platform = Platform.objects.get(name="citrix.adc") + new_device = NewDevice( + name=ids["name"], + status=Status.objects.get(name=attrs["status"]), + role=lb_role, + location=Location.objects.get(name=attrs["site"], location_type=adapter.job.dc_loctype), + device_type=lb_dt, + serial=attrs["serial"], + platform=citrix_platform, + ) + if attrs.get("tenant"): + new_device.tenant = Tenant.objects.update_or_create(name=attrs["tenant"])[0] + if attrs.get("version"): + new_device.software_version = SoftwareVersion.objects.get_or_create( + version=attrs["version"], platform=citrix_platform + )[0] + if attrs.get("hanode"): + new_device.custom_field_data["ha_node"] = attrs["hanode"] + new_device.custom_field_data["system_of_record"] = "Citrix ADM" + new_device.custom_field_data["last_synced_from_sor"] = datetime.today().date().isoformat() + new_device.validated_save() + return super().create(adapter=adapter, ids=ids, attrs=attrs) + + def update(self, attrs): + """Update Device in Nautobot from NautobotDevice object.""" + device = NewDevice.objects.get(id=self.uuid) + if "model" in attrs: + device.device_type, _ = DeviceType.objects.get_or_create( + model=attrs["model"], manufacturer=Manufacturer.objects.get(name="Citrix") + ) + if "status" in attrs: + device.status = Status.objects.get(name=attrs["status"]) + if "role" in attrs: + device.role = Role.objects.get_or_create(name=attrs["role"])[0] + if "serial" in attrs: + device.serial = attrs["serial"] + if "site" in attrs: + device.location = Location.objects.get(name=attrs["site"]) + if "tenant" in attrs: + if attrs.get("tenant"): + device.tenant = Tenant.objects.update_or_create(name=attrs["tenant"])[0] + else: + device.tenant = None + if "version" in attrs: + if attrs.get("version"): + device.software_version = SoftwareVersion.objects.get_or_create( + version=attrs["version"], platform=Platform.objects.get(name="citrix.adc") + )[0] + else: + device.software_version = None + if "hanode" in attrs: + device.custom_field_data["ha_node"] = attrs["hanode"] + device.custom_field_data["system_of_record"] = "Citrix ADM" + device.custom_field_data["last_synced_from_sor"] = datetime.today().date().isoformat() + device.validated_save() + return super().update(attrs) + + def delete(self): + """Delete Device in Nautobot from NautobotDevice object.""" + dev = NewDevice.objects.get(id=self.uuid) + super().delete() + self.adapter.job.logger.info(f"Deleting Device {dev.name}.") + self.adapter.objects_to_delete["devices"].append(dev) + return self + + +class NautobotPort(Port): + """Nautobot implementation of Citrix ADM Port model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create Interface in Nautobot from NautobotPort object.""" + new_port = Interface( + name=ids["name"], + device=NewDevice.objects.get(name=ids["device"]), + status=Status.objects.get(name=attrs["status"]), + description=attrs["description"], + type="virtual", + mgmt_only=bool(ids["name"] == "Management"), + ) + new_port.custom_field_data["system_of_record"] = "Citrix ADM" + new_port.custom_field_data["last_synced_from_sor"] = datetime.today().date().isoformat() + new_port.validated_save() + return super().create(adapter=adapter, ids=ids, attrs=attrs) + + def update(self, attrs): + """Update Interface in Nautobot from NautobotPort object.""" + port = Interface.objects.get(self.uuid) + if "status" in attrs: + port.status = Status.objects.get(name=attrs["status"]) + if "description" in attrs: + port.description = attrs["description"] + port.custom_field_data["system_of_record"] = "Citrix ADM" + port.custom_field_data["last_synced_from_sor"] = datetime.today().date().isoformat() + port.validated_save() + return super().update(attrs) + + def delete(self): + """Delete Interface in Nautobot from NautobotPort object.""" + port = Interface.objects.get(id=self.uuid) + super().delete() + self.adapter.job.logger.info(f"Deleting Port {port.name} for {port.device.name}.") + self.adapter.objects_to_delete["ports"].append(port) + return self + + +class NautobotSubnet(Subnet): + """Nautobot implementation of Citrix ADM Subnet model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create Prefix in Nautobot from NautobotSubnet object.""" + namespace = Namespace.objects.get_or_create(name=ids["namespace"])[0] + if adapter.job.debug: + adapter.job.logger.info(f"Creating Prefix {ids['prefix']}.") + _pf = Prefix( + prefix=ids["prefix"], + namespace=namespace, + status=Status.objects.get(name="Active"), + tenant=Tenant.objects.get(name=attrs["tenant"]) if attrs.get("tenant") else None, + ) + _pf.custom_field_data.update({"system_of_record": "Citrix ADM"}) + _pf.custom_field_data.update({"last_synced_from_sor": datetime.today().date().isoformat()}) + _pf.validated_save() + return super().create(adapter=adapter, ids=ids, attrs=attrs) + + def update(self, attrs): + """Update IP Address in Nautobot from NautobotAddress object.""" + _pf = Prefix.objects.get(id=self.uuid) + if "tenant" in attrs: + if attrs.get("tenant"): + _pf.tenant = Tenant.objects.get(name=attrs["tenant"]) + else: + _pf.tenant = None + _pf.custom_field_data.update({"system_of_record": "Citrix ADM"}) + _pf.custom_field_data.update({"last_synced_from_sor": datetime.today().date().isoformat()}) + _pf.validated_save() + return super().update(attrs) + + def delete(self): # pylint: disable=inconsistent-return-statements + """Delete Prefix in Nautobot.""" + try: + _pf = Prefix.objects.get(id=self.uuid) + self.adapter.objects_to_delete["prefixes"].append(_pf) + super().delete() + return self + except Prefix.DoesNotExist as err: + if self.adapter.job.debug: + self.adapter.job.logger.warning(f"Unable to find Prefix {self.prefix} {self.uuid} for deletion. {err}") + + +class NautobotAddress(Address): + """Nautobot implementation of Citrix ADM Address model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create IP Address in Nautobot from NautobotAddress object.""" + new_ip = IPAddress( + address=ids["address"], + parent=Prefix.objects.filter(network__net_contains=ids["address"].split("/")[0]).last(), + status=Status.objects.get(name="Active"), + namespace=( + Namespace.objects.get_or_create(name=attrs["tenant"])[0] + if attrs.get("tenant") + else Namespace.objects.get(name="Global") + ), + ) + if attrs.get("tenant"): + new_ip.tenant = Tenant.objects.update_or_create(name=attrs["tenant"])[0] + if attrs.get("tags"): + new_ip.tags.set(attrs["tags"]) + for tag in attrs["tags"]: + new_tag = Tag.objects.get(name=tag) + new_tag.content_types.add(ContentType.objects.get_for_model(NewDevice)) + new_ip.custom_field_data["system_of_record"] = "Citrix ADM" + new_ip.custom_field_data["last_synced_from_sor"] = datetime.today().date().isoformat() + new_ip.validated_save() + return super().create(adapter=adapter, ids=ids, attrs=attrs) + + def update(self, attrs): + """Update IP Address in Nautobot from NautobotAddress object.""" + addr = IPAddress.objects.get(id=self.uuid) + if "tenant" in attrs: + if attrs.get("tenant"): + addr.tenant = Tenant.objects.update_or_create(name=attrs["tenant"])[0] + else: + addr.tenant = None + if "tags" in attrs: + addr.tags.set(attrs["tags"]) + for tag in attrs["tags"]: + new_tag = Tag.objects.get(name=tag) + new_tag.content_types.add(ContentType.objects.get_for_model(NewDevice)) + else: + addr.tags.clear() + addr.custom_field_data["system_of_record"] = "Citrix ADM" + addr.custom_field_data["last_synced_from_sor"] = datetime.today().date().isoformat() + addr.validated_save() + return super().update(attrs) + + def delete(self): + """Delete IP Address in Nautobot from NautobotAddress object.""" + addr = IPAddress.objects.get(id=self.uuid) + super().delete() + self.adapter.job.logger.info(f"Deleting IP Address {self}.") + self.adapter.objects_to_delete["addresses"].append(addr) + return self + + +class NautobotIPAddressOnInterface(IPAddressOnInterface): + """Nautobot implementation of Citrix ADM IPAddressOnInterface model.""" + + @classmethod + def create(cls, adapter, ids, attrs): + """Create IPAddressToInterface in Nautobot from IPAddressOnInterface object.""" + new_map = IPAddressToInterface( + ip_address=IPAddress.objects.get(address=ids["address"]), + interface=Interface.objects.get(name=ids["port"], device__name=ids["device"]), + ) + new_map.validated_save() + if attrs.get("primary"): + if new_map.ip_address.ip_version == 4: + new_map.interface.device.primary_ip4 = new_map.ip_address + else: + new_map.interface.device.primary_ip6 = new_map.ip_address + new_map.interface.device.validated_save() + return super().create(adapter=adapter, ids=ids, attrs=attrs) + + def update(self, attrs): + """Update IP Address in Nautobot from IPAddressOnInterface object.""" + ip_to_intf = IPAddressToInterface.objects.get(id=self.uuid) + if attrs.get("primary"): + if ip_to_intf.ip_address.ip_version == 4: + ip_to_intf.interface.device.primary_ip4 = ip_to_intf.ip_address + else: + ip_to_intf.interface.device.primary_ip6 = ip_to_intf.ip_address + ip_to_intf.interface.device.validated_save() + ip_to_intf.validated_save() + return super().update(attrs) + + def delete(self): + """Delete IPAddressToInterface in Nautobot from NautobotIPAddressOnInterface object.""" + ip_to_intf = IPAddressToInterface.objects.get(id=self.uuid) + super().delete() + self.adapter.job.logger.info( + f"Deleting IPAddress to Interface mapping between {self.address} and {self.device}'s {self.port} port." + ) + ip_to_intf.delete() + return self diff --git a/nautobot_ssot/integrations/citrix_adm/jobs.py b/nautobot_ssot/integrations/citrix_adm/jobs.py new file mode 100644 index 000000000..48acb18f5 --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/jobs.py @@ -0,0 +1,200 @@ +"""Jobs for Citrix ADM SSoT integration.""" + +from diffsync.enum import DiffSyncFlags +from django.templatetags.static import static +from django.urls import reverse +from nautobot.core.celery import register_jobs +from nautobot.dcim.models import Location, LocationType +from nautobot.extras.jobs import BooleanVar, Job, JSONVar, MultiObjectVar, ObjectVar +from nautobot.extras.models import ExternalIntegration +from nautobot.tenancy.models import Tenant + +from nautobot_ssot.exceptions import JobException +from nautobot_ssot.integrations.citrix_adm.diffsync.adapters import citrix_adm, nautobot +from nautobot_ssot.jobs.base import DataMapping, DataSource, DataTarget + +name = "Citrix ADM SSoT" # pylint: disable=invalid-name + + +class CitrixAdmDataSource(DataSource, Job): # pylint: disable=too-many-instance-attributes + """Citrix ADM SSoT Data Source.""" + + instances = MultiObjectVar( + model=ExternalIntegration, + queryset=ExternalIntegration.objects.all(), + display_field="display", + label="Citrix ADM Instances", + required=True, + ) + dc_loctype = ObjectVar( + model=LocationType, + queryset=LocationType.objects.all(), + query_params={"content_types": "dcim.device"}, + display_field="display", + label="Datacenter LocationType", + description="LocationType to use when importing Datacenters from Citrix ADM. Must have Device ContentType.", + required=True, + ) + parent_location = ObjectVar( + model=Location, + queryset=Location.objects.all(), + query_params={"location_type": "$dc_loctype.parent"}, + display_field="display", + label="Parent Location", + description="Parent Location to assign to imported Datacenters. Required if parent is specified on Datacenter LocationType.", + required=False, + ) + location_map = JSONVar( + label="Location Map", + description="Mapping of Datacenter name to parent and name. Ex: {'US': {'name': 'United States', 'parent': 'North America'}}.", + default={}, + required=False, + ) + hostname_mapping = JSONVar( + label="Hostname Mapping", + description="Mapping of Device hostname to Role. Ex: {'router01': 'router'}.", + default={}, + required=False, + ) + tenant = ObjectVar(model=Tenant, queryset=Tenant.objects.all(), display_field="display_name", required=False) + debug = BooleanVar(description="Enable for more verbose debug logging", default=False) + + class Meta: # pylint: disable=too-few-public-methods + """Meta data for Citrix ADM.""" + + name = "Citrix ADM to Nautobot" + data_source = "Citrix ADM" + data_target = "Nautobot" + data_source_icon = static("nautobot_ssot_citrix_adm/citrix_logo.png") + description = "Sync information from Citrix ADM to Nautobot" + field_order = [ + "dryrun", + "debug", + "instances", + "dc_loctype", + "parent_location", + "location_map", + "hostname_mapping", + "tenant", + ] + + def __init__(self): + """Initialize job objects.""" + super().__init__() + self.data = None + self.diffsync_flags = DiffSyncFlags.CONTINUE_ON_FAILURE + + @classmethod + def config_information(cls): + """Dictionary describing the configuration of this DataSource.""" + return {} + + @classmethod + def data_mappings(cls): + """List describing the data mappings involved in this DataSource.""" + return ( + DataMapping("Datacenters", None, "Locations", reverse("dcim:location_list")), + DataMapping("Devices", None, "Devices", reverse("dcim:device_list")), + DataMapping("Ports", None, "Interfaces", reverse("dcim:interface_list")), + DataMapping("Prefixes", None, "Prefixes", reverse("ipam:prefix_list")), + DataMapping("IP Addresses", None, "IP Addresses", reverse("ipam:ipaddress_list")), + ) + + def load_source_adapter(self): + """Load data from Citrix ADM into DiffSync models.""" + self.source_adapter = citrix_adm.CitrixAdmAdapter( + job=self, sync=self.sync, instances=self.instances, tenant=self.tenant + ) + self.source_adapter.load() + + def load_target_adapter(self): + """Load data from Nautobot into DiffSync models.""" + self.target_adapter = nautobot.NautobotAdapter(job=self, sync=self.sync, tenant=self.tenant) + self.target_adapter.load() + + def validate_job_settings(self): + """Validate the settings defined in the Job form are correct.""" + if ( + self.dc_loctype.parent + and not self.parent_location + and (self.location_map and not all(bool("parent" in value) for value in self.location_map.values())) + ): + self.logger.error( + f"{self.dc_loctype.name} requires a parent Location and you've not specified a parent Location. Please review your Job settings." + ) + raise JobException(message=f"Parent Location is required for {self.dc_loctype.name} LocationType.") + + def run( # pylint: disable=arguments-differ, too-many-arguments + self, dryrun, memory_profiling, instances, tenant, debug, *args, **kwargs + ): + """Perform data synchronization.""" + self.instances = instances + self.tenant = tenant + self.debug = debug + self.dryrun = dryrun + self.dc_loctype = kwargs["dc_loctype"] + self.parent_location = kwargs["parent_location"] + self.location_map = kwargs["location_map"] + self.hostname_mapping = kwargs["hostname_mapping"] + self.validate_job_settings() + self.memory_profiling = memory_profiling + super().run(dryrun=self.dryrun, memory_profiling=self.memory_profiling, *args, **kwargs) + + +class CitrixAdmDataTarget(DataTarget, Job): + """Citrix ADM SSoT Data Target.""" + + instance = ObjectVar( + model=ExternalIntegration, + queryset=ExternalIntegration.objects.all(), + display_field="display", + label="Citrix ADM Instance", + required=True, + ) + tenant = ObjectVar(model=Tenant, queryset=Tenant.objects.all(), display_field="display_name", required=False) + debug = BooleanVar(description="Enable for more verbose debug logging", default=False) + + class Meta: # pylint: disable=too-few-public-methods + """Meta data for Citrix ADM.""" + + name = "Nautobot to Citrix ADM" + data_source = "Nautobot" + data_target = "Citrix ADM" + description = "Sync information from Nautobot to Citrix ADM" + + @classmethod + def config_information(cls): + """Dictionary describing the configuration of this DataTarget.""" + return {} + + @classmethod + def data_mappings(cls): + """List describing the data mappings involved in this DataSource.""" + return () + + def load_source_adapter(self): + """Load data from Nautobot into DiffSync models.""" + self.source_adapter = nautobot.NautobotAdapter(job=self, sync=self.sync, tenant=self.tenant) + self.source_adapter.load() + + def load_target_adapter(self): + """Load data from Citrix ADM into DiffSync models.""" + self.target_adapter = citrix_adm.CitrixAdmAdapter( + job=self, sync=self.sync, instances=self.instance, tenant=self.tenant + ) + self.target_adapter.load() + + def run( # pylint: disable=arguments-differ, too-many-arguments + self, dryrun, memory_profiling, instance, tenant, debug, *args, **kwargs + ): + """Perform data synchronization.""" + self.instance = instance + self.tenant = tenant + self.debug = debug + self.dryrun = dryrun + self.memory_profiling = memory_profiling + super().run(dryrun=self.dryrun, memory_profiling=self.memory_profiling, *args, **kwargs) + + +jobs = [CitrixAdmDataSource] +register_jobs(*jobs) diff --git a/nautobot_ssot/integrations/citrix_adm/signals.py b/nautobot_ssot/integrations/citrix_adm/signals.py new file mode 100644 index 000000000..ab2768658 --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/signals.py @@ -0,0 +1,55 @@ +"""Signals triggered when Nautobot starts to perform certain actions.""" + +from nautobot.core.signals import nautobot_database_ready +from nautobot.extras.choices import CustomFieldTypeChoices + +from nautobot_ssot.utils import create_or_update_custom_field + + +def register_signals(sender): + """Register signals for IPFabric integration.""" + nautobot_database_ready.connect(nautobot_database_ready_callback, sender=sender) + + +def nautobot_database_ready_callback(sender, *, apps, **kwargs): # pylint: disable=unused-argument + """Ensure the Citrix Manufacturer is in place for DeviceTypes to use. Adds OS Version CustomField to Devices and System of Record and Last Sync'd to Site, Device, Interface, and IPAddress. + + Callback function triggered by the nautobot_database_ready signal when the Nautobot database is fully ready. + """ + # pylint: disable=invalid-name, too-many-locals + ContentType = apps.get_model("contenttypes", "ContentType") + Manufacturer = apps.get_model("dcim", "Manufacturer") + Device = apps.get_model("dcim", "Device") + Interface = apps.get_model("dcim", "Interface") + Prefix = apps.get_model("ipam", "Prefix") + IPAddress = apps.get_model("ipam", "IPAddress") + Platform = apps.get_model("dcim", "Platform") + + citrix_manu, _ = Manufacturer.objects.update_or_create(name="Citrix") + Platform.objects.update_or_create( + name="citrix.adc", + defaults={ + "name": "citrix.adc", + "napalm_driver": "netscaler", + "manufacturer": citrix_manu, + "network_driver": "citrix_netscaler", + }, + ) + ha_node_field, _ = create_or_update_custom_field( + apps, key="ha_node", field_type=CustomFieldTypeChoices.TYPE_TEXT, label="HA Node" + ) + ha_node_field.content_types.add(ContentType.objects.get_for_model(Device)) + + sor_custom_field = create_or_update_custom_field( + apps, key="system_of_record", field_type=CustomFieldTypeChoices.TYPE_TEXT, label="System of Record" + )[0] + sync_custom_field = create_or_update_custom_field( + apps, + key="last_synced_from_sor", + field_type=CustomFieldTypeChoices.TYPE_DATE, + label="Last sync from System of Record", + )[0] + + for model in [Device, Interface, Prefix, IPAddress]: + sor_custom_field.content_types.add(ContentType.objects.get_for_model(model)) + sync_custom_field.content_types.add(ContentType.objects.get_for_model(model)) diff --git a/nautobot_ssot/integrations/citrix_adm/utils/__init__.py b/nautobot_ssot/integrations/citrix_adm/utils/__init__.py new file mode 100644 index 000000000..05c104799 --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/utils/__init__.py @@ -0,0 +1 @@ +"""Utility functions for working with Citrix ADM and Nautobot.""" diff --git a/nautobot_ssot/integrations/citrix_adm/utils/citrix_adm.py b/nautobot_ssot/integrations/citrix_adm/utils/citrix_adm.py new file mode 100644 index 000000000..607de0819 --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/utils/citrix_adm.py @@ -0,0 +1,283 @@ +"""Utility functions for working with Citrix ADM.""" + +import re +from typing import List, Optional, Union + +import requests +import urllib3 +from netutils.ip import ipaddress_interface, is_ip_within, netmask_to_cidr + + +# based on client found at https://github.com/slauger/python-nitro +class CitrixNitroClient: + """Client for interacting with Citrix ADM NITRO API.""" + + def __init__( # pylint: disable=too-many-arguments + self, base_url: str, user: str, password: str, job, verify: bool = True + ): + """Initialize NITRO client. + + Args: + base_url (str): Base URL for MAS/ADM API. Must include schema, http(s). + user (str): Username to authenticate with Citrix ADM. + password (str): Password to authenticate with Citrix ADM. + verify (bool, optional): Whether to validate SSL certificate on Citrix ADM or not. Defaults to True. + job (Job): Job logger to notify users of progress. + """ + if base_url.endswith("/"): + base_url = base_url.rstrip("/") + self.url = base_url + self.username = user + self.password = password + self.headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + self.verify = verify + if self.verify is False: + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + self.job = job + + def login(self): + """Login to ADM/MAS and set authorization token to enable further communication.""" + url = "config" + objecttype = "login" + login = {"login": {"username": self.username, "password": self.password}} + payload = f"object={login}" + response = self.request(method="POST", endpoint=url, objecttype=objecttype, data=payload) + if response: + session_id = response["login"][0]["sessionid"] + self.headers["Cookie"] = f"SESSID={session_id}; path=/; SameSite=Lax; secure; HttpOnly" + else: + self.job.logger.error("Error while logging into Citrix ADM. Please validate your configuration is correct.") + raise requests.exceptions.RequestException() + + def logout(self): + """Best practice to logout when session is complete.""" + url = "config" + objecttype = "logout" + logout = {"logout": {"username": self.username, "password": self.password}} + payload = f"object={logout}" + self.headers.pop("_MPS_API_PROXY_MANAGED_INSTANCE_IP", None) + self.headers.pop("_MPS_API_PROXY_MANAGED_INSTANCE_USERNAME", None) + self.headers.pop("_MPS_API_PROXY_MANAGED_INSTANCE_PASSWORD", None) + self.request(method="POST", endpoint=url, objecttype=objecttype, data=payload) + + def request( # pylint: disable=too-many-arguments + self, + method: str, + endpoint: str, + objecttype: str = "", + objectname: str = "", + params: Optional[Union[str, dict]] = None, + data: Optional[str] = None, + ): + """Perform request of specified method to endpoint. + + Args: + method (str): HTTP method to use with request, ie GET, PUT, POST, etc. + endpoint (str): API endpoint to query. + objecttype (str, optional): Specific object type to query the API about. Defaults to "". + objectname (str, optional): Specifc object to query the API about. Defaults to "". + params (Optional[Union[str, dict]], optional): Additional parameters for the request. Defaults to None. + data (Optional[str], optional): Addiontal data payload for the request. Defaults to None. + + Returns: + dict: Dictionary of data about objectname of objecttype with specified parameters if specified. + """ + url = self.url + "/nitro/v1/" + endpoint + "/" + objecttype + + if objectname: + url += "/" + objectname + + if params: + url += "?" + + if isinstance(params, dict): + for key, value in params.items(): + url += key + "=" + value + else: + url += params + + _result = requests.request( + method=method, + url=url, + data=data, + headers=self.headers, + timeout=60, + verify=self.verify, + ) + if _result: + _result.raise_for_status() + _result = _result.json() + if _result.get("errorcode") == 0: + return _result + self.job.logger.warning(f"Failure with request: {_result['message']}") + return {} + + def get_sites(self): + """Gather all sites configured on MAS/ADM instance.""" + if self.job.debug: + self.job.logger.info("Getting sites from Citrix ADM.") + endpoint = "config" + objecttype = "mps_datacenter" + params = {"attrs": "city,zipcode,type,name,region,country,latitude,longitude,id"} + result = self.request("GET", endpoint, objecttype, params=params) + if result: + return result[objecttype] + if self.job.debug: + self.job.logger.error("Error getting sites from Citrix ADM.") + return {} + + def get_devices(self): + """Gather all devices registered to MAS/ADM instance.""" + if self.job.debug: + self.job.logger.info("Getting devices from Citrix ADM.") + endpoint = "config" + objecttype = "managed_device" + params = { + "attrs": "ip_address,hostname,gateway,mgmt_ip_address,description,serialnumber,type,display_name,netmask,datacenter_id,version,instance_state,ha_ip_address" + } + result = self.request("GET", endpoint, objecttype, params=params) + if result: + return result[objecttype] + self.job.logger.error("Error getting devices from Citrix ADM.") + return {} + + def get_nsip(self, adc): + """Gather all nsip addresses from ADC instance using ADM as proxy.""" + endpoint = "config" + objecttype = "nsip" + params = {} + self.headers["_MPS_API_PROXY_MANAGED_INSTANCE_USERNAME"] = self.username + self.headers["_MPS_API_PROXY_MANAGED_INSTANCE_PASSWORD"] = self.password + self.headers["_MPS_API_PROXY_MANAGED_INSTANCE_IP"] = adc["ip_address"] + result = self.request("GET", endpoint, objecttype, params=params) + if result: + return result[objecttype] + if self.job.debug: + self.job.logger.error(f"Error getting nsip from {adc['hostname']}") + return {} + + def get_nsip6(self, adc): + """Gather all nsip6 addresses from ADC instance using ADM as proxy.""" + endpoint = "config" + objecttype = "nsip6" + params = {} + self.headers["_MPS_API_PROXY_MANAGED_INSTANCE_USERNAME"] = self.username + self.headers["_MPS_API_PROXY_MANAGED_INSTANCE_PASSWORD"] = self.password + self.headers["_MPS_API_PROXY_MANAGED_INSTANCE_IP"] = adc["ip_address"] + result = self.request("GET", endpoint, objecttype, params=params) + if result: + return result[objecttype] + if self.job.debug: + self.job.logger.error(f"Error getting nsip6 from {adc['hostname']}") + return {} + + def get_vlan_bindings(self, adc): + """Gather all interface vlan and nsip bindings from ADC instance using ADM as proxy.""" + endpoint = "config" + objecttype = "vlan_binding" + params = {"bulkbindings": "yes"} + self.headers["_MPS_API_PROXY_MANAGED_INSTANCE_USERNAME"] = self.username + self.headers["_MPS_API_PROXY_MANAGED_INSTANCE_PASSWORD"] = self.password + self.headers["_MPS_API_PROXY_MANAGED_INSTANCE_IP"] = adc["ip_address"] + result = self.request("GET", endpoint, objecttype, params=params) + if result: + return result[objecttype] + if self.job.debug: + self.job.logger.error(f"Error getting vlan bindings from {adc['hostname']}") + return {} + + +def parse_version(version: str): + """Parse Device version from string. + + Args: + version (str): Version string from device API query. + """ + result = "" + match_pattern = r"NetScaler\s(?PNS\d+\.\d+: Build\s\d+\.\d+\.\w+)" + match = re.match(pattern=match_pattern, string=version) + if match: + result = match.group("version") + return result + + +def parse_vlan_bindings(vlan_bindings: List[dict], adc: dict, job) -> List[dict]: + """Parse VLAN Bindings from ADC.""" + ports = [] + for binding in vlan_bindings: + if binding.get("vlan_interface_binding"): + if binding.get("vlan_nsip_binding"): + for nsip in binding["vlan_nsip_binding"]: + vlan = nsip["id"] + ipaddress = nsip["ipaddress"] + netmask = netmask_to_cidr(nsip["netmask"]) + port = binding["vlan_interface_binding"][0]["ifnum"] + record = {"vlan": vlan, "ipaddress": ipaddress, "netmask": netmask, "port": port, "version": 4} + ports.append(record) + if binding.get("vlan_nsip6_binding"): + for nsip6 in binding["vlan_nsip6_binding"]: + vlan = nsip6["id"] + ipaddress, netmask = nsip6["ipaddress"].split("/") + port = binding["vlan_interface_binding"][0]["ifnum"] + record = {"vlan": vlan, "ipaddress": ipaddress, "netmask": netmask, "port": port, "version": 6} + ports.append(record) + else: + if job.debug: + job.logger.warning(f"{adc['hostname']}: VLAN {binding['id']} has no interface binding: {binding}.") + + # Account for NSIP in VLAN 1 which is not returned by get_vlan_bindings() + if vlan_bindings: + ports_dict = {port["ipaddress"]: port for port in ports} + if adc["ip_address"] not in ports_dict: + port = vlan_bindings[0]["vlan_interface_binding"][0]["ifnum"] + netmask = netmask_to_cidr(adc["netmask"]) + ipaddress = adc["ip_address"] + record = {"vlan": "1", "ipaddress": ipaddress, "netmask": netmask, "port": port, "version": 4} + ports.append(record) + + if job.debug: + job.logger.warning(f"{adc['hostname']} is using VLAN 1 for NSIP.") + + return ports + + +def parse_nsips(nsips: List[dict], ports: List[dict], adc: dict) -> List[dict]: + """Parse Netscaler IPv4 Addresses.""" + for nsip in nsips: + for port in ports: + if port["ipaddress"] == nsip["ipaddress"]: + if nsip["type"] == "NSIP": + port["tags"] = ["NSIP"] + break + + if nsip["type"] in ["SNIP", "MIP"] and port["version"] != 6: + network = str(ipaddress_interface(f"{port['ipaddress']}/{port['netmask']}", "network")) + if is_ip_within(nsip["ipaddress"], network): + _tags = ["MGMT"] if nsip["ipaddress"] == adc["mgmt_ip_address"] else [] + _tags = ["MIP"] if nsip["type"] == "MIP" else _tags + record = { + "vlan": port["vlan"], + "ipaddress": nsip["ipaddress"], + "netmask": netmask_to_cidr(nsip["netmask"]), + "port": port["port"], + "version": 4, + "tags": _tags, + } + ports.append(record) + return ports + + +def parse_nsip6s(nsip6s: List[dict], ports: List[dict]) -> List[dict]: + """Parse Netscaler IPv6 Addresses.""" + for nsip6 in nsip6s: + if nsip6["scope"] == "link-local": + vlan = nsip6["vlan"] + ipaddress, netmask = nsip6["ipv6address"].split("/") + port = "L0/1" + record = {"vlan": vlan, "ipaddress": ipaddress, "netmask": netmask, "port": port} + ports.append(record) + + return ports diff --git a/nautobot_ssot/integrations/citrix_adm/utils/nautobot.py b/nautobot_ssot/integrations/citrix_adm/utils/nautobot.py new file mode 100644 index 000000000..a6972f721 --- /dev/null +++ b/nautobot_ssot/integrations/citrix_adm/utils/nautobot.py @@ -0,0 +1,22 @@ +"""Utility functions for working with Nautobot.""" + +from typing import List + +from taggit.managers import TaggableManager + + +def get_tag_strings(list_tags: TaggableManager) -> List[str]: + """Gets string values of all Tags in a list. + + This is the opposite of the `get_tags` function. + + Args: + list_tags (TaggableManager): List of Tag objects to convert to strings. + + Returns: + List[str]: List of string values matching the Tags passed in. + """ + _strings = list(list_tags.names()) + if len(_strings) > 1: + _strings.sort() + return _strings diff --git a/nautobot_ssot/integrations/dna_center/diffsync/adapters/dna_center.py b/nautobot_ssot/integrations/dna_center/diffsync/adapters/dna_center.py index 4b9dd65b5..6b4188003 100644 --- a/nautobot_ssot/integrations/dna_center/diffsync/adapters/dna_center.py +++ b/nautobot_ssot/integrations/dna_center/diffsync/adapters/dna_center.py @@ -23,6 +23,7 @@ DnaCenterPrefix, ) from nautobot_ssot.integrations.dna_center.utils.dna_center import DnaCenterClient +from nautobot_ssot.utils import parse_hostname_for_role class DnaCenterAdapter(Adapter): @@ -309,8 +310,8 @@ def load_devices(self): self.failed_import_devices.append(dev) continue if self.job.hostname_map: - dev_role = self.conn.parse_hostname_for_role( - hostname_map=self.job.hostname_map, device_hostname=dev["hostname"] + dev_role = parse_hostname_for_role( + hostname_map=self.job.hostname_map, device_hostname=dev["hostname"], default_role="Unknown" ) if dev_role == "Unknown": dev_role = dev["role"] diff --git a/nautobot_ssot/integrations/dna_center/jobs.py b/nautobot_ssot/integrations/dna_center/jobs.py index e087ee92d..851a74083 100644 --- a/nautobot_ssot/integrations/dna_center/jobs.py +++ b/nautobot_ssot/integrations/dna_center/jobs.py @@ -2,6 +2,7 @@ from ast import literal_eval +from diffsync.enum import DiffSyncFlags from django.templatetags.static import static from django.urls import reverse from nautobot.apps.jobs import BooleanVar, JSONVar, ObjectVar, StringVar @@ -97,8 +98,9 @@ class Meta: # pylint: disable=too-few-public-methods def __init__(self): """Initiailize Job vars.""" - self.controller_group = None super().__init__() + self.controller_group = None + self.diffsync_flags = DiffSyncFlags.CONTINUE_ON_FAILURE @classmethod def config_information(cls): diff --git a/nautobot_ssot/integrations/dna_center/utils/dna_center.py b/nautobot_ssot/integrations/dna_center/utils/dna_center.py index c695e299d..00edd69c4 100644 --- a/nautobot_ssot/integrations/dna_center/utils/dna_center.py +++ b/nautobot_ssot/integrations/dna_center/utils/dna_center.py @@ -2,7 +2,7 @@ import logging import re -from typing import List, Tuple +from typing import List from dnacentersdk import api from dnacentersdk.exceptions import dnacentersdkException @@ -198,25 +198,6 @@ def get_port_status(port_info: dict): status = "Planned" return status - @staticmethod - def parse_hostname_for_role(hostname_map: List[Tuple[str, str]], device_hostname: str): - """Parse device hostname from hostname_map to get Device Role. - - Args: - hostname_map (List[Tuple[str, str]]): List of tuples containing regex to compare with hostname and associated DeviceRole name. - device_hostname (str): Hostname of Device to determine role of. - - Returns: - str: Name of DeviceRole. Defaults to Unknown. - """ - device_role = "Unknown" - if hostname_map: - for entry in hostname_map: - match = re.match(pattern=entry[0], string=device_hostname) - if match: - device_role = entry[1] - return device_role - @staticmethod def get_model_name(models: str) -> str: """Obtain DeviceType model from a list of models. diff --git a/nautobot_ssot/static/nautobot_ssot_citrix_adm/citrix_logo.png b/nautobot_ssot/static/nautobot_ssot_citrix_adm/citrix_logo.png new file mode 100644 index 000000000..1c357afd1 Binary files /dev/null and b/nautobot_ssot/static/nautobot_ssot_citrix_adm/citrix_logo.png differ diff --git a/nautobot_ssot/tests/citrix_adm/__init__.py b/nautobot_ssot/tests/citrix_adm/__init__.py new file mode 100644 index 000000000..1545f7692 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/__init__.py @@ -0,0 +1 @@ +"""Unit tests for Citrix ADM integration.""" diff --git a/nautobot_ssot/tests/citrix_adm/fixtures/__init__.py b/nautobot_ssot/tests/citrix_adm/fixtures/__init__.py new file mode 100644 index 000000000..fda7f67d4 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/fixtures/__init__.py @@ -0,0 +1,22 @@ +"""Fixtures for tests.""" + +import json + + +def load_json(path): + """Load a json file.""" + with open(path, encoding="utf-8") as file: + return json.loads(file.read()) + + +SITE_FIXTURE_SENT = load_json("./nautobot_ssot/tests/citrix_adm/fixtures/get_sites_sent.json") +SITE_FIXTURE_RECV = load_json("./nautobot_ssot/tests/citrix_adm/fixtures/get_sites_recv.json") +DEVICE_FIXTURE_SENT = load_json("./nautobot_ssot/tests/citrix_adm/fixtures/get_devices_sent.json") +DEVICE_FIXTURE_RECV = load_json("./nautobot_ssot/tests/citrix_adm/fixtures/get_devices_recv.json") +VLAN_FIXTURE_SENT = load_json("./nautobot_ssot/tests/citrix_adm/fixtures/get_vlan_bindings_sent.json") +VLAN_FIXTURE_RECV = load_json("./nautobot_ssot/tests/citrix_adm/fixtures/get_vlan_bindings_recv.json") +NSIP6_FIXTURE_SENT = load_json("./nautobot_ssot/tests/citrix_adm/fixtures/get_nsip6_sent.json") +NSIP6_FIXTURE_RECV = load_json("./nautobot_ssot/tests/citrix_adm/fixtures/get_nsip6_recv.json") +ADM_DEVICE_MAP_FIXTURE = load_json("./nautobot_ssot/tests/citrix_adm/fixtures/adm_device_map.json") +NSIP_FIXTURE_SENT = load_json("./nautobot_ssot/tests/citrix_adm/fixtures/get_nsip_sent.json") +NSIP_FIXTURE_RECV = load_json("./nautobot_ssot/tests/citrix_adm/fixtures/get_nsip_recv.json") diff --git a/nautobot_ssot/tests/citrix_adm/fixtures/adm_device_map.json b/nautobot_ssot/tests/citrix_adm/fixtures/adm_device_map.json new file mode 100644 index 000000000..51b8895c5 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/fixtures/adm_device_map.json @@ -0,0 +1,30 @@ +{ + "TEST": { + "hostname": "TEST", + "ip_address": "192.168.1.3", + "mgmt_ip_address": "192.168.1.5", + "netmask": "255.255.255.0", + "ports": [ + { + "ipaddress": "192.168.1.3", + "netmask": 24, + "port": "10/1", + "version": 4, + "vlan": "5", + "tags": [ + "NSIP" + ] + }, + { + "ipaddress": "192.168.1.5", + "netmask": 24, + "port": "0/1", + "version": 4, + "vlan": "5", + "tags": [ + "MGMT" + ] + } + ] + } +} \ No newline at end of file diff --git a/nautobot_ssot/tests/citrix_adm/fixtures/get_devices_recv.json b/nautobot_ssot/tests/citrix_adm/fixtures/get_devices_recv.json new file mode 100644 index 000000000..3cb684399 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/fixtures/get_devices_recv.json @@ -0,0 +1,62 @@ +[ + { + "gateway": "56.881.284.240", + "mgmt_ip_address": "128.08.54.48", + "description": "", + "serialnumber": "78NKST0SJ2", + "display_name": "172.18.77.78-172.18.77.79", + "type": "nsvpx", + "netmask": "255.255.255.192", + "ha_ip_address": "172.18.77.78", + "datacenter_id": "28aa2970-0160-4860-aca8-a85f89268803", + "hostname": "UYLLBFRCXM55-EA", + "ip_address": "172.18.77.79", + "version": "NetScaler NS12.1: Build 63.22.nc, Date: Oct 13 2021, 01:18:50 (64-bit)", + "instance_state": "Up" + }, + { + "gateway": "48.14.5.5", + "mgmt_ip_address": "85.52.0.128", + "description": "", + "serialnumber": "44WDBA1XS3", + "display_name": "10.62.7.168-10.62.7.169", + "type": "nsvpx", + "netmask": "255.255.255.0", + "ha_ip_address": "10.62.7.168", + "datacenter_id": "28aa2970-0160-4860-aca8-a85f89268803", + "hostname": "OLQE-WHOO-KAL-WKH-SndJhcc3-X", + "ip_address": "10.62.7.169", + "version": "NetScaler NS13.1: Build 37.38.nc, Date: Nov 23 2022, 04:42:36 (64-bit)", + "instance_state": "Up" + }, + { + "gateway": "64.131.96.4", + "mgmt_ip_address": "13.250.66.32", + "description": "", + "serialnumber": "AA7D14BP22", + "display_name": "10.220.11.138-10.220.11.139", + "type": "nsvpx", + "netmask": "255.255.255.192", + "ha_ip_address": "10.220.11.138", + "datacenter_id": "4f91c698-4a2f-4e08-bc57-827d6531a444", + "hostname": "AGSPAMKMRN53", + "ip_address": "10.220.11.139", + "version": "NetScaler NS12.1: Build 57.18.nc, Date: Jun 9 2020, 11:42:01 (64-bit)", + "instance_state": "Up" + }, + { + "gateway": "1.81.7.1", + "mgmt_ip_address": "65.61.6.121", + "description": "", + "serialnumber": "98ATECSRNJ", + "display_name": "10.62.7.111-10.62.7.112", + "type": "nsvpx", + "netmask": "255.255.255.0", + "ha_ip_address": "10.62.7.110", + "datacenter_id": "28aa2970-0160-4860-aca8-a85f89268803", + "hostname": "OGI-MSCI-IMS-Mctdgj-Pqsf-M", + "ip_address": "10.62.7.111", + "version": "NetScaler NS12.1: Build 63.22.nc, Date: Oct 13 2021, 01:18:50 (64-bit)", + "instance_state": "Up" + } +] \ No newline at end of file diff --git a/nautobot_ssot/tests/citrix_adm/fixtures/get_devices_sent.json b/nautobot_ssot/tests/citrix_adm/fixtures/get_devices_sent.json new file mode 100644 index 000000000..dcf968806 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/fixtures/get_devices_sent.json @@ -0,0 +1,73 @@ +{ + "errorcode": 0, + "message": "Done", + "operation": "get", + "resourceType": "managed_device", + "username": "a1pnautobot", + "tenant_name": "Owner", + "tenant_id": "ed2aa84e-b157-4a2b-9a3e-489149fb7842", + "resrc_total_count": 0, + "resourceName": "", + "managed_device": [ + { + "gateway": "56.881.284.240", + "mgmt_ip_address": "128.08.54.48", + "description": "", + "serialnumber": "78NKST0SJ2", + "display_name": "172.18.77.78-172.18.77.79", + "type": "nsvpx", + "netmask": "255.255.255.192", + "ha_ip_address": "172.18.77.78", + "datacenter_id": "28aa2970-0160-4860-aca8-a85f89268803", + "hostname": "UYLLBFRCXM55-EA", + "ip_address": "172.18.77.79", + "version": "NetScaler NS12.1: Build 63.22.nc, Date: Oct 13 2021, 01:18:50 (64-bit)", + "instance_state": "Up" + }, + { + "gateway": "48.14.5.5", + "mgmt_ip_address": "85.52.0.128", + "description": "", + "serialnumber": "44WDBA1XS3", + "display_name": "10.62.7.168-10.62.7.169", + "type": "nsvpx", + "netmask": "255.255.255.0", + "ha_ip_address": "10.62.7.168", + "datacenter_id": "28aa2970-0160-4860-aca8-a85f89268803", + "hostname": "OLQE-WHOO-KAL-WKH-SndJhcc3-X", + "ip_address": "10.62.7.169", + "version": "NetScaler NS13.1: Build 37.38.nc, Date: Nov 23 2022, 04:42:36 (64-bit)", + "instance_state": "Up" + }, + { + "gateway": "64.131.96.4", + "mgmt_ip_address": "13.250.66.32", + "description": "", + "serialnumber": "AA7D14BP22", + "display_name": "10.220.11.138-10.220.11.139", + "type": "nsvpx", + "netmask": "255.255.255.192", + "ha_ip_address": "10.220.11.138", + "datacenter_id": "4f91c698-4a2f-4e08-bc57-827d6531a444", + "hostname": "AGSPAMKMRN53", + "ip_address": "10.220.11.139", + "version": "NetScaler NS12.1: Build 57.18.nc, Date: Jun 9 2020, 11:42:01 (64-bit)", + "instance_state": "Up" + }, + { + "gateway": "1.81.7.1", + "mgmt_ip_address": "65.61.6.121", + "description": "", + "serialnumber": "98ATECSRNJ", + "display_name": "10.62.7.111-10.62.7.112", + "type": "nsvpx", + "netmask": "255.255.255.0", + "ha_ip_address": "10.62.7.110", + "datacenter_id": "28aa2970-0160-4860-aca8-a85f89268803", + "hostname": "OGI-MSCI-IMS-Mctdgj-Pqsf-M", + "ip_address": "10.62.7.111", + "version": "NetScaler NS12.1: Build 63.22.nc, Date: Oct 13 2021, 01:18:50 (64-bit)", + "instance_state": "Up" + } + ] +} \ No newline at end of file diff --git a/nautobot_ssot/tests/citrix_adm/fixtures/get_nsip6_recv.json b/nautobot_ssot/tests/citrix_adm/fixtures/get_nsip6_recv.json new file mode 100644 index 000000000..34f7fc162 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/fixtures/get_nsip6_recv.json @@ -0,0 +1,171 @@ +[ + [ + { + "ipv6address": "fe80::1234:5678:9abc:dev1/64", + "td": "0", + "scope": "link-local", + "iptype": [ + "NSIP" + ], + "vlan": "1", + "nd": "ENABLED", + "icmp": "ENABLED", + "vserver": "DISABLED", + "telnet": "ENABLED", + "ssh": "ENABLED", + "gui": "ENABLED", + "snmp": "ENABLED", + "ftp": "ENABLED", + "mgmtaccess": "ENABLED", + "restrictaccess": "DISABLED", + "state": "ENABLED", + "curstate": "ACTIVE", + "map": "0.0.0.0", + "decrementhoplimit": "DISABLED", + "dynamicrouting": "ENABLED", + "hostroute": "DISABLED", + "advertiseondefaultpartition": "DISABLED", + "networkroute": "DISABLED", + "tag": "0", + "ip6hostrtgw": "::", + "metric": 0, + "vserverrhilevel": "ONE_VSERVER", + "viprtadv2bsd": false, + "vipvsercount": "0", + "vipvserdowncount": "0", + "ospf6lsatype": "EXTERNAL", + "ownernode": "255", + "ownerdownresponse": "YES", + "systemtype": "HA" + } + ], + [ + { + "ipv6address": "fe80::1234:5678:9abc:dev2/64", + "td": "0", + "scope": "link-local", + "iptype": [ + "NSIP" + ], + "vlan": "1", + "nd": "ENABLED", + "icmp": "ENABLED", + "vserver": "DISABLED", + "telnet": "ENABLED", + "ssh": "ENABLED", + "gui": "ENABLED", + "snmp": "ENABLED", + "ftp": "ENABLED", + "mgmtaccess": "ENABLED", + "restrictaccess": "DISABLED", + "state": "ENABLED", + "curstate": "ACTIVE", + "map": "0.0.0.0", + "decrementhoplimit": "DISABLED", + "dynamicrouting": "ENABLED", + "hostroute": "DISABLED", + "advertiseondefaultpartition": "DISABLED", + "networkroute": "DISABLED", + "tag": "0", + "ip6hostrtgw": "::", + "metric": 0, + "vserverrhilevel": "ONE_VSERVER", + "viprtadv2bsd": false, + "vipvsercount": "0", + "vipvserdowncount": "0", + "ospf6lsatype": "EXTERNAL", + "ownernode": "255", + "ownerdownresponse": "YES", + "systemtype": "HA", + "ndowner": "255", + "operationalndowner": "0", + "mptcpadvertise": "NO" + } + ], + [ + { + "ipv6address": "fe80::1234:5678:9abc:dev3/64", + "td": "0", + "scope": "link-local", + "iptype": [ + "NSIP" + ], + "vlan": "1", + "nd": "ENABLED", + "icmp": "ENABLED", + "vserver": "DISABLED", + "telnet": "ENABLED", + "ssh": "ENABLED", + "gui": "ENABLED", + "snmp": "ENABLED", + "ftp": "ENABLED", + "mgmtaccess": "ENABLED", + "restrictaccess": "DISABLED", + "state": "ENABLED", + "curstate": "ACTIVE", + "map": "0.0.0.0", + "decrementhoplimit": "DISABLED", + "dynamicrouting": "ENABLED", + "hostroute": "DISABLED", + "advertiseondefaultpartition": "DISABLED", + "networkroute": "DISABLED", + "tag": "0", + "ip6hostrtgw": "::", + "metric": 0, + "vserverrhilevel": "ONE_VSERVER", + "viprtadv2bsd": false, + "vipvsercount": "0", + "vipvserdowncount": "0", + "ospf6lsatype": "EXTERNAL", + "ownernode": "255", + "ownerdownresponse": "YES", + "systemtype": "HA", + "ndowner": "255", + "operationalndowner": "0", + "mptcpadvertise": "NO" + } + ], + [ + { + "ipv6address": "fe80::1234:5678:9abc:dev4/64", + "td": "0", + "scope": "link-local", + "iptype": [ + "NSIP" + ], + "vlan": "1", + "nd": "ENABLED", + "icmp": "ENABLED", + "vserver": "DISABLED", + "telnet": "ENABLED", + "ssh": "ENABLED", + "gui": "ENABLED", + "snmp": "ENABLED", + "ftp": "ENABLED", + "mgmtaccess": "ENABLED", + "restrictaccess": "DISABLED", + "state": "ENABLED", + "curstate": "ACTIVE", + "map": "0.0.0.0", + "decrementhoplimit": "DISABLED", + "dynamicrouting": "ENABLED", + "hostroute": "DISABLED", + "advertiseondefaultpartition": "DISABLED", + "networkroute": "DISABLED", + "tag": "0", + "ip6hostrtgw": "::", + "metric": 0, + "vserverrhilevel": "ONE_VSERVER", + "viprtadv2bsd": false, + "vipvsercount": "0", + "vipvserdowncount": "0", + "ospf6lsatype": "EXTERNAL", + "ownernode": "255", + "ownerdownresponse": "YES", + "systemtype": "HA", + "ndowner": "255", + "operationalndowner": "0", + "mptcpadvertise": "NO" + } + ] +] \ No newline at end of file diff --git a/nautobot_ssot/tests/citrix_adm/fixtures/get_nsip6_sent.json b/nautobot_ssot/tests/citrix_adm/fixtures/get_nsip6_sent.json new file mode 100644 index 000000000..a93462870 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/fixtures/get_nsip6_sent.json @@ -0,0 +1,191 @@ +[ + { + "errorcode": 0, + "message": "Done", + "severity": "NONE", + "nsip6": [ + { + "ipv6address": "fe80::1234:5678:9abc:dev1/64", + "td": "0", + "scope": "link-local", + "iptype": [ + "NSIP" + ], + "vlan": "1", + "nd": "ENABLED", + "icmp": "ENABLED", + "vserver": "DISABLED", + "telnet": "ENABLED", + "ssh": "ENABLED", + "gui": "ENABLED", + "snmp": "ENABLED", + "ftp": "ENABLED", + "mgmtaccess": "ENABLED", + "restrictaccess": "DISABLED", + "state": "ENABLED", + "curstate": "ACTIVE", + "map": "0.0.0.0", + "decrementhoplimit": "DISABLED", + "dynamicrouting": "ENABLED", + "hostroute": "DISABLED", + "advertiseondefaultpartition": "DISABLED", + "networkroute": "DISABLED", + "tag": "0", + "ip6hostrtgw": "::", + "metric": 0, + "vserverrhilevel": "ONE_VSERVER", + "viprtadv2bsd": false, + "vipvsercount": "0", + "vipvserdowncount": "0", + "ospf6lsatype": "EXTERNAL", + "ownernode": "255", + "ownerdownresponse": "YES", + "systemtype": "HA" + } + ] + }, + { + "errorcode": 0, + "message": "Done", + "severity": "NONE", + "nsip6": [ + { + "ipv6address": "fe80::1234:5678:9abc:dev2/64", + "td": "0", + "scope": "link-local", + "iptype": [ + "NSIP" + ], + "vlan": "1", + "nd": "ENABLED", + "icmp": "ENABLED", + "vserver": "DISABLED", + "telnet": "ENABLED", + "ssh": "ENABLED", + "gui": "ENABLED", + "snmp": "ENABLED", + "ftp": "ENABLED", + "mgmtaccess": "ENABLED", + "restrictaccess": "DISABLED", + "state": "ENABLED", + "curstate": "ACTIVE", + "map": "0.0.0.0", + "decrementhoplimit": "DISABLED", + "dynamicrouting": "ENABLED", + "hostroute": "DISABLED", + "advertiseondefaultpartition": "DISABLED", + "networkroute": "DISABLED", + "tag": "0", + "ip6hostrtgw": "::", + "metric": 0, + "vserverrhilevel": "ONE_VSERVER", + "viprtadv2bsd": false, + "vipvsercount": "0", + "vipvserdowncount": "0", + "ospf6lsatype": "EXTERNAL", + "ownernode": "255", + "ownerdownresponse": "YES", + "systemtype": "HA", + "ndowner": "255", + "operationalndowner": "0", + "mptcpadvertise": "NO" + } + ] + }, + { + "errorcode": 0, + "message": "Done", + "severity": "NONE", + "nsip6": [ + { + "ipv6address": "fe80::1234:5678:9abc:dev3/64", + "td": "0", + "scope": "link-local", + "iptype": [ + "NSIP" + ], + "vlan": "1", + "nd": "ENABLED", + "icmp": "ENABLED", + "vserver": "DISABLED", + "telnet": "ENABLED", + "ssh": "ENABLED", + "gui": "ENABLED", + "snmp": "ENABLED", + "ftp": "ENABLED", + "mgmtaccess": "ENABLED", + "restrictaccess": "DISABLED", + "state": "ENABLED", + "curstate": "ACTIVE", + "map": "0.0.0.0", + "decrementhoplimit": "DISABLED", + "dynamicrouting": "ENABLED", + "hostroute": "DISABLED", + "advertiseondefaultpartition": "DISABLED", + "networkroute": "DISABLED", + "tag": "0", + "ip6hostrtgw": "::", + "metric": 0, + "vserverrhilevel": "ONE_VSERVER", + "viprtadv2bsd": false, + "vipvsercount": "0", + "vipvserdowncount": "0", + "ospf6lsatype": "EXTERNAL", + "ownernode": "255", + "ownerdownresponse": "YES", + "systemtype": "HA", + "ndowner": "255", + "operationalndowner": "0", + "mptcpadvertise": "NO" + } + ] + }, + { + "errorcode": 0, + "message": "Done", + "severity": "NONE", + "nsip6": [ + { + "ipv6address": "fe80::1234:5678:9abc:dev4/64", + "td": "0", + "scope": "link-local", + "iptype": [ + "NSIP" + ], + "vlan": "1", + "nd": "ENABLED", + "icmp": "ENABLED", + "vserver": "DISABLED", + "telnet": "ENABLED", + "ssh": "ENABLED", + "gui": "ENABLED", + "snmp": "ENABLED", + "ftp": "ENABLED", + "mgmtaccess": "ENABLED", + "restrictaccess": "DISABLED", + "state": "ENABLED", + "curstate": "ACTIVE", + "map": "0.0.0.0", + "decrementhoplimit": "DISABLED", + "dynamicrouting": "ENABLED", + "hostroute": "DISABLED", + "advertiseondefaultpartition": "DISABLED", + "networkroute": "DISABLED", + "tag": "0", + "ip6hostrtgw": "::", + "metric": 0, + "vserverrhilevel": "ONE_VSERVER", + "viprtadv2bsd": false, + "vipvsercount": "0", + "vipvserdowncount": "0", + "ospf6lsatype": "EXTERNAL", + "ownernode": "255", + "ownerdownresponse": "YES", + "systemtype": "HA", + "ndowner": "255", + "operationalndowner": "0", + "mptcpadvertise": "NO" + } + ] + } +] \ No newline at end of file diff --git a/nautobot_ssot/tests/citrix_adm/fixtures/get_nsip_recv.json b/nautobot_ssot/tests/citrix_adm/fixtures/get_nsip_recv.json new file mode 100644 index 000000000..be9df5b8c --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/fixtures/get_nsip_recv.json @@ -0,0 +1,87 @@ +[ + { + "ipaddress": "192.168.0.1", + "td": "0", + "type": "NSIP", + "netmask": "255.255.255.0", + "flags": "40", + "arp": "ENABLED", + "icmp": "ENABLED", + "vserver": "DISABLED", + "telnet": "ENABLED", + "ssh": "ENABLED", + "gui": "SECUREONLY", + "snmp": "ENABLED", + "ftp": "ENABLED", + "mgmtaccess": "ENABLED", + "restrictaccess": "DISABLED", + "decrementttl": "DISABLED", + "dynamicrouting": "ENABLED", + "hostroute": "DISABLED", + "advertiseondefaultpartition": "DISABLED", + "networkroute": "DISABLED", + "tag": "0", + "hostrtgwact": "0.0.0.0", + "metric": 0, + "ospfareaval": "0", + "vserverrhilevel": "ONE_VSERVER", + "viprtadv2bsd": false, + "vipvsercount": "0", + "vipvserdowncount": "0", + "vipvsrvrrhiactivecount": "0", + "vipvsrvrrhiactiveupcount": "0", + "ospflsatype": "TYPE5", + "state": "ENABLED", + "freeports": "1032094", + "iptype": [ + "NSIP" + ], + "icmpresponse": "NONE", + "ownernode": "255", + "arpresponse": "NONE", + "ownerdownresponse": "YES" + }, + { + "ipaddress": "192.168.0.2", + "td": "0", + "type": "SNIP", + "netmask": "255.255.255.0", + "flags": "4", + "arp": "ENABLED", + "icmp": "ENABLED", + "vserver": "DISABLED", + "telnet": "ENABLED", + "ssh": "ENABLED", + "gui": "ENABLED", + "snmp": "ENABLED", + "ftp": "ENABLED", + "mgmtaccess": "ENABLED", + "restrictaccess": "DISABLED", + "decrementttl": "DISABLED", + "dynamicrouting": "DISABLED", + "hostroute": "DISABLED", + "advertiseondefaultpartition": "DISABLED", + "networkroute": "DISABLED", + "tag": "0", + "hostrtgwact": "0.0.0.0", + "metric": 0, + "ospfareaval": "0", + "vserverrhilevel": "ONE_VSERVER", + "viprtadv2bsd": true, + "vipvsercount": "0", + "vipvserdowncount": "0", + "vipvsrvrrhiactivecount": "0", + "vipvsrvrrhiactiveupcount": "0", + "ospflsatype": "TYPE5", + "state": "ENABLED", + "freeports": "1032080", + "iptype": [ + "SNIP", + "GSLBsiteIP" + ], + "icmpresponse": "NONE", + "ownernode": "255", + "arpresponse": "NONE", + "ownerdownresponse": "YES" + } +] \ No newline at end of file diff --git a/nautobot_ssot/tests/citrix_adm/fixtures/get_nsip_sent.json b/nautobot_ssot/tests/citrix_adm/fixtures/get_nsip_sent.json new file mode 100644 index 000000000..3cb665ece --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/fixtures/get_nsip_sent.json @@ -0,0 +1,92 @@ +{ + "errorcode": 0, + "message": "Done", + "severity": "NONE", + "nsip": [ + { + "ipaddress": "192.168.0.1", + "td": "0", + "type": "NSIP", + "netmask": "255.255.255.0", + "flags": "40", + "arp": "ENABLED", + "icmp": "ENABLED", + "vserver": "DISABLED", + "telnet": "ENABLED", + "ssh": "ENABLED", + "gui": "SECUREONLY", + "snmp": "ENABLED", + "ftp": "ENABLED", + "mgmtaccess": "ENABLED", + "restrictaccess": "DISABLED", + "decrementttl": "DISABLED", + "dynamicrouting": "ENABLED", + "hostroute": "DISABLED", + "advertiseondefaultpartition": "DISABLED", + "networkroute": "DISABLED", + "tag": "0", + "hostrtgwact": "0.0.0.0", + "metric": 0, + "ospfareaval": "0", + "vserverrhilevel": "ONE_VSERVER", + "viprtadv2bsd": false, + "vipvsercount": "0", + "vipvserdowncount": "0", + "vipvsrvrrhiactivecount": "0", + "vipvsrvrrhiactiveupcount": "0", + "ospflsatype": "TYPE5", + "state": "ENABLED", + "freeports": "1032094", + "iptype": [ + "NSIP" + ], + "icmpresponse": "NONE", + "ownernode": "255", + "arpresponse": "NONE", + "ownerdownresponse": "YES" + }, + { + "ipaddress": "192.168.0.2", + "td": "0", + "type": "SNIP", + "netmask": "255.255.255.0", + "flags": "4", + "arp": "ENABLED", + "icmp": "ENABLED", + "vserver": "DISABLED", + "telnet": "ENABLED", + "ssh": "ENABLED", + "gui": "ENABLED", + "snmp": "ENABLED", + "ftp": "ENABLED", + "mgmtaccess": "ENABLED", + "restrictaccess": "DISABLED", + "decrementttl": "DISABLED", + "dynamicrouting": "DISABLED", + "hostroute": "DISABLED", + "advertiseondefaultpartition": "DISABLED", + "networkroute": "DISABLED", + "tag": "0", + "hostrtgwact": "0.0.0.0", + "metric": 0, + "ospfareaval": "0", + "vserverrhilevel": "ONE_VSERVER", + "viprtadv2bsd": true, + "vipvsercount": "0", + "vipvserdowncount": "0", + "vipvsrvrrhiactivecount": "0", + "vipvsrvrrhiactiveupcount": "0", + "ospflsatype": "TYPE5", + "state": "ENABLED", + "freeports": "1032080", + "iptype": [ + "SNIP", + "GSLBsiteIP" + ], + "icmpresponse": "NONE", + "ownernode": "255", + "arpresponse": "NONE", + "ownerdownresponse": "YES" + } + ] +} \ No newline at end of file diff --git a/nautobot_ssot/tests/citrix_adm/fixtures/get_sites_recv.json b/nautobot_ssot/tests/citrix_adm/fixtures/get_sites_recv.json new file mode 100644 index 000000000..b7c246b8c --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/fixtures/get_sites_recv.json @@ -0,0 +1,57 @@ +[ + { + "city": "", + "zipcode": "", + "type": "1", + "name": "Default", + "region": "", + "country": "", + "longitude": "-1", + "id": "0c686cbd-dd4e-4402-afb3-0294f5b4cb93", + "latitude": "-1" + }, + { + "city": "Atlanta", + "zipcode": "30009", + "type": "1", + "name": "Delta HQ", + "region": "East", + "country": "USA", + "longitude": "-84.320000", + "id": "28aa2970-0160-4860-aca8-a85f89268803", + "latitude": "34.030000" + }, + { + "city": "Las Vegas", + "zipcode": "89044", + "type": "1", + "name": "ARIA", + "region": "West", + "country": "USA", + "longitude": "-115.1768183", + "id": "4f91c698-4a2f-4e08-bc57-827d6531a444", + "latitude": "36.1071859" + }, + { + "city": "Austin", + "zipcode": "78732", + "type": "1", + "name": "Apple Inc.", + "region": "South", + "country": "USA", + "longitude": "-97.734766", + "id": "6fa14721-299c-4d5a-b584-75a8a3dddadc", + "latitude": "30.432546" + }, + { + "city": "New York City", + "zipcode": "10018", + "type": "1", + "name": "NTC Corporate HQ", + "region": "North", + "country": "USA", + "longitude": "-73.989429", + "id": "7d29e100-ae0c-4580-ba86-b72df0b6cfd8", + "latitude": "40.753146" + } +] \ No newline at end of file diff --git a/nautobot_ssot/tests/citrix_adm/fixtures/get_sites_sent.json b/nautobot_ssot/tests/citrix_adm/fixtures/get_sites_sent.json new file mode 100644 index 000000000..dfbcdd286 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/fixtures/get_sites_sent.json @@ -0,0 +1,68 @@ +{ + "errorcode": 0, + "message": "Done", + "operation": "get", + "resourceType": "mps_datacenter", + "username": "r1ohvxgmgbt", + "tenant_name": "Owner", + "tenant_id": "ce7dl08m-m175-0e3v-1o1u-181043bp8045", + "resrc_total_count": 0, + "resourceName": "", + "mps_datacenter": [ + { + "city": "", + "zipcode": "", + "type": "1", + "name": "Default", + "region": "", + "country": "", + "longitude": "-1", + "id": "0c686cbd-dd4e-4402-afb3-0294f5b4cb93", + "latitude": "-1" + }, + { + "city": "Atlanta", + "zipcode": "30009", + "type": "1", + "name": "Delta HQ", + "region": "East", + "country": "USA", + "longitude": "-84.320000", + "id": "28aa2970-0160-4860-aca8-a85f89268803", + "latitude": "34.030000" + }, + { + "city": "Las Vegas", + "zipcode": "89044", + "type": "1", + "name": "ARIA", + "region": "West", + "country": "USA", + "longitude": "-115.1768183", + "id": "4f91c698-4a2f-4e08-bc57-827d6531a444", + "latitude": "36.1071859" + }, + { + "city": "Austin", + "zipcode": "78732", + "type": "1", + "name": "Apple Inc.", + "region": "South", + "country": "USA", + "longitude": "-97.734766", + "id": "6fa14721-299c-4d5a-b584-75a8a3dddadc", + "latitude": "30.432546" + }, + { + "city": "New York City", + "zipcode": "10018", + "type": "1", + "name": "NTC Corporate HQ", + "region": "North", + "country": "USA", + "longitude": "-73.989429", + "id": "7d29e100-ae0c-4580-ba86-b72df0b6cfd8", + "latitude": "40.753146" + } + ] +} \ No newline at end of file diff --git a/nautobot_ssot/tests/citrix_adm/fixtures/get_vlan_bindings_recv.json b/nautobot_ssot/tests/citrix_adm/fixtures/get_vlan_bindings_recv.json new file mode 100644 index 000000000..b5c1a6877 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/fixtures/get_vlan_bindings_recv.json @@ -0,0 +1,146 @@ +[ + [ + { + "id": "1", + "vlan_interface_binding": [ + { + "id": "1", + "ifnum": "LO/1", + "tagged": false, + "stateflag": "4" + } + ] + }, + { + "id": "80", + "vlan_interface_binding": [ + { + "id": "80", + "ifnum": "10/1", + "tagged": true, + "stateflag": "4" + } + ], + "vlan_nsip_binding": [ + { + "id": "80", + "ipaddress": "192.168.0.1", + "netmask": "255.255.255.0", + "td": "0", + "stateflag": "1", + "ownergroup": "" + } + ] + } + ], + [ + { + "id": "1", + "vlan_interface_binding": [ + { + "id": "1", + "ifnum": "10/1", + "tagged": false, + "stateflag": "4" + }, + { + "id": "1", + "ifnum": "LO/1", + "tagged": false, + "stateflag": "4" + } + ] + }, + { + "id": "363", + "vlan_interface_binding": [ + { + "id": "363", + "ifnum": "10/1", + "tagged": true, + "stateflag": "4" + } + ], + "vlan_nsip_binding": [ + { + "id": "363", + "ipaddress": "192.168.1.2", + "netmask": "255.255.255.0", + "stateflag": "1" + } + ] + } + ], + [ + { + "id": "1", + "vlan_interface_binding": [ + { + "id": "1", + "ifnum": "LO/1", + "tagged": false, + "stateflag": "4" + } + ] + }, + { + "id": "5", + "vlan_interface_binding": [ + { + "id": "5", + "ifnum": "1/1", + "tagged": false, + "stateflag": "4" + } + ], + "vlan_nsip_binding": [ + { + "id": "5", + "ipaddress": "192.168.1.3", + "netmask": "255.255.255.224", + "stateflag": "1" + } + ] + } + ], + [ + { + "id": "1", + "vlan_interface_binding": [ + { + "id": "1", + "ifnum": "10/1", + "tagged": false, + "stateflag": "4" + }, + { + "id": "1", + "ifnum": "LO/1", + "tagged": false, + "stateflag": "4" + } + ] + }, + { + "id": "250", + "vlan_interface_binding": [ + { + "id": "250", + "ifnum": "10/1", + "tagged": true, + "stateflag": "4" + } + ], + "vlan_nsip_binding": [ + { + "id": "250", + "ipaddress": "192.168.1.4", + "netmask": "255.255.255.0", + "td": "0", + "stateflag": "1", + "ownergroup": "" + } + ] + } + ] +] \ No newline at end of file diff --git a/nautobot_ssot/tests/citrix_adm/fixtures/get_vlan_bindings_sent.json b/nautobot_ssot/tests/citrix_adm/fixtures/get_vlan_bindings_sent.json new file mode 100644 index 000000000..88f57f129 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/fixtures/get_vlan_bindings_sent.json @@ -0,0 +1,166 @@ +[ + { + "errorcode": 0, + "message": "Done", + "severity": "NONE", + "vlan_binding": [ + { + "id": "1", + "vlan_interface_binding": [ + { + "id": "1", + "ifnum": "LO/1", + "tagged": false, + "stateflag": "4" + } + ] + }, + { + "id": "80", + "vlan_interface_binding": [ + { + "id": "80", + "ifnum": "10/1", + "tagged": true, + "stateflag": "4" + } + ], + "vlan_nsip_binding": [ + { + "id": "80", + "ipaddress": "192.168.0.1", + "netmask": "255.255.255.0", + "td": "0", + "stateflag": "1", + "ownergroup": "" + } + ] + } + ] + }, + { + "errorcode": 0, + "message": "Done", + "severity": "NONE", + "vlan_binding": [ + { + "id": "1", + "vlan_interface_binding": [ + { + "id": "1", + "ifnum": "10/1", + "tagged": false, + "stateflag": "4" + }, + { + "id": "1", + "ifnum": "LO/1", + "tagged": false, + "stateflag": "4" + } + ] + }, + { + "id": "363", + "vlan_interface_binding": [ + { + "id": "363", + "ifnum": "10/1", + "tagged": true, + "stateflag": "4" + } + ], + "vlan_nsip_binding": [ + { + "id": "363", + "ipaddress": "192.168.1.2", + "netmask": "255.255.255.0", + "stateflag": "1" + } + ] + } + ] + }, + { + "errorcode": 0, + "message": "Done", + "severity": "NONE", + "vlan_binding": [ + { + "id": "1", + "vlan_interface_binding": [ + { + "id": "1", + "ifnum": "LO/1", + "tagged": false, + "stateflag": "4" + } + ] + }, + { + "id": "5", + "vlan_interface_binding": [ + { + "id": "5", + "ifnum": "1/1", + "tagged": false, + "stateflag": "4" + } + ], + "vlan_nsip_binding": [ + { + "id": "5", + "ipaddress": "192.168.1.3", + "netmask": "255.255.255.224", + "stateflag": "1" + } + ] + } + ] + }, + { + "errorcode": 0, + "message": "Done", + "severity": "NONE", + "vlan_binding": [ + { + "id": "1", + "vlan_interface_binding": [ + { + "id": "1", + "ifnum": "10/1", + "tagged": false, + "stateflag": "4" + }, + { + "id": "1", + "ifnum": "LO/1", + "tagged": false, + "stateflag": "4" + } + ] + }, + { + "id": "250", + "vlan_interface_binding": [ + { + "id": "250", + "ifnum": "10/1", + "tagged": true, + "stateflag": "4" + } + ], + "vlan_nsip_binding": [ + { + "id": "250", + "ipaddress": "192.168.1.4", + "netmask": "255.255.255.0", + "td": "0", + "stateflag": "1", + "ownergroup": "" + } + ] + } + ] + } +] \ No newline at end of file diff --git a/nautobot_ssot/tests/citrix_adm/test_adapters_citrix_adm.py b/nautobot_ssot/tests/citrix_adm/test_adapters_citrix_adm.py new file mode 100644 index 000000000..28f4ce3d1 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/test_adapters_citrix_adm.py @@ -0,0 +1,158 @@ +"""Test Citrix ADM adapter.""" + +from unittest.mock import MagicMock + +from diffsync.exceptions import ObjectNotFound +from nautobot.core.testing import TransactionTestCase +from nautobot.extras.models import JobResult + +from nautobot_ssot.integrations.citrix_adm.diffsync.adapters.citrix_adm import CitrixAdmAdapter +from nautobot_ssot.integrations.citrix_adm.jobs import CitrixAdmDataSource +from nautobot_ssot.tests.citrix_adm.fixtures import ( + ADM_DEVICE_MAP_FIXTURE, + DEVICE_FIXTURE_RECV, + NSIP6_FIXTURE_RECV, + SITE_FIXTURE_RECV, + VLAN_FIXTURE_RECV, +) + + +class TestCitrixAdmAdapterTestCase(TransactionTestCase): # pylint: disable=too-many-instance-attributes + """Test NautobotSsotCitrixAdmAdapter class.""" + + databases = ("default", "job_logs") + + def __init__(self, *args, **kwargs): + """Initialize test case.""" + self.sor_cf = None + self.status_active = None + self.hq_site = None + self.test_dev = None + self.intf = None + self.addr = None + super().__init__(*args, **kwargs) + + def setUp(self): + """Configure shared objects for test cases.""" + super().setUp() + self.instance = MagicMock() + self.instance.name = "Test" + self.instance.remote_url = "https://test.example.com" + self.instance.verify_ssl = True + + self.citrix_adm_client = MagicMock() + self.citrix_adm_client.get_sites.return_value = SITE_FIXTURE_RECV + self.citrix_adm_client.get_devices.return_value = DEVICE_FIXTURE_RECV + self.citrix_adm_client.get_vlan_bindings.side_effect = VLAN_FIXTURE_RECV + self.citrix_adm_client.get_nsip6.side_effect = NSIP6_FIXTURE_RECV + self.job = CitrixAdmDataSource() + self.job.debug = True + self.job.location_map = {} + self.job.parent_location = None + self.job.hostname_mapping = {} + self.job.logger.warning = MagicMock() + self.job.logger.info = MagicMock() + self.job.job_result = JobResult.objects.create( + name=self.job.class_path, task_name="fake task", worker="default" + ) + self.citrix_adm = CitrixAdmAdapter(job=self.job, sync=None, instances=[self.instance]) + self.citrix_adm.conn = self.citrix_adm_client + + def test_load_site(self): + """Test Nautobot SSoT Citrix ADM load_site() function.""" + self.citrix_adm.load_site(site_info=SITE_FIXTURE_RECV[2]) + self.assertEqual( + {"ARIA__West"}, + {site.get_unique_id() for site in self.citrix_adm.get_all("datacenter")}, + ) + self.job.logger.info.assert_called_with("Loaded Datacenter from Citrix ADM: ARIA") + + def test_load_site_w_location_map(self): + """Test Nautobot SSoT Citrix ADM load_site() function with location_map from Job form.""" + site_info = SITE_FIXTURE_RECV[3] + self.job.debug = True + self.job.location_map = {"Apple Inc.": {"name": "Apple", "parent": "Cupertino"}} + self.citrix_adm.load_site(site_info=site_info) + self.assertEqual( + {"Apple__Cupertino"}, + {site.get_unique_id() for site in self.citrix_adm.get_all("datacenter")}, + ) + self.job.logger.info.assert_called_with("Loaded Datacenter from Citrix ADM: Apple") + + def test_load_devices(self): + """Test the Nautobot SSoT Citrix ADM load_devices() function.""" + self.citrix_adm.adm_site_map[DEVICE_FIXTURE_RECV[0]["datacenter_id"]] = SITE_FIXTURE_RECV[1] + self.citrix_adm_client.get_devices.return_value = [DEVICE_FIXTURE_RECV[0]] + self.citrix_adm.load_devices() + self.assertEqual( + {"UYLLBFRCXM55-EA"}, + {dev.get_unique_id() for dev in self.citrix_adm.get_all("device")}, + ) + + def test_load_devices_duplicate(self): + """Test the Nautobot SSoT Citrix ADM load_devices() function with duplicate devices.""" + self.citrix_adm.adm_site_map[DEVICE_FIXTURE_RECV[3]["datacenter_id"]] = SITE_FIXTURE_RECV[2] + self.citrix_adm_client.get_devices.return_value = [DEVICE_FIXTURE_RECV[3]] + self.citrix_adm.load_devices() + self.citrix_adm.load_devices() + self.job.logger.warning.assert_called_with( + "Duplicate Device attempting to be loaded: OGI-MSCI-IMS-Mctdgj-Pqsf-M" + ) + + def test_load_devices_without_hostname(self): + """Test the Nautobot SSoT Citrix ADM load_devices() function with a device missing hostname.""" + self.citrix_adm_client.get_devices.return_value = [{"hostname": ""}] + self.citrix_adm.load_devices() + self.job.logger.warning.assert_called_with("Device without hostname will not be loaded. {'hostname': ''}") + + def test_load_ports(self): + """Test the Nautobot SSoT Citrix ADM load_ports() function.""" + self.citrix_adm.adm_device_map = ADM_DEVICE_MAP_FIXTURE + self.citrix_adm.get = MagicMock() + self.citrix_adm.get.side_effect = [ObjectNotFound, MagicMock(), ObjectNotFound, MagicMock()] + self.citrix_adm.load_ports() + expected_ports = { + f"{port['port']}__{adc['hostname']}" + for _, adc in self.citrix_adm.adm_device_map.items() + for port in adc["ports"] + } + expected_ports = list(expected_ports) + actual_ports = [port.get_unique_id() for port in self.citrix_adm.get_all("port")] + self.assertEqual(sorted(expected_ports), sorted(actual_ports)) + + def test_load_addresses(self): + """Test the Nautobot SSoT Citrix ADM load_addresses() function.""" + self.citrix_adm.adm_device_map = ADM_DEVICE_MAP_FIXTURE + self.citrix_adm.load_prefix = MagicMock() + self.citrix_adm.load_address = MagicMock() + self.citrix_adm.load_address_to_interface = MagicMock() + self.citrix_adm.load_addresses() + self.citrix_adm.load_prefix.assert_called_with(prefix="192.168.1.0/24") + self.citrix_adm.load_address.assert_called_with( + address="192.168.1.5/24", + prefix="192.168.1.0/24", + tags=["MGMT"], + ) + self.citrix_adm.load_address_to_interface.assert_called_with( + address="192.168.1.5/24", device="TEST", port="0/1", primary=True + ) + + def test_load_prefix(self): + """Test the Nautobot SSoT Citrix ADM load_prefix() function.""" + self.citrix_adm.load_prefix(prefix="10.0.0.0/16") + self.assertEqual({"10.0.0.0/16__Global"}, {pf.get_unique_id() for pf in self.citrix_adm.get_all("prefix")}) + + def test_load_address(self): + """Test the Nautobot SSoT Citrix ADM load_address() function.""" + self.citrix_adm.load_address(address="10.0.0.1/24", prefix="10.0.0.0/24", tags=["TEST"]) + self.assertEqual( + {"10.0.0.1/24__10.0.0.0/24"}, + {addr.get_unique_id() for addr in self.citrix_adm.get_all("address")}, + ) + + def test_load_address_to_interface(self): + """Test the Nautobot SSoT Citrix ADM load_address_to_interface() function.""" + self.citrix_adm.load_address_to_interface(address="10.0.0.1/24", device="TEST", port="mgmt", primary=True) + self.assertEqual( + {"10.0.0.1/24__TEST__mgmt"}, {map.get_unique_id() for map in self.citrix_adm.get_all("ip_on_intf")} + ) diff --git a/nautobot_ssot/tests/citrix_adm/test_adapters_nautobot.py b/nautobot_ssot/tests/citrix_adm/test_adapters_nautobot.py new file mode 100644 index 000000000..84d4cb621 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/test_adapters_nautobot.py @@ -0,0 +1,239 @@ +"""Test Nautobot adapter.""" + +from unittest.mock import MagicMock + +from diffsync.exceptions import ObjectNotFound +from django.contrib.contenttypes.models import ContentType +from django.db.models import ProtectedError +from nautobot.core.testing import TransactionTestCase +from nautobot.dcim.models import ( + Device, + DeviceType, + Interface, + Location, + LocationType, + Manufacturer, +) +from nautobot.extras.models import JobResult, Role, Status +from nautobot.ipam.models import IPAddress, IPAddressToInterface, Namespace, Prefix +from nautobot.tenancy.models import Tenant + +from nautobot_ssot.integrations.citrix_adm.diffsync.adapters.nautobot import NautobotAdapter +from nautobot_ssot.integrations.citrix_adm.jobs import CitrixAdmDataSource + + +class NautobotDiffSyncTestCase(TransactionTestCase): + """Test the NautobotAdapter class.""" + + databases = ("default", "job_logs") + + def __init__(self, *args, **kwargs): + """Initialize shared variables.""" + super().__init__(*args, **kwargs) + self.hq_site = None + self.ny_region = None + + def setUp(self): # pylint: disable=too-many-locals + """Per-test-case data setup.""" + super().setUp() + self.status_active = Status.objects.get(name="Active") + + self.job = CitrixAdmDataSource() + self.job.job_result = JobResult.objects.create( + name=self.job.class_path, task_name="fake task", worker="default" + ) + self.nb_adapter = NautobotAdapter(job=self.job, sync=None) + self.job.logger.info = MagicMock() + self.job.logger.warning = MagicMock() + self.build_nautobot_objects() + + def build_nautobot_objects(self): + """Build out Nautobot objects to test loading.""" + test_tenant = Tenant.objects.get_or_create(name="Test")[0] + region_type = LocationType.objects.get_or_create(name="Region", nestable=True)[0] + self.ny_region = Location.objects.create(name="NY", location_type=region_type, status=self.status_active) + self.ny_region.validated_save() + + site_type = LocationType.objects.get_or_create(name="Site", parent=region_type)[0] + site_type.content_types.add(ContentType.objects.get_for_model(Device)) + self.job.dc_loctype = site_type + self.job.parent_location = self.ny_region + self.hq_site = Location.objects.create( + parent=self.ny_region, name="HQ", location_type=site_type, status=self.status_active + ) + self.hq_site.validated_save() + + citrix_manu, _ = Manufacturer.objects.get_or_create(name="Citrix") + srx_devicetype, _ = DeviceType.objects.get_or_create(model="SDX", manufacturer=citrix_manu) + core_role, _ = Role.objects.get_or_create(name="CORE") + core_role.content_types.add(ContentType.objects.get_for_model(Device)) + + core_router = Device.objects.create( + name="edge-fw.test.com", + device_type=srx_devicetype, + role=core_role, + serial="FQ123456", + location=self.hq_site, + status=self.status_active, + tenant=test_tenant, + ) + core_router._custom_field_data["system_of_record"] = "Citrix ADM" # pylint: disable=protected-access + core_router.validated_save() + mgmt_intf = Interface.objects.create( + name="Management", + type="virtual", + device=core_router, + status=self.status_active, + ) + mgmt_intf.validated_save() + + global_ns = Namespace.objects.get_or_create(name="Global")[0] + mgmt4_pf = Prefix.objects.create( + prefix="10.1.1.0/24", namespace=global_ns, status=self.status_active, tenant=test_tenant + ) + mgmt6_pf = Prefix.objects.create( + prefix="2001:db8:3333:4444:5555:6666:7777:8888/128", + namespace=global_ns, + status=self.status_active, + tenant=test_tenant, + ) + mgmt4_pf._custom_field_data["system_of_record"] = "Citrix ADM" # pylint: disable=protected-access + mgmt4_pf.validated_save() + mgmt6_pf._custom_field_data["system_of_record"] = "Citrix ADM" # pylint: disable=protected-access + mgmt6_pf.validated_save() + + mgmt_addr = IPAddress.objects.create( + address="10.1.1.1/24", + namespace=global_ns, + parent=mgmt4_pf, + status=self.status_active, + tenant=test_tenant, + ) + mgmt_addr._custom_field_data["system_of_record"] = "Citrix ADM" # pylint: disable=protected-access + mgmt_addr.validated_save() + mgmt_addr6 = IPAddress.objects.create( + address="2001:db8:3333:4444:5555:6666:7777:8888/128", + parent=mgmt6_pf, + status=self.status_active, + tenant=test_tenant, + ) + mgmt_addr6._custom_field_data["system_of_record"] = "Citrix ADM" # pylint: disable=protected-access + mgmt_addr6.validated_save() + + IPAddressToInterface.objects.create(ip_address=mgmt_addr, interface=mgmt_intf) + IPAddressToInterface.objects.create(ip_address=mgmt_addr6, interface=mgmt_intf) + core_router.primary_ip4 = mgmt_addr + core_router.primary_ip6 = mgmt_addr6 + core_router.validated_save() + + def test_load_sites(self): + """Test the load_sites() function.""" + self.nb_adapter.load_sites() + self.assertEqual( + { + "HQ__NY", + }, + {site.get_unique_id() for site in self.nb_adapter.get_all("datacenter")}, + ) + self.job.logger.info.assert_called_once_with("Loaded Site HQ from Nautobot.") + + def test_load_devices(self): + """Test the load_devices() function.""" + self.nb_adapter.load_devices() + self.assertEqual( + {"edge-fw.test.com"}, + {dev.get_unique_id() for dev in self.nb_adapter.get_all("device")}, + ) + self.job.logger.info.assert_any_call("Loading Device edge-fw.test.com from Nautobot.") + + def test_load_ports_success(self): + """Test the load_ports() function success.""" + self.nb_adapter.load_devices() + self.nb_adapter.load_ports() + self.assertEqual( + {"Management__edge-fw.test.com"}, + {port.get_unique_id() for port in self.nb_adapter.get_all("port")}, + ) + + def test_load_ports_missing_device(self): + """Test the load_ports() function with missing device.""" + self.nb_adapter.get = MagicMock() + self.nb_adapter.get.side_effect = ObjectNotFound + self.nb_adapter.load_ports() + self.job.logger.warning.assert_called_once_with( + "Unable to find edge-fw.test.com loaded so skipping loading port Management." + ) + + def test_load_addresses(self): + """Test the load_addresses() function.""" + self.nb_adapter.load_addresses() + self.assertEqual( + { + "10.1.1.1/24__10.1.1.0/24", + "2001:db8:3333:4444:5555:6666:7777:8888/128__2001:db8:3333:4444:5555:6666:7777:8888/128", + }, + {addr.get_unique_id() for addr in self.nb_adapter.get_all("address")}, + ) + + def test_load_prefixes(self): + """Test the load_prefix() function.""" + self.nb_adapter.load_prefixes() + self.assertEqual( + {"10.1.1.0/24__Global", "2001:db8:3333:4444:5555:6666:7777:8888/128__Global"}, + {pf.get_unique_id() for pf in self.nb_adapter.get_all("prefix")}, + ) + + def test_sync_complete(self): + """Test the sync_complete() method in the NautobotAdapter.""" + self.nb_adapter.objects_to_delete = { + "devices": [MagicMock()], + "ports": [MagicMock()], + "prefixes": [MagicMock()], + "addresses": [MagicMock()], + } + self.nb_adapter.job = MagicMock() + self.nb_adapter.job.logger.info = MagicMock() + + deleted_objs = [] + for group in ["addresses", "ports", "devices"]: + deleted_objs.extend(self.nb_adapter.objects_to_delete[group]) + + self.nb_adapter.sync_complete(diff=MagicMock(), source=MagicMock()) + + for obj in deleted_objs: + self.assertTrue(obj.delete.called) + self.assertEqual(len(self.nb_adapter.objects_to_delete["addresses"]), 0) + self.assertEqual(len(self.nb_adapter.objects_to_delete["prefixes"]), 0) + self.assertEqual(len(self.nb_adapter.objects_to_delete["ports"]), 0) + self.assertEqual(len(self.nb_adapter.objects_to_delete["devices"]), 0) + self.assertTrue(self.nb_adapter.job.logger.info.called) + self.assertTrue(self.nb_adapter.job.logger.info.call_count, 4) + self.assertTrue(self.nb_adapter.job.logger.info.call_args_list[0].startswith("Deleting")) + self.assertTrue(self.nb_adapter.job.logger.info.call_args_list[1].startswith("Deleting")) + self.assertTrue(self.nb_adapter.job.logger.info.call_args_list[2].startswith("Deleting")) + self.assertTrue(self.nb_adapter.job.logger.info.call_args_list[3].startswith("Deleting")) + + def test_sync_complete_protected_error(self): + """ + Tests that ProtectedError exception is handled when deleting objects from Nautobot. + """ + mock_dev = MagicMock() + mock_dev.delete.side_effect = ProtectedError(msg="Cannot delete protected object.", protected_objects=mock_dev) + self.nb_adapter.objects_to_delete["devices"].append(mock_dev) + self.nb_adapter.sync_complete(source=self.nb_adapter, diff=MagicMock()) + self.job.logger.info.assert_called() + self.job.logger.info.calls[1].starts_with("Deletion failed protected object") + + def test_load(self): + """Test the load() function.""" + self.nb_adapter.load_sites = MagicMock() + self.nb_adapter.load_devices = MagicMock() + self.nb_adapter.load_ports = MagicMock() + self.nb_adapter.load_prefixes = MagicMock() + self.nb_adapter.load_addresses = MagicMock() + self.nb_adapter.load() + self.nb_adapter.load_sites.assert_called_once() + self.nb_adapter.load_devices.assert_called_once() + self.nb_adapter.load_ports.assert_called_once() + self.nb_adapter.load_prefixes.assert_called_once() + self.nb_adapter.load_addresses.assert_called_once() diff --git a/nautobot_ssot/tests/citrix_adm/test_models_nautobot.py b/nautobot_ssot/tests/citrix_adm/test_models_nautobot.py new file mode 100644 index 000000000..8a114f0f3 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/test_models_nautobot.py @@ -0,0 +1,83 @@ +"""Test the Nautobot CRUD functions for all DiffSync models.""" + +from unittest.mock import MagicMock + +from diffsync import Adapter +from django.contrib.contenttypes.models import ContentType +from django.test import override_settings +from nautobot.core.testing import TransactionTestCase +from nautobot.dcim.models import Device, Location, LocationType +from nautobot.extras.models import Status + +from nautobot_ssot.integrations.citrix_adm.diffsync.models.nautobot import NautobotDatacenter + + +@override_settings(PLUGINS_CONFIG={"nautobot_ssot": {"enable_citrix_adm": True}}) +class TestNautobotDatacenter(TransactionTestCase): + """Test the NautobotDatacenter class.""" + + def setUp(self): + """Configure shared objects.""" + super().setUp() + self.adapter = Adapter() + self.adapter.job = MagicMock() + self.adapter.job.logger.warning = MagicMock() + self.status_active = Status.objects.get(name="Active") + self.test_dc = NautobotDatacenter(name="Test", region="", latitude=None, longitude=None, uuid=None) + region_lt = LocationType.objects.get_or_create(name="Region")[0] + self.global_region = Location.objects.create(name="Global", location_type=region_lt, status=self.status_active) + site_lt = LocationType.objects.get_or_create(name="Site", parent=region_lt)[0] + site_lt.content_types.add(ContentType.objects.get_for_model(Device)) + self.site_obj = Location.objects.create( + name="HQ", + location_type=site_lt, + parent=self.global_region, + status=self.status_active, + ) + self.adapter.job.dc_loctype = site_lt + self.adapter.job.parent_loc = None + + def test_create(self): + """Validate the NautobotDatacenter create() method creates a Site.""" + self.site_obj.delete() + ids = {"name": "HQ", "region": "Global"} + attrs = {"latitude": 12.345, "longitude": -67.89} + result = NautobotDatacenter.create(self.adapter, ids, attrs) + self.assertIsInstance(result, NautobotDatacenter) + + site_obj = Location.objects.get(name="HQ") + self.assertEqual(site_obj.parent, self.global_region) + self.assertEqual(float(site_obj.latitude), attrs["latitude"]) + self.assertEqual(float(site_obj.longitude), attrs["longitude"]) + + def test_create_with_duplicate_site(self): + """Validate the NautobotDatacenter create() method handling of duplicate Site.""" + ids = {"name": "HQ", "region": ""} + attrs = {} + NautobotDatacenter.create(self.adapter, ids, attrs) + self.adapter.job.logger.warning.assert_called_with("Site HQ already exists so skipping creation.") + + @override_settings(PLUGINS_CONFIG={"nautobot_ssot": {"citrix_adm_update_sites": True}}) + def test_update(self): + """Validate the NautobotDatacenter update() method updates a Site.""" + self.test_dc.uuid = self.site_obj.id + update_attrs = { + "latitude": 12.345, + "longitude": -67.89, + } + actual = NautobotDatacenter.update(self=self.test_dc, attrs=update_attrs) + self.site_obj.refresh_from_db() + self.assertEqual(float(self.site_obj.latitude), update_attrs["latitude"]) + self.assertEqual(float(self.site_obj.longitude), update_attrs["longitude"]) + self.assertEqual(actual, self.test_dc) + + @override_settings(PLUGINS_CONFIG={"nautobot_ssot": {"citrix_adm_update_sites": False}}) + def test_update_setting_disabled(self): + """Validate the NautobotDatacenter update() method doesn't update a Site if setting is False.""" + self.test_dc.adapter = MagicMock() + self.test_dc.adapter.job = MagicMock() + self.test_dc.adapter.job.logger.warning = MagicMock() + NautobotDatacenter.update(self=self.test_dc, attrs={}) + self.test_dc.adapter.job.logger.warning.assert_called_once_with( + "Update sites setting is disabled so skipping updating Test." + ) diff --git a/nautobot_ssot/tests/citrix_adm/test_utils_citrix_adm.py b/nautobot_ssot/tests/citrix_adm/test_utils_citrix_adm.py new file mode 100644 index 000000000..4c43ae464 --- /dev/null +++ b/nautobot_ssot/tests/citrix_adm/test_utils_citrix_adm.py @@ -0,0 +1,258 @@ +"""Utility functions for working with Citrix ADM.""" + +import logging +from unittest.mock import MagicMock, patch + +import requests +from nautobot.core.testing import TestCase +from requests.exceptions import HTTPError + +from nautobot_ssot.integrations.citrix_adm.utils.citrix_adm import ( + CitrixNitroClient, + parse_nsip6s, + parse_nsips, + parse_version, + parse_vlan_bindings, +) +from nautobot_ssot.tests.citrix_adm.fixtures import ( + DEVICE_FIXTURE_RECV, + DEVICE_FIXTURE_SENT, + NSIP6_FIXTURE_RECV, + NSIP6_FIXTURE_SENT, + NSIP_FIXTURE_RECV, + NSIP_FIXTURE_SENT, + SITE_FIXTURE_RECV, + SITE_FIXTURE_SENT, + VLAN_FIXTURE_RECV, + VLAN_FIXTURE_SENT, +) + +LOGGER = logging.getLogger(__name__) +# pylint: disable=too-many-public-methods + + +class TestCitrixAdmClient(TestCase): + """Test the Citrix ADM client and calls.""" + + databases = ("default", "job_logs") + + def setUp(self): + """Configure common variables for tests.""" + self.base_url = "https://example.com" + self.user = "user" + self.password = "password" # nosec: B105 + self.verify = True + self.job = MagicMock() + self.job.debug = True + self.job.logger.info = MagicMock() + self.job.logger.warning = MagicMock() + self.client = CitrixNitroClient(self.base_url, self.user, self.password, self.job, self.verify) + + def test_init(self): + """Validate the class initializer works as expected.""" + self.assertEqual(self.client.url, self.base_url) + self.assertEqual(self.client.username, self.user) + self.assertEqual(self.client.password, self.password) + self.assertEqual(self.client.verify, self.verify) + + def test_url_updated(self): + """Validate the URL is updated if a trailing slash is included in URL.""" + self.base_url = "https://example.com/" + self.client = CitrixNitroClient(self.base_url, self.user, self.password, self.job, self.verify) + self.assertEqual(self.client.url, self.base_url.rstrip("/")) + + @patch.object(CitrixNitroClient, "request") + def test_login(self, mock_request): + """Validate functionality of the login() method success.""" + mock_response = MagicMock() + mock_response = {"login": [{"sessionid": "1234"}]} + mock_request.return_value = mock_response + self.client.login() + self.assertEqual(self.client.headers["Cookie"], "SESSID=1234; path=/; SameSite=Lax; secure; HttpOnly") + + @patch.object(CitrixNitroClient, "request") + def test_login_failure(self, mock_request): + """Validate functionality of the login() method failure.""" + mock_response = MagicMock() + mock_response = {} + mock_request.return_value = mock_response + with self.assertRaises(requests.exceptions.RequestException): + self.client.login() + self.job.logger.error.assert_called_once_with( + "Error while logging into Citrix ADM. Please validate your configuration is correct." + ) + + @patch.object(CitrixNitroClient, "request") + def test_logout(self, mock_request): + """Validate functionality of the logout() method success.""" + self.client.logout() + mock_request.assert_called_with( + method="POST", + endpoint="config", + objecttype="logout", + data="object={'logout': {'username': 'user', 'password': 'password'}}", + ) + + @patch("nautobot_ssot.integrations.citrix_adm.utils.citrix_adm.requests.request") + def test_request(self, mock_request): + """Validate functionality of the request() method success.""" + mock_response = MagicMock() + mock_response.raise_for_status = MagicMock() + mock_response.json.return_value = {"errorcode": 0} + mock_request.return_value = mock_response + + endpoint = "example" + objecttype = "sample" + objectname = "test" + params = {"param1": "value1", "param2": "value2"} + data = '{"key": "value"}' + + response = self.client.request("POST", endpoint, objecttype, objectname, params, data) + + mock_request.assert_called_with( + method="POST", + url="https://example.com/nitro/v1/example/sample/test?param1=value1param2=value2", + data='{"key": "value"}', + headers={"Accept": "application/json", "Content-Type": "application/json"}, + timeout=60, + verify=True, + ) + mock_response.raise_for_status.assert_called_once() + self.assertEqual(response, {"errorcode": 0}) + + @patch("nautobot_ssot.integrations.citrix_adm.utils.citrix_adm.requests.request") + def test_request_failure(self, mock_request): + """Validate functionality of the request() method failure.""" + mock_response = MagicMock() + mock_response.raise_for_status = MagicMock() + mock_response.raise_for_status.side_effect = HTTPError + mock_request.return_value = mock_response + + endpoint = "example" + objecttype = "sample" + objectname = "test" + params = "test" + data = '{"key": "value"}' + + with self.assertRaises(requests.exceptions.HTTPError): + result = self.client.request("POST", endpoint, objecttype, objectname, params, data) + self.assertEqual(result, {}) + mock_response.raise_for_status.assert_called_once() + + @patch.object(CitrixNitroClient, "request") + def test_get_sites_success(self, mock_request): + """Validate functionality of the get_sites() method success.""" + mock_request.return_value = SITE_FIXTURE_SENT + expected = self.client.get_sites() + self.assertEqual(SITE_FIXTURE_RECV, expected) + + @patch.object(CitrixNitroClient, "request") + def test_get_sites_failure(self, mock_request): + """Validate functionality of the get_sites() method failure.""" + mock_request.return_value = {} + expected = self.client.get_sites() + self.job.logger.error.assert_called_once_with("Error getting sites from Citrix ADM.") + self.assertEqual(expected, {}) + + @patch.object(CitrixNitroClient, "request") + def test_get_devices_success(self, mock_request): + """Validate functionality of the get_devices() method success.""" + mock_request.return_value = DEVICE_FIXTURE_SENT + expected = self.client.get_devices() + self.assertEqual(DEVICE_FIXTURE_RECV, expected) + + @patch.object(CitrixNitroClient, "request") + def test_get_devices_failure(self, mock_request): + """Validate functionality of the get_devices() method failure.""" + mock_request.return_value = {} + expected = self.client.get_devices() + self.job.logger.error.assert_called_once_with("Error getting devices from Citrix ADM.") + self.assertEqual(expected, {}) + + @patch.object(CitrixNitroClient, "request") + def test_get_nsip_success(self, mock_request): + """Validate functionality of the get_nsip6() method success.""" + adc = {"hostname": "test", "ip_address": ""} + mock_request.return_value = NSIP_FIXTURE_SENT + expected = self.client.get_nsip(adc) + self.assertEqual(NSIP_FIXTURE_RECV, expected) + + @patch.object(CitrixNitroClient, "request") + def test_get_nsip_failure(self, mock_request): + """Validate functionality of the get_nsip() method failure.""" + adc = {"hostname": "test", "ip_address": ""} + mock_request.return_value = {} + actual = self.client.get_nsip(adc) + self.job.logger.error.assert_called_once_with("Error getting nsip from test") + self.assertEqual(actual, {}) + + @patch.object(CitrixNitroClient, "request") + def test_get_nsip6_success(self, mock_request): + """Validate functionality of the get_nsip() method success.""" + adc = {"hostname": "test", "ip_address": ""} + mock_request.side_effect = NSIP6_FIXTURE_SENT + for expected in NSIP6_FIXTURE_RECV: + actual = self.client.get_nsip6(adc) + self.assertEqual(actual, expected) + + @patch.object(CitrixNitroClient, "request") + def test_get_nsip6_failure(self, mock_request): + """Validate functionality of the get_nsip6() method failure.""" + adc = {"hostname": "test", "ip_address": ""} + mock_request.return_value = {} + actual = self.client.get_nsip6(adc) + self.job.logger.error.assert_called_once_with("Error getting nsip6 from test") + self.assertEqual(actual, {}) + + @patch.object(CitrixNitroClient, "request") + def test_get_vlan_bindings_success(self, mock_request): + """Validate functionality of the get_vlan_bindings() method success.""" + adc = {"hostname": "test", "ip_address": ""} + mock_request.side_effect = VLAN_FIXTURE_SENT + for expected in VLAN_FIXTURE_RECV: + actual = self.client.get_vlan_bindings(adc) + self.assertEqual(actual, expected) + + @patch.object(CitrixNitroClient, "request") + def test_get_vlan_bindings_failure(self, mock_request): + """Validate functionality of the get_vlan_bindings() method failure.""" + adc = {"hostname": "test", "ip_address": ""} + mock_request.return_value = {} + actual = self.client.get_vlan_bindings(adc) + self.job.logger.error.assert_called_once_with("Error getting vlan bindings from test") + self.assertEqual(actual, {}) + + def test_parse_version(self): + """Validate functionality of the parse_version function.""" + version = "NetScaler NS13.1: Build 37.38.nc, Date: Nov 23 2022, 04:42:36 (64-bit)" + expected = "NS13.1: Build 37.38.nc" + actual = parse_version(version=version) + self.assertEqual(actual, expected) + + def test_parse_vlan_bindings(self): + """Validate functionality of the parse_vlan_bindings function.""" + vlan_bindings = VLAN_FIXTURE_RECV[0] + adc = {"hostname": "test", "ip_address": "192.168.0.1", "netmask": "255.255.255.0"} + actual = parse_vlan_bindings(vlan_bindings=vlan_bindings, adc=adc, job=self) + expected = [{"ipaddress": "192.168.0.1", "netmask": 24, "port": "10/1", "version": 4, "vlan": "80"}] + self.assertEqual(actual, expected) + + def test_parse_nsips(self): + """Validate functionality of the parse_nsips function.""" + nsips = NSIP_FIXTURE_RECV + adc = {"hostname": "test", "mgmt_ip_address": "192.168.0.2"} + ports = [{"ipaddress": "192.168.0.1", "netmask": 24, "port": "10/1", "version": 4, "vlan": "80"}] + expected = [ + {"ipaddress": "192.168.0.1", "netmask": 24, "tags": ["NSIP"], "port": "10/1", "version": 4, "vlan": "80"}, + {"ipaddress": "192.168.0.2", "netmask": 24, "tags": ["MGMT"], "port": "10/1", "version": 4, "vlan": "80"}, + ] + actual = parse_nsips(nsips=nsips, adc=adc, ports=ports) + self.assertEqual(actual, expected) + + def test_parse_nsip6s(self): + """Validate functionality of the parse_nsip6s function.""" + nsip6s = NSIP6_FIXTURE_RECV[0] + ports = [] + expected = [{"ipaddress": "fe80::1234:5678:9abc:dev1", "netmask": "64", "port": "L0/1", "vlan": "1"}] + actual = parse_nsip6s(nsip6s=nsip6s, ports=ports) + self.assertEqual(actual, expected) diff --git a/nautobot_ssot/tests/dna_center/test_utils_dna_center.py b/nautobot_ssot/tests/dna_center/test_utils_dna_center.py index 9fe2a8e2e..fd06b12ee 100644 --- a/nautobot_ssot/tests/dna_center/test_utils_dna_center.py +++ b/nautobot_ssot/tests/dna_center/test_utils_dna_center.py @@ -201,20 +201,6 @@ def test_get_port_status(self, name, sent, received): # pylint: disable=unused- actual = self.dnac.get_port_status(port_info=sent) self.assertEqual(actual, received) - def test_parse_hostname_for_role_success(self): - """Validate the functionality of the parse_hostname_for_role method success.""" - hostname_mapping = [(".*EDGE.*", "Edge"), (".*DMZ.*", "DMZ")] - hostname = "DMZ-switch.example.com" - result = self.dnac.parse_hostname_for_role(hostname_map=hostname_mapping, device_hostname=hostname) - self.assertEqual(result, "DMZ") - - def test_parse_hostname_for_role_failure(self): - """Validate the functionality of the parse_hostname_for_role method failure.""" - hostname_mapping = [] - hostname = "core-router.example.com" - result = self.dnac.parse_hostname_for_role(hostname_map=hostname_mapping, device_hostname=hostname) - self.assertEqual(result, "Unknown") - def test_get_model_name_single_model(self): """Validate the functionality of get_model_name method with single model in string.""" test_model = "CSR1000v" diff --git a/nautobot_ssot/tests/test_utils.py b/nautobot_ssot/tests/test_utils.py new file mode 100644 index 000000000..886eff5e4 --- /dev/null +++ b/nautobot_ssot/tests/test_utils.py @@ -0,0 +1,27 @@ +"""Tests for utility functions.""" + +import unittest + +from nautobot_ssot.utils import parse_hostname_for_role + + +class TestSSoTUtils(unittest.TestCase): + """Test SSoT utility functions.""" + + def test_parse_hostname_for_role_success(self): + """Validate the functionality of the parse_hostname_for_role method success.""" + hostname_mapping = [(".*EDGE.*", "Edge"), (".*DMZ.*", "DMZ")] + hostname = "DMZ-switch.example.com" + result = parse_hostname_for_role( + hostname_map=hostname_mapping, device_hostname=hostname, default_role="Unknown" + ) + self.assertEqual(result, "DMZ") + + def test_parse_hostname_for_role_failure(self): + """Validate the functionality of the parse_hostname_for_role method failure.""" + hostname_mapping = [] + hostname = "core-router.example.com" + result = parse_hostname_for_role( + hostname_map=hostname_mapping, device_hostname=hostname, default_role="Unknown" + ) + self.assertEqual(result, "Unknown") diff --git a/nautobot_ssot/utils.py b/nautobot_ssot/utils.py index b0333b455..7ca3a77f4 100644 --- a/nautobot_ssot/utils.py +++ b/nautobot_ssot/utils.py @@ -1,10 +1,19 @@ """Utility functions for Nautobot SSoT App.""" import logging +import re +from typing import List, Tuple -from nautobot.dcim.models import Controller, ControllerManagedDeviceGroup from nautobot.extras.choices import SecretsGroupAccessTypeChoices, SecretsGroupSecretTypeChoices -from nautobot.extras.models import CustomField, SecretsGroup +from nautobot.extras.models import SecretsGroup + +try: + from nautobot.dcim.models import Controller, ControllerManagedDeviceGroup + + CONTROLLER_FOUND = True +except (ImportError, RuntimeError): + CONTROLLER_FOUND = False + logger = logging.getLogger("nautobot.ssot") @@ -26,25 +35,48 @@ def get_username_password_https_from_secretsgroup(group: SecretsGroup): return username, password -def verify_controller_managed_device_group(controller: Controller) -> ControllerManagedDeviceGroup: - """Validate that Controller Managed Device Group exists or create it. +if CONTROLLER_FOUND: - Args: - controller (Controller): Controller for associated ManagedDeviceGroup. + def verify_controller_managed_device_group(controller: Controller) -> ControllerManagedDeviceGroup: + """Validate that Controller Managed Device Group exists or create it. - Returns: - ControllerManagedDeviceGroup: The ControllerManagedDeviceGroup that was either found or created for the Controller. - """ - return ControllerManagedDeviceGroup.objects.get_or_create( - controller=controller, defaults={"name": f"{controller.name} Managed Devices"} - )[0] + Args: + controller (Controller): Controller for associated ManagedDeviceGroup. + + Returns: + ControllerManagedDeviceGroup: The ControllerManagedDeviceGroup that was either found or created for the Controller. + """ + return ControllerManagedDeviceGroup.objects.get_or_create( + controller=controller, defaults={"name": f"{controller.name} Managed Devices"} + )[0] -def create_or_update_custom_field(key, field_type, label): +def create_or_update_custom_field(apps, key, field_type, label): """Create or update a custom field object.""" + CustomField = apps.get_model("extras", "CustomField") # pylint: disable=invalid-name cf_dict = { "type": field_type, "key": key, "label": label, } return CustomField.objects.update_or_create(key=cf_dict["key"], defaults=cf_dict) + + +def parse_hostname_for_role(hostname_map: List[Tuple[str, str]], device_hostname: str, default_role: str): + """Parse device hostname from hostname_map to get Device Role. + + Args: + hostname_map (List[Tuple[str, str]]): List of tuples containing regex to compare with hostname and associated DeviceRole name. + device_hostname (str): Hostname of Device to determine role of. + default_role (str): String representing default Role to return if no match found. + + Returns: + str: Name of DeviceRole. Defaults to default_role. + """ + device_role = default_role + if hostname_map: # pylint: disable=duplicate-code + for entry in hostname_map: + match = re.match(pattern=entry[0], string=device_hostname) + if match: + device_role = entry[1] + return device_role diff --git a/poetry.lock b/poetry.lock index 75a8e8fa2..b66d2c7cc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,108 +13,108 @@ files = [ [[package]] name = "aiohttp" -version = "3.10.10" +version = "3.10.11" description = "Async http client/server framework (asyncio)" optional = true python-versions = ">=3.8" files = [ - {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f"}, - {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9"}, - {file = "aiohttp-3.10.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68"}, - {file = "aiohttp-3.10.10-cp310-cp310-win32.whl", hash = "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257"}, - {file = "aiohttp-3.10.10-cp310-cp310-win_amd64.whl", hash = "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6"}, - {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f"}, - {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb"}, - {file = "aiohttp-3.10.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a"}, - {file = "aiohttp-3.10.10-cp311-cp311-win32.whl", hash = "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94"}, - {file = "aiohttp-3.10.10-cp311-cp311-win_amd64.whl", hash = "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959"}, - {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c"}, - {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28"}, - {file = "aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205"}, - {file = "aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628"}, - {file = "aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf"}, - {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28"}, - {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d"}, - {file = "aiohttp-3.10.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b"}, - {file = "aiohttp-3.10.10-cp313-cp313-win32.whl", hash = "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8"}, - {file = "aiohttp-3.10.10-cp313-cp313-win_amd64.whl", hash = "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151"}, - {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1b66ccafef7336a1e1f0e389901f60c1d920102315a56df85e49552308fc0486"}, - {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:acd48d5b80ee80f9432a165c0ac8cbf9253eaddb6113269a5e18699b33958dbb"}, - {file = "aiohttp-3.10.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3455522392fb15ff549d92fbf4b73b559d5e43dc522588f7eb3e54c3f38beee7"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c3b868724137f713a38376fef8120c166d1eadd50da1855c112fe97954aed8"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da1dee8948d2137bb51fbb8a53cce6b1bcc86003c6b42565f008438b806cccd8"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5ce2ce7c997e1971b7184ee37deb6ea9922ef5163c6ee5aa3c274b05f9e12fa"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28529e08fde6f12eba8677f5a8608500ed33c086f974de68cc65ab218713a59d"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7db54c7914cc99d901d93a34704833568d86c20925b2762f9fa779f9cd2e70f"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03a42ac7895406220124c88911ebee31ba8b2d24c98507f4a8bf826b2937c7f2"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7e338c0523d024fad378b376a79faff37fafb3c001872a618cde1d322400a572"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:038f514fe39e235e9fef6717fbf944057bfa24f9b3db9ee551a7ecf584b5b480"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:64f6c17757251e2b8d885d728b6433d9d970573586a78b78ba8929b0f41d045a"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:93429602396f3383a797a2a70e5f1de5df8e35535d7806c9f91df06f297e109b"}, - {file = "aiohttp-3.10.10-cp38-cp38-win32.whl", hash = "sha256:c823bc3971c44ab93e611ab1a46b1eafeae474c0c844aff4b7474287b75fe49c"}, - {file = "aiohttp-3.10.10-cp38-cp38-win_amd64.whl", hash = "sha256:54ca74df1be3c7ca1cf7f4c971c79c2daf48d9aa65dea1a662ae18926f5bc8ce"}, - {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01948b1d570f83ee7bbf5a60ea2375a89dfb09fd419170e7f5af029510033d24"}, - {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9fc1500fd2a952c5c8e3b29aaf7e3cc6e27e9cfc0a8819b3bce48cc1b849e4cc"}, - {file = "aiohttp-3.10.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f614ab0c76397661b90b6851a030004dac502e48260ea10f2441abd2207fbcc7"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00819de9e45d42584bed046314c40ea7e9aea95411b38971082cad449392b08c"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05646ebe6b94cc93407b3bf34b9eb26c20722384d068eb7339de802154d61bc5"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:998f3bd3cfc95e9424a6acd7840cbdd39e45bc09ef87533c006f94ac47296090"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9010c31cd6fa59438da4e58a7f19e4753f7f264300cd152e7f90d4602449762"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ea7ffc6d6d6f8a11e6f40091a1040995cdff02cfc9ba4c2f30a516cb2633554"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ef9c33cc5cbca35808f6c74be11eb7f5f6b14d2311be84a15b594bd3e58b5527"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ce0cdc074d540265bfeb31336e678b4e37316849d13b308607efa527e981f5c2"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:597a079284b7ee65ee102bc3a6ea226a37d2b96d0418cc9047490f231dc09fe8"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7789050d9e5d0c309c706953e5e8876e38662d57d45f936902e176d19f1c58ab"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e7f8b04d83483577fd9200461b057c9f14ced334dcb053090cea1da9c8321a91"}, - {file = "aiohttp-3.10.10-cp39-cp39-win32.whl", hash = "sha256:c02a30b904282777d872266b87b20ed8cc0d1501855e27f831320f471d54d983"}, - {file = "aiohttp-3.10.10-cp39-cp39-win_amd64.whl", hash = "sha256:edfe3341033a6b53a5c522c802deb2079eee5cbfbb0af032a55064bd65c73a23"}, - {file = "aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a"}, + {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"}, + {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"}, + {file = "aiohttp-3.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffbfde2443696345e23a3c597049b1dd43049bb65337837574205e7368472177"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20b3d9e416774d41813bc02fdc0663379c01817b0874b932b81c7f777f67b217"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b943011b45ee6bf74b22245c6faab736363678e910504dd7531a58c76c9015a"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48bc1d924490f0d0b3658fe5c4b081a4d56ebb58af80a6729d4bd13ea569797a"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e12eb3f4b1f72aaaf6acd27d045753b18101524f72ae071ae1c91c1cd44ef115"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f14ebc419a568c2eff3c1ed35f634435c24ead2fe19c07426af41e7adb68713a"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72b191cdf35a518bfc7ca87d770d30941decc5aaf897ec8b484eb5cc8c7706f3"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ab2328a61fdc86424ee540d0aeb8b73bbcad7351fb7cf7a6546fc0bcffa0038"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa93063d4af05c49276cf14e419550a3f45258b6b9d1f16403e777f1addf4519"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30283f9d0ce420363c24c5c2421e71a738a2155f10adbb1a11a4d4d6d2715cfc"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e5358addc8044ee49143c546d2182c15b4ac3a60be01c3209374ace05af5733d"}, + {file = "aiohttp-3.10.11-cp310-cp310-win32.whl", hash = "sha256:e1ffa713d3ea7cdcd4aea9cddccab41edf6882fa9552940344c44e59652e1120"}, + {file = "aiohttp-3.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:778cbd01f18ff78b5dd23c77eb82987ee4ba23408cbed233009fd570dda7e674"}, + {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:80ff08556c7f59a7972b1e8919f62e9c069c33566a6d28586771711e0eea4f07"}, + {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c8f96e9ee19f04c4914e4e7a42a60861066d3e1abf05c726f38d9d0a466e695"}, + {file = "aiohttp-3.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fb8601394d537da9221947b5d6e62b064c9a43e88a1ecd7414d21a1a6fba9c24"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea224cf7bc2d8856d6971cea73b1d50c9c51d36971faf1abc169a0d5f85a382"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9503f79e12d5d80b3efd4d01312853565c05367493379df76d2674af881caa"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0f449a50cc33f0384f633894d8d3cd020e3ccef81879c6e6245c3c375c448625"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82052be3e6d9e0c123499127782a01a2b224b8af8c62ab46b3f6197035ad94e9"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20063c7acf1eec550c8eb098deb5ed9e1bb0521613b03bb93644b810986027ac"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:489cced07a4c11488f47aab1f00d0c572506883f877af100a38f1fedaa884c3a"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea9b3bab329aeaa603ed3bf605f1e2a6f36496ad7e0e1aa42025f368ee2dc07b"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ca117819d8ad113413016cb29774b3f6d99ad23c220069789fc050267b786c16"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2dfb612dcbe70fb7cdcf3499e8d483079b89749c857a8f6e80263b021745c730"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9b615d3da0d60e7d53c62e22b4fd1c70f4ae5993a44687b011ea3a2e49051b8"}, + {file = "aiohttp-3.10.11-cp311-cp311-win32.whl", hash = "sha256:29103f9099b6068bbdf44d6a3d090e0a0b2be6d3c9f16a070dd9d0d910ec08f9"}, + {file = "aiohttp-3.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:236b28ceb79532da85d59aa9b9bf873b364e27a0acb2ceaba475dc61cffb6f3f"}, + {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7480519f70e32bfb101d71fb9a1f330fbd291655a4c1c922232a48c458c52710"}, + {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f65267266c9aeb2287a6622ee2bb39490292552f9fbf851baabc04c9f84e048d"}, + {file = "aiohttp-3.10.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7400a93d629a0608dc1d6c55f1e3d6e07f7375745aaa8bd7f085571e4d1cee97"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f34b97e4b11b8d4eb2c3a4f975be626cc8af99ff479da7de49ac2c6d02d35725"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e7b825da878464a252ccff2958838f9caa82f32a8dbc334eb9b34a026e2c636"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f92a344c50b9667827da308473005f34767b6a2a60d9acff56ae94f895f385"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f1ab987a27b83c5268a17218463c2ec08dbb754195113867a27b166cd6087"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1dc0f4ca54842173d03322793ebcf2c8cc2d34ae91cc762478e295d8e361e03f"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7ce6a51469bfaacff146e59e7fb61c9c23006495d11cc24c514a455032bcfa03"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aad3cd91d484d065ede16f3cf15408254e2469e3f613b241a1db552c5eb7ab7d"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f4df4b8ca97f658c880fb4b90b1d1ec528315d4030af1ec763247ebfd33d8b9a"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e4e18a0a2d03531edbc06c366954e40a3f8d2a88d2b936bbe78a0c75a3aab3e"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ce66780fa1a20e45bc753cda2a149daa6dbf1561fc1289fa0c308391c7bc0a4"}, + {file = "aiohttp-3.10.11-cp312-cp312-win32.whl", hash = "sha256:a919c8957695ea4c0e7a3e8d16494e3477b86f33067478f43106921c2fef15bb"}, + {file = "aiohttp-3.10.11-cp312-cp312-win_amd64.whl", hash = "sha256:b5e29706e6389a2283a91611c91bf24f218962717c8f3b4e528ef529d112ee27"}, + {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:703938e22434d7d14ec22f9f310559331f455018389222eed132808cd8f44127"}, + {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bc50b63648840854e00084c2b43035a62e033cb9b06d8c22b409d56eb098413"}, + {file = "aiohttp-3.10.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f0463bf8b0754bc744e1feb61590706823795041e63edf30118a6f0bf577461"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6c6dec398ac5a87cb3a407b068e1106b20ef001c344e34154616183fe684288"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcaf2d79104d53d4dcf934f7ce76d3d155302d07dae24dff6c9fffd217568067"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25fd5470922091b5a9aeeb7e75be609e16b4fba81cdeaf12981393fb240dd10e"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbde2ca67230923a42161b1f408c3992ae6e0be782dca0c44cb3206bf330dee1"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:249c8ff8d26a8b41a0f12f9df804e7c685ca35a207e2410adbd3e924217b9006"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:878ca6a931ee8c486a8f7b432b65431d095c522cbeb34892bee5be97b3481d0f"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8663f7777ce775f0413324be0d96d9730959b2ca73d9b7e2c2c90539139cbdd6"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6cd3f10b01f0c31481fba8d302b61603a2acb37b9d30e1d14e0f5a58b7b18a31"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e8d8aad9402d3aa02fdc5ca2fe68bcb9fdfe1f77b40b10410a94c7f408b664d"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38e3c4f80196b4f6c3a85d134a534a56f52da9cb8d8e7af1b79a32eefee73a00"}, + {file = "aiohttp-3.10.11-cp313-cp313-win32.whl", hash = "sha256:fc31820cfc3b2863c6e95e14fcf815dc7afe52480b4dc03393c4873bb5599f71"}, + {file = "aiohttp-3.10.11-cp313-cp313-win_amd64.whl", hash = "sha256:4996ff1345704ffdd6d75fb06ed175938c133425af616142e7187f28dc75f14e"}, + {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74baf1a7d948b3d640badeac333af581a367ab916b37e44cf90a0334157cdfd2"}, + {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:473aebc3b871646e1940c05268d451f2543a1d209f47035b594b9d4e91ce8339"}, + {file = "aiohttp-3.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c2f746a6968c54ab2186574e15c3f14f3e7f67aef12b761e043b33b89c5b5f95"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d110cabad8360ffa0dec8f6ec60e43286e9d251e77db4763a87dcfe55b4adb92"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0099c7d5d7afff4202a0c670e5b723f7718810000b4abcbc96b064129e64bc7"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0316e624b754dbbf8c872b62fe6dcb395ef20c70e59890dfa0de9eafccd2849d"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a5f7ab8baf13314e6b2485965cbacb94afff1e93466ac4d06a47a81c50f9cca"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c891011e76041e6508cbfc469dd1a8ea09bc24e87e4c204e05f150c4c455a5fa"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9208299251370ee815473270c52cd3f7069ee9ed348d941d574d1457d2c73e8b"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:459f0f32c8356e8125f45eeff0ecf2b1cb6db1551304972702f34cd9e6c44658"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:14cdc8c1810bbd4b4b9f142eeee23cda528ae4e57ea0923551a9af4820980e39"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:971aa438a29701d4b34e4943e91b5e984c3ae6ccbf80dd9efaffb01bd0b243a9"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9a309c5de392dfe0f32ee57fa43ed8fc6ddf9985425e84bd51ed66bb16bce3a7"}, + {file = "aiohttp-3.10.11-cp38-cp38-win32.whl", hash = "sha256:9ec1628180241d906a0840b38f162a3215114b14541f1a8711c368a8739a9be4"}, + {file = "aiohttp-3.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:9c6e0ffd52c929f985c7258f83185d17c76d4275ad22e90aa29f38e211aacbec"}, + {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc493a2e5d8dc79b2df5bec9558425bcd39aff59fc949810cbd0832e294b106"}, + {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3e70f24e7d0405be2348da9d5a7836936bf3a9b4fd210f8c37e8d48bc32eca6"}, + {file = "aiohttp-3.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968b8fb2a5eee2770eda9c7b5581587ef9b96fbdf8dcabc6b446d35ccc69df01"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deef4362af9493d1382ef86732ee2e4cbc0d7c005947bd54ad1a9a16dd59298e"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:686b03196976e327412a1b094f4120778c7c4b9cff9bce8d2fdfeca386b89829"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3bf6d027d9d1d34e1c2e1645f18a6498c98d634f8e373395221121f1c258ace8"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:099fd126bf960f96d34a760e747a629c27fb3634da5d05c7ef4d35ef4ea519fc"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c73c4d3dae0b4644bc21e3de546530531d6cdc88659cdeb6579cd627d3c206aa"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c5580f3c51eea91559db3facd45d72e7ec970b04528b4709b1f9c2555bd6d0b"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fdf6429f0caabfd8a30c4e2eaecb547b3c340e4730ebfe25139779b9815ba138"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d97187de3c276263db3564bb9d9fad9e15b51ea10a371ffa5947a5ba93ad6777"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0acafb350cfb2eba70eb5d271f55e08bd4502ec35e964e18ad3e7d34d71f7261"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c13ed0c779911c7998a58e7848954bd4d63df3e3575f591e321b19a2aec8df9f"}, + {file = "aiohttp-3.10.11-cp39-cp39-win32.whl", hash = "sha256:22b7c540c55909140f63ab4f54ec2c20d2635c0289cdd8006da46f3327f971b9"}, + {file = "aiohttp-3.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:7b26b1551e481012575dab8e3727b16fe7dd27eb2711d2e63ced7368756268fb"}, + {file = "aiohttp-3.10.11.tar.gz", hash = "sha256:9dc2b8f3dcab2e39e0fa309c8da50c3b55e6f34ab25f1a71d3288f24924d33a7"}, ] [package.dependencies] aiohappyeyeballs = ">=2.3.0" aiosignal = ">=1.1.2" -async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" @@ -150,13 +150,13 @@ files = [ [[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] @@ -291,13 +291,13 @@ 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]] @@ -1510,13 +1510,13 @@ django = ">=4.2" [[package]] name = "dnacentersdk" -version = "2.7.4" +version = "2.7.7" description = "Cisco DNA Center Platform SDK" optional = true python-versions = "<4.0,>=3.8" files = [ - {file = "dnacentersdk-2.7.4-py3-none-any.whl", hash = "sha256:befab14a8a7a2dc3e6f4a51e66ec3bf3937f97dbad091098670d1a0620eb87a3"}, - {file = "dnacentersdk-2.7.4.tar.gz", hash = "sha256:91cbe7c7664afd59afca9150819cf45c5b6d0705e218dadf21fee1882a2f0802"}, + {file = "dnacentersdk-2.7.7-py3-none-any.whl", hash = "sha256:815bb07cf5a9c498b4e6ae971a986b2a3de7285c0989ea7854aebd32492a918b"}, + {file = "dnacentersdk-2.7.7.tar.gz", hash = "sha256:95bfc91e10fbb91866a816a29cd25cde1986cb924157edbc2c8786a48fa4405d"}, ] [package.dependencies] @@ -1596,13 +1596,13 @@ sidecar = ["drf-spectacular-sidecar"] [[package]] name = "drf-spectacular-sidecar" -version = "2024.7.1" +version = "2024.11.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.11.1-py3-none-any.whl", hash = "sha256:e2efd49c5bd1a607fd5d120d9da58d78e587852db8220b8880282a849296ff83"}, + {file = "drf_spectacular_sidecar-2024.11.1.tar.gz", hash = "sha256:fcfccc72cbdbe41e93f8416fa0c712d14126b8d1629e65c09c07c8edea24aad0"}, ] [package.dependencies] @@ -1669,59 +1669,61 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "fonttools" -version = "4.54.1" +version = "4.55.0" description = "Tools to manipulate font files" optional = true python-versions = ">=3.8" files = [ - {file = "fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2"}, - {file = "fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882"}, - {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10"}, - {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e"}, - {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e"}, - {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44"}, - {file = "fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02"}, - {file = "fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d"}, - {file = "fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20"}, - {file = "fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2"}, - {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7"}, - {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"}, - {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8"}, - {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a"}, - {file = "fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc"}, - {file = "fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6"}, - {file = "fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d"}, - {file = "fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08"}, - {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263"}, - {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab"}, - {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d"}, - {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714"}, - {file = "fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac"}, - {file = "fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e"}, - {file = "fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff"}, - {file = "fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb"}, - {file = "fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a"}, - {file = "fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c"}, - {file = "fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58"}, - {file = "fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d"}, - {file = "fonttools-4.54.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ed2f80ca07025551636c555dec2b755dd005e2ea8fbeb99fc5cdff319b70b23b"}, - {file = "fonttools-4.54.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dc080e5a1c3b2656caff2ac2633d009b3a9ff7b5e93d0452f40cd76d3da3b3c"}, - {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d152d1be65652fc65e695e5619e0aa0982295a95a9b29b52b85775243c06556"}, - {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8583e563df41fdecef31b793b4dd3af8a9caa03397be648945ad32717a92885b"}, - {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d1d353ef198c422515a3e974a1e8d5b304cd54a4c2eebcae708e37cd9eeffb1"}, - {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fda582236fee135d4daeca056c8c88ec5f6f6d88a004a79b84a02547c8f57386"}, - {file = "fonttools-4.54.1-cp38-cp38-win32.whl", hash = "sha256:e7d82b9e56716ed32574ee106cabca80992e6bbdcf25a88d97d21f73a0aae664"}, - {file = "fonttools-4.54.1-cp38-cp38-win_amd64.whl", hash = "sha256:ada215fd079e23e060157aab12eba0d66704316547f334eee9ff26f8c0d7b8ab"}, - {file = "fonttools-4.54.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5b8a096e649768c2f4233f947cf9737f8dbf8728b90e2771e2497c6e3d21d13"}, - {file = "fonttools-4.54.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e10d2e0a12e18f4e2dd031e1bf7c3d7017be5c8dbe524d07706179f355c5dac"}, - {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c32d7d4b0958600eac75eaf524b7b7cb68d3a8c196635252b7a2c30d80e986"}, - {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c39287f5c8f4a0c5a55daf9eaf9ccd223ea59eed3f6d467133cc727d7b943a55"}, - {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7a310c6e0471602fe3bf8efaf193d396ea561486aeaa7adc1f132e02d30c4b9"}, - {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d3b659d1029946f4ff9b6183984578041b520ce0f8fb7078bb37ec7445806b33"}, - {file = "fonttools-4.54.1-cp39-cp39-win32.whl", hash = "sha256:e96bc94c8cda58f577277d4a71f51c8e2129b8b36fd05adece6320dd3d57de8a"}, - {file = "fonttools-4.54.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8a4b261c1ef91e7188a30571be6ad98d1c6d9fa2427244c545e2fa0a2494dd7"}, - {file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"}, - {file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"}, + {file = "fonttools-4.55.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:51c029d4c0608a21a3d3d169dfc3fb776fde38f00b35ca11fdab63ba10a16f61"}, + {file = "fonttools-4.55.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bca35b4e411362feab28e576ea10f11268b1aeed883b9f22ed05675b1e06ac69"}, + {file = "fonttools-4.55.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ce4ba6981e10f7e0ccff6348e9775ce25ffadbee70c9fd1a3737e3e9f5fa74f"}, + {file = "fonttools-4.55.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31d00f9852a6051dac23294a4cf2df80ced85d1d173a61ba90a3d8f5abc63c60"}, + {file = "fonttools-4.55.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e198e494ca6e11f254bac37a680473a311a88cd40e58f9cc4dc4911dfb686ec6"}, + {file = "fonttools-4.55.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7208856f61770895e79732e1dcbe49d77bd5783adf73ae35f87fcc267df9db81"}, + {file = "fonttools-4.55.0-cp310-cp310-win32.whl", hash = "sha256:e7e6a352ff9e46e8ef8a3b1fe2c4478f8a553e1b5a479f2e899f9dc5f2055880"}, + {file = "fonttools-4.55.0-cp310-cp310-win_amd64.whl", hash = "sha256:636caaeefe586d7c84b5ee0734c1a5ab2dae619dc21c5cf336f304ddb8f6001b"}, + {file = "fonttools-4.55.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fa34aa175c91477485c44ddfbb51827d470011e558dfd5c7309eb31bef19ec51"}, + {file = "fonttools-4.55.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:37dbb3fdc2ef7302d3199fb12468481cbebaee849e4b04bc55b77c24e3c49189"}, + {file = "fonttools-4.55.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5263d8e7ef3c0ae87fbce7f3ec2f546dc898d44a337e95695af2cd5ea21a967"}, + {file = "fonttools-4.55.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f307f6b5bf9e86891213b293e538d292cd1677e06d9faaa4bf9c086ad5f132f6"}, + {file = "fonttools-4.55.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f0a4b52238e7b54f998d6a56b46a2c56b59c74d4f8a6747fb9d4042190f37cd3"}, + {file = "fonttools-4.55.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3e569711464f777a5d4ef522e781dc33f8095ab5efd7548958b36079a9f2f88c"}, + {file = "fonttools-4.55.0-cp311-cp311-win32.whl", hash = "sha256:2b3ab90ec0f7b76c983950ac601b58949f47aca14c3f21eed858b38d7ec42b05"}, + {file = "fonttools-4.55.0-cp311-cp311-win_amd64.whl", hash = "sha256:aa046f6a63bb2ad521004b2769095d4c9480c02c1efa7d7796b37826508980b6"}, + {file = "fonttools-4.55.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:838d2d8870f84fc785528a692e724f2379d5abd3fc9dad4d32f91cf99b41e4a7"}, + {file = "fonttools-4.55.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f46b863d74bab7bb0d395f3b68d3f52a03444964e67ce5c43ce43a75efce9246"}, + {file = "fonttools-4.55.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33b52a9cfe4e658e21b1f669f7309b4067910321757fec53802ca8f6eae96a5a"}, + {file = "fonttools-4.55.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:732a9a63d6ea4a81b1b25a1f2e5e143761b40c2e1b79bb2b68e4893f45139a40"}, + {file = "fonttools-4.55.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7dd91ac3fcb4c491bb4763b820bcab6c41c784111c24172616f02f4bc227c17d"}, + {file = "fonttools-4.55.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1f0e115281a32ff532118aa851ef497a1b7cda617f4621c1cdf81ace3e36fb0c"}, + {file = "fonttools-4.55.0-cp312-cp312-win32.whl", hash = "sha256:6c99b5205844f48a05cb58d4a8110a44d3038c67ed1d79eb733c4953c628b0f6"}, + {file = "fonttools-4.55.0-cp312-cp312-win_amd64.whl", hash = "sha256:f8c8c76037d05652510ae45be1cd8fb5dd2fd9afec92a25374ac82255993d57c"}, + {file = "fonttools-4.55.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8118dc571921dc9e4b288d9cb423ceaf886d195a2e5329cc427df82bba872cd9"}, + {file = "fonttools-4.55.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01124f2ca6c29fad4132d930da69158d3f49b2350e4a779e1efbe0e82bd63f6c"}, + {file = "fonttools-4.55.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ffd58d2691f11f7c8438796e9f21c374828805d33e83ff4b76e4635633674c"}, + {file = "fonttools-4.55.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5435e5f1eb893c35c2bc2b9cd3c9596b0fcb0a59e7a14121562986dd4c47b8dd"}, + {file = "fonttools-4.55.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d12081729280c39d001edd0f4f06d696014c26e6e9a0a55488fabc37c28945e4"}, + {file = "fonttools-4.55.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7ad1f1b98ab6cb927ab924a38a8649f1ffd7525c75fe5b594f5dab17af70e18"}, + {file = "fonttools-4.55.0-cp313-cp313-win32.whl", hash = "sha256:abe62987c37630dca69a104266277216de1023cf570c1643bb3a19a9509e7a1b"}, + {file = "fonttools-4.55.0-cp313-cp313-win_amd64.whl", hash = "sha256:2863555ba90b573e4201feaf87a7e71ca3b97c05aa4d63548a4b69ea16c9e998"}, + {file = "fonttools-4.55.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:00f7cf55ad58a57ba421b6a40945b85ac7cc73094fb4949c41171d3619a3a47e"}, + {file = "fonttools-4.55.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f27526042efd6f67bfb0cc2f1610fa20364396f8b1fc5edb9f45bb815fb090b2"}, + {file = "fonttools-4.55.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e67974326af6a8879dc2a4ec63ab2910a1c1a9680ccd63e4a690950fceddbe"}, + {file = "fonttools-4.55.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61dc0a13451143c5e987dec5254d9d428f3c2789a549a7cf4f815b63b310c1cc"}, + {file = "fonttools-4.55.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b2e526b325a903868c62155a6a7e24df53f6ce4c5c3160214d8fe1be2c41b478"}, + {file = "fonttools-4.55.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b7ef9068a1297714e6fefe5932c33b058aa1d45a2b8be32a4c6dee602ae22b5c"}, + {file = "fonttools-4.55.0-cp38-cp38-win32.whl", hash = "sha256:55718e8071be35dff098976bc249fc243b58efa263768c611be17fe55975d40a"}, + {file = "fonttools-4.55.0-cp38-cp38-win_amd64.whl", hash = "sha256:553bd4f8cc327f310c20158e345e8174c8eed49937fb047a8bda51daf2c353c8"}, + {file = "fonttools-4.55.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f901cef813f7c318b77d1c5c14cf7403bae5cb977cede023e22ba4316f0a8f6"}, + {file = "fonttools-4.55.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c9679fc0dd7e8a5351d321d8d29a498255e69387590a86b596a45659a39eb0d"}, + {file = "fonttools-4.55.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd2820a8b632f3307ebb0bf57948511c2208e34a4939cf978333bc0a3f11f838"}, + {file = "fonttools-4.55.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23bbbb49bec613a32ed1b43df0f2b172313cee690c2509f1af8fdedcf0a17438"}, + {file = "fonttools-4.55.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a656652e1f5d55b9728937a7e7d509b73d23109cddd4e89ee4f49bde03b736c6"}, + {file = "fonttools-4.55.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f50a1f455902208486fbca47ce33054208a4e437b38da49d6721ce2fef732fcf"}, + {file = "fonttools-4.55.0-cp39-cp39-win32.whl", hash = "sha256:161d1ac54c73d82a3cded44202d0218ab007fde8cf194a23d3dd83f7177a2f03"}, + {file = "fonttools-4.55.0-cp39-cp39-win_amd64.whl", hash = "sha256:ca7fd6987c68414fece41c96836e945e1f320cda56fc96ffdc16e54a44ec57a2"}, + {file = "fonttools-4.55.0-py3-none-any.whl", hash = "sha256:12db5888cd4dd3fcc9f0ee60c6edd3c7e1fd44b7dd0f31381ea03df68f8a153f"}, + {file = "fonttools-4.55.0.tar.gz", hash = "sha256:7636acc6ab733572d5e7eec922b254ead611f1cdad17be3f0be7418e8bfaca71"}, ] [package.extras] @@ -2008,70 +2010,70 @@ colorama = ">=0.4" [[package]] name = "grpcio" -version = "1.67.0" +version = "1.68.0" description = "HTTP/2-based RPC framework" optional = true python-versions = ">=3.8" files = [ - {file = "grpcio-1.67.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:bd79929b3bb96b54df1296cd3bf4d2b770bd1df6c2bdf549b49bab286b925cdc"}, - {file = "grpcio-1.67.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:16724ffc956ea42967f5758c2f043faef43cb7e48a51948ab593570570d1e68b"}, - {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:2b7183c80b602b0ad816315d66f2fb7887614ead950416d60913a9a71c12560d"}, - {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:efe32b45dd6d118f5ea2e5deaed417d8a14976325c93812dd831908522b402c9"}, - {file = "grpcio-1.67.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe89295219b9c9e47780a0f1c75ca44211e706d1c598242249fe717af3385ec8"}, - {file = "grpcio-1.67.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa8d025fae1595a207b4e47c2e087cb88d47008494db258ac561c00877d4c8f8"}, - {file = "grpcio-1.67.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f95e15db43e75a534420e04822df91f645664bf4ad21dfaad7d51773c80e6bb4"}, - {file = "grpcio-1.67.0-cp310-cp310-win32.whl", hash = "sha256:a6b9a5c18863fd4b6624a42e2712103fb0f57799a3b29651c0e5b8119a519d65"}, - {file = "grpcio-1.67.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6eb68493a05d38b426604e1dc93bfc0137c4157f7ab4fac5771fd9a104bbaa6"}, - {file = "grpcio-1.67.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:e91d154689639932305b6ea6f45c6e46bb51ecc8ea77c10ef25aa77f75443ad4"}, - {file = "grpcio-1.67.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb204a742997277da678611a809a8409657b1398aaeebf73b3d9563b7d154c13"}, - {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:ae6de510f670137e755eb2a74b04d1041e7210af2444103c8c95f193340d17ee"}, - {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74b900566bdf68241118f2918d312d3bf554b2ce0b12b90178091ea7d0a17b3d"}, - {file = "grpcio-1.67.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4e95e43447a02aa603abcc6b5e727d093d161a869c83b073f50b9390ecf0fa8"}, - {file = "grpcio-1.67.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0bb94e66cd8f0baf29bd3184b6aa09aeb1a660f9ec3d85da615c5003154bc2bf"}, - {file = "grpcio-1.67.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:82e5bd4b67b17c8c597273663794a6a46a45e44165b960517fe6d8a2f7f16d23"}, - {file = "grpcio-1.67.0-cp311-cp311-win32.whl", hash = "sha256:7fc1d2b9fd549264ae585026b266ac2db53735510a207381be509c315b4af4e8"}, - {file = "grpcio-1.67.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac11ecb34a86b831239cc38245403a8de25037b448464f95c3315819e7519772"}, - {file = "grpcio-1.67.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:227316b5631260e0bef8a3ce04fa7db4cc81756fea1258b007950b6efc90c05d"}, - {file = "grpcio-1.67.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d90cfdafcf4b45a7a076e3e2a58e7bc3d59c698c4f6470b0bb13a4d869cf2273"}, - {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:77196216d5dd6f99af1c51e235af2dd339159f657280e65ce7e12c1a8feffd1d"}, - {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c05a26a0f7047f720da41dc49406b395c1470eef44ff7e2c506a47ac2c0591"}, - {file = "grpcio-1.67.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3840994689cc8cbb73d60485c594424ad8adb56c71a30d8948d6453083624b52"}, - {file = "grpcio-1.67.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5a1e03c3102b6451028d5dc9f8591131d6ab3c8a0e023d94c28cb930ed4b5f81"}, - {file = "grpcio-1.67.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:682968427a63d898759474e3b3178d42546e878fdce034fd7474ef75143b64e3"}, - {file = "grpcio-1.67.0-cp312-cp312-win32.whl", hash = "sha256:d01793653248f49cf47e5695e0a79805b1d9d4eacef85b310118ba1dfcd1b955"}, - {file = "grpcio-1.67.0-cp312-cp312-win_amd64.whl", hash = "sha256:985b2686f786f3e20326c4367eebdaed3e7aa65848260ff0c6644f817042cb15"}, - {file = "grpcio-1.67.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:8c9a35b8bc50db35ab8e3e02a4f2a35cfba46c8705c3911c34ce343bd777813a"}, - {file = "grpcio-1.67.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:42199e704095b62688998c2d84c89e59a26a7d5d32eed86d43dc90e7a3bd04aa"}, - {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c4c425f440fb81f8d0237c07b9322fc0fb6ee2b29fbef5f62a322ff8fcce240d"}, - {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:323741b6699cd2b04a71cb38f502db98f90532e8a40cb675393d248126a268af"}, - {file = "grpcio-1.67.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:662c8e105c5e5cee0317d500eb186ed7a93229586e431c1bf0c9236c2407352c"}, - {file = "grpcio-1.67.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f6bd2ab135c64a4d1e9e44679a616c9bc944547357c830fafea5c3caa3de5153"}, - {file = "grpcio-1.67.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:2f55c1e0e2ae9bdd23b3c63459ee4c06d223b68aeb1961d83c48fb63dc29bc03"}, - {file = "grpcio-1.67.0-cp313-cp313-win32.whl", hash = "sha256:fd6bc27861e460fe28e94226e3673d46e294ca4673d46b224428d197c5935e69"}, - {file = "grpcio-1.67.0-cp313-cp313-win_amd64.whl", hash = "sha256:cf51d28063338608cd8d3cd64677e922134837902b70ce00dad7f116e3998210"}, - {file = "grpcio-1.67.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:7f200aca719c1c5dc72ab68be3479b9dafccdf03df530d137632c534bb6f1ee3"}, - {file = "grpcio-1.67.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0892dd200ece4822d72dd0952f7112c542a487fc48fe77568deaaa399c1e717d"}, - {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:f4d613fbf868b2e2444f490d18af472ccb47660ea3df52f068c9c8801e1f3e85"}, - {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c69bf11894cad9da00047f46584d5758d6ebc9b5950c0dc96fec7e0bce5cde9"}, - {file = "grpcio-1.67.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9bca3ca0c5e74dea44bf57d27e15a3a3996ce7e5780d61b7c72386356d231db"}, - {file = "grpcio-1.67.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:014dfc020e28a0d9be7e93a91f85ff9f4a87158b7df9952fe23cc42d29d31e1e"}, - {file = "grpcio-1.67.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d4ea4509d42c6797539e9ec7496c15473177ce9abc89bc5c71e7abe50fc25737"}, - {file = "grpcio-1.67.0-cp38-cp38-win32.whl", hash = "sha256:9d75641a2fca9ae1ae86454fd25d4c298ea8cc195dbc962852234d54a07060ad"}, - {file = "grpcio-1.67.0-cp38-cp38-win_amd64.whl", hash = "sha256:cff8e54d6a463883cda2fab94d2062aad2f5edd7f06ae3ed030f2a74756db365"}, - {file = "grpcio-1.67.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:62492bd534979e6d7127b8a6b29093161a742dee3875873e01964049d5250a74"}, - {file = "grpcio-1.67.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eef1dce9d1a46119fd09f9a992cf6ab9d9178b696382439446ca5f399d7b96fe"}, - {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:f623c57a5321461c84498a99dddf9d13dac0e40ee056d884d6ec4ebcab647a78"}, - {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54d16383044e681f8beb50f905249e4e7261dd169d4aaf6e52eab67b01cbbbe2"}, - {file = "grpcio-1.67.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2a44e572fb762c668e4812156b81835f7aba8a721b027e2d4bb29fb50ff4d33"}, - {file = "grpcio-1.67.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:391df8b0faac84d42f5b8dfc65f5152c48ed914e13c522fd05f2aca211f8bfad"}, - {file = "grpcio-1.67.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfd9306511fdfc623a1ba1dc3bc07fbd24e6cfbe3c28b4d1e05177baa2f99617"}, - {file = "grpcio-1.67.0-cp39-cp39-win32.whl", hash = "sha256:30d47dbacfd20cbd0c8be9bfa52fdb833b395d4ec32fe5cff7220afc05d08571"}, - {file = "grpcio-1.67.0-cp39-cp39-win_amd64.whl", hash = "sha256:f55f077685f61f0fbd06ea355142b71e47e4a26d2d678b3ba27248abfe67163a"}, - {file = "grpcio-1.67.0.tar.gz", hash = "sha256:e090b2553e0da1c875449c8e75073dd4415dd71c9bde6a406240fdf4c0ee467c"}, + {file = "grpcio-1.68.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:619b5d0f29f4f5351440e9343224c3e19912c21aeda44e0c49d0d147a8d01544"}, + {file = "grpcio-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:a59f5822f9459bed098ffbceb2713abbf7c6fd13f2b9243461da5c338d0cd6c3"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:c03d89df516128febc5a7e760d675b478ba25802447624edf7aa13b1e7b11e2a"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44bcbebb24363d587472089b89e2ea0ab2e2b4df0e4856ba4c0b087c82412121"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79f81b7fbfb136247b70465bd836fa1733043fdee539cd6031cb499e9608a110"}, + {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88fb2925789cfe6daa20900260ef0a1d0a61283dfb2d2fffe6194396a354c618"}, + {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:99f06232b5c9138593ae6f2e355054318717d32a9c09cdc5a2885540835067a1"}, + {file = "grpcio-1.68.0-cp310-cp310-win32.whl", hash = "sha256:a6213d2f7a22c3c30a479fb5e249b6b7e648e17f364598ff64d08a5136fe488b"}, + {file = "grpcio-1.68.0-cp310-cp310-win_amd64.whl", hash = "sha256:15327ab81131ef9b94cb9f45b5bd98803a179c7c61205c8c0ac9aff9d6c4e82a"}, + {file = "grpcio-1.68.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:3b2b559beb2d433129441783e5f42e3be40a9e1a89ec906efabf26591c5cd415"}, + {file = "grpcio-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e46541de8425a4d6829ac6c5d9b16c03c292105fe9ebf78cb1c31e8d242f9155"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c1245651f3c9ea92a2db4f95d37b7597db6b246d5892bca6ee8c0e90d76fb73c"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f1931c7aa85be0fa6cea6af388e576f3bf6baee9e5d481c586980c774debcb4"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ff09c81e3aded7a183bc6473639b46b6caa9c1901d6f5e2cba24b95e59e30"}, + {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8c73f9fbbaee1a132487e31585aa83987ddf626426d703ebcb9a528cf231c9b1"}, + {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6b2f98165ea2790ea159393a2246b56f580d24d7da0d0342c18a085299c40a75"}, + {file = "grpcio-1.68.0-cp311-cp311-win32.whl", hash = "sha256:e1e7ed311afb351ff0d0e583a66fcb39675be112d61e7cfd6c8269884a98afbc"}, + {file = "grpcio-1.68.0-cp311-cp311-win_amd64.whl", hash = "sha256:e0d2f68eaa0a755edd9a47d40e50dba6df2bceda66960dee1218da81a2834d27"}, + {file = "grpcio-1.68.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8af6137cc4ae8e421690d276e7627cfc726d4293f6607acf9ea7260bd8fc3d7d"}, + {file = "grpcio-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4028b8e9a3bff6f377698587d642e24bd221810c06579a18420a17688e421af7"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f60fa2adf281fd73ae3a50677572521edca34ba373a45b457b5ebe87c2d01e1d"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e18589e747c1e70b60fab6767ff99b2d0c359ea1db8a2cb524477f93cdbedf5b"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d30f3fee9372796f54d3100b31ee70972eaadcc87314be369360248a3dcffe"}, + {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7e0a3e72c0e9a1acab77bef14a73a416630b7fd2cbd893c0a873edc47c42c8cd"}, + {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a831dcc343440969aaa812004685ed322cdb526cd197112d0db303b0da1e8659"}, + {file = "grpcio-1.68.0-cp312-cp312-win32.whl", hash = "sha256:5a180328e92b9a0050958ced34dddcb86fec5a8b332f5a229e353dafc16cd332"}, + {file = "grpcio-1.68.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bddd04a790b69f7a7385f6a112f46ea0b34c4746f361ebafe9ca0be567c78e9"}, + {file = "grpcio-1.68.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:fc05759ffbd7875e0ff2bd877be1438dfe97c9312bbc558c8284a9afa1d0f40e"}, + {file = "grpcio-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:15fa1fe25d365a13bc6d52fcac0e3ee1f9baebdde2c9b3b2425f8a4979fccea1"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:32a9cb4686eb2e89d97022ecb9e1606d132f85c444354c17a7dbde4a455e4a3b"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dba037ff8d284c8e7ea9a510c8ae0f5b016004f13c3648f72411c464b67ff2fb"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0efbbd849867e0e569af09e165363ade75cf84f5229b2698d53cf22c7a4f9e21"}, + {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:4e300e6978df0b65cc2d100c54e097c10dfc7018b9bd890bbbf08022d47f766d"}, + {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:6f9c7ad1a23e1047f827385f4713b5b8c6c7d325705be1dd3e31fb00dcb2f665"}, + {file = "grpcio-1.68.0-cp313-cp313-win32.whl", hash = "sha256:3ac7f10850fd0487fcce169c3c55509101c3bde2a3b454869639df2176b60a03"}, + {file = "grpcio-1.68.0-cp313-cp313-win_amd64.whl", hash = "sha256:afbf45a62ba85a720491bfe9b2642f8761ff348006f5ef67e4622621f116b04a"}, + {file = "grpcio-1.68.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:f8f695d9576ce836eab27ba7401c60acaf9ef6cf2f70dfe5462055ba3df02cc3"}, + {file = "grpcio-1.68.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9fe1b141cda52f2ca73e17d2d3c6a9f3f3a0c255c216b50ce616e9dca7e3441d"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:4df81d78fd1646bf94ced4fb4cd0a7fe2e91608089c522ef17bc7db26e64effd"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46a2d74d4dd8993151c6cd585594c082abe74112c8e4175ddda4106f2ceb022f"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17278d977746472698460c63abf333e1d806bd41f2224f90dbe9460101c9796"}, + {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:15377bce516b1c861c35e18eaa1c280692bf563264836cece693c0f169b48829"}, + {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cc5f0a4f5904b8c25729a0498886b797feb817d1fd3812554ffa39551112c161"}, + {file = "grpcio-1.68.0-cp38-cp38-win32.whl", hash = "sha256:def1a60a111d24376e4b753db39705adbe9483ef4ca4761f825639d884d5da78"}, + {file = "grpcio-1.68.0-cp38-cp38-win_amd64.whl", hash = "sha256:55d3b52fd41ec5772a953612db4e70ae741a6d6ed640c4c89a64f017a1ac02b5"}, + {file = "grpcio-1.68.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:0d230852ba97654453d290e98d6aa61cb48fa5fafb474fb4c4298d8721809354"}, + {file = "grpcio-1.68.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:50992f214264e207e07222703c17d9cfdcc2c46ed5a1ea86843d440148ebbe10"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:14331e5c27ed3545360464a139ed279aa09db088f6e9502e95ad4bfa852bb116"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f84890b205692ea813653ece4ac9afa2139eae136e419231b0eec7c39fdbe4c2"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0cf343c6f4f6aa44863e13ec9ddfe299e0be68f87d68e777328bff785897b05"}, + {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fd2c2d47969daa0e27eadaf15c13b5e92605c5e5953d23c06d0b5239a2f176d3"}, + {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:18668e36e7f4045820f069997834e94e8275910b1f03e078a6020bd464cb2363"}, + {file = "grpcio-1.68.0-cp39-cp39-win32.whl", hash = "sha256:2af76ab7c427aaa26aa9187c3e3c42f38d3771f91a20f99657d992afada2294a"}, + {file = "grpcio-1.68.0-cp39-cp39-win_amd64.whl", hash = "sha256:e694b5928b7b33ca2d3b4d5f9bf8b5888906f181daff6b406f4938f3a997a490"}, + {file = "grpcio-1.68.0.tar.gz", hash = "sha256:7e7483d39b4a4fddb9906671e9ea21aaad4f031cdfc349fec76bdfa1e404543a"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.67.0)"] +protobuf = ["grpcio-tools (>=1.68.0)"] [[package]] name = "h11" @@ -2112,13 +2114,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.6" +version = "1.0.7" description = "A minimal low-level HTTP client." optional = true python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"}, - {file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"}, + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, ] [package.dependencies] @@ -2378,13 +2380,13 @@ files = [ [[package]] name = "ipfabric" -version = "6.10.0" +version = "6.10.4" description = "Python package for interacting with IP Fabric" optional = true python-versions = "<4.0,>=3.8" files = [ - {file = "ipfabric-6.10.0-py3-none-any.whl", hash = "sha256:5f419f1fbe1c9fa939c15969ae8a8e541d3c4e18f2588bc81ee3ba028af21c7a"}, - {file = "ipfabric-6.10.0.tar.gz", hash = "sha256:02b3b47fd10aada88891d7fe56b56782508778daecad34d37f75e3828e3faa07"}, + {file = "ipfabric-6.10.4-py3-none-any.whl", hash = "sha256:c948cd8c5554c6524afa6bb20a320759a14437169c5f736951d790e033f6175b"}, + {file = "ipfabric-6.10.4.tar.gz", hash = "sha256:b19cfa7bc327e005ec8cd8100dca1b0c0971681433103f70c5d5def50816d16b"}, ] [package.dependencies] @@ -3394,13 +3396,13 @@ testing-docutils = ["pygments", "pytest (>=7,<8)", "pytest-param-files (>=0.3.4, [[package]] name = "nautobot" -version = "2.3.8" +version = "2.3.11" description = "Source of truth and network automation platform." optional = false python-versions = "<3.13,>=3.8" files = [ - {file = "nautobot-2.3.8-py3-none-any.whl", hash = "sha256:1624480705158ba29fb3c9e0dc3d80e4809115b9a8584a90a787f3e7b37b484c"}, - {file = "nautobot-2.3.8.tar.gz", hash = "sha256:25319ccec7f99478b506d899159e02f02c8dcf61cd14c68401787a6e1e301dd1"}, + {file = "nautobot-2.3.11-py3-none-any.whl", hash = "sha256:f6ab9599a927904f5e956b1dc80c80dc53bdd44222c383bc073dd81f0b3bf727"}, + {file = "nautobot-2.3.11.tar.gz", hash = "sha256:d65e5b10612bfd7092469ddc76a19e5fe9f1eb37157ae09bba71b132bbc240e1"}, ] [package.dependencies] @@ -3443,7 +3445,7 @@ nh3 = ">=0.2.15,<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" @@ -3451,9 +3453,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.5,<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.5,<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)"] @@ -4287,20 +4289,21 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-extra-types" -version = "2.9.0" +version = "2.10.0" description = "Extra Pydantic types." optional = true python-versions = ">=3.8" files = [ - {file = "pydantic_extra_types-2.9.0-py3-none-any.whl", hash = "sha256:f0bb975508572ba7bf3390b7337807588463b7248587e69f43b1ad7c797530d0"}, - {file = "pydantic_extra_types-2.9.0.tar.gz", hash = "sha256:e061c01636188743bb69f368dcd391f327b8cfbfede2fe1cbb1211b06601ba3b"}, + {file = "pydantic_extra_types-2.10.0-py3-none-any.whl", hash = "sha256:b19943914e6286548254f5079d1da094e9c0583ee91a8e611e9df24bfd07dbcd"}, + {file = "pydantic_extra_types-2.10.0.tar.gz", hash = "sha256:552c47dd18fe1d00cfed75d9981162a2f3203cf7e77e55a3d3e70936f59587b9"}, ] [package.dependencies] pydantic = ">=2.5.2" +typing-extensions = "*" [package.extras] -all = ["pendulum (>=3.0.0,<4.0.0)", "phonenumbers (>=8,<9)", "pycountry (>=23)", "python-ulid (>=1,<2)", "python-ulid (>=1,<3)", "pytz (>=2024.1)", "semver (>=3.0.2)", "tzdata (>=2024.1)"] +all = ["pendulum (>=3.0.0,<4.0.0)", "phonenumbers (>=8,<9)", "pycountry (>=23)", "python-ulid (>=1,<2)", "python-ulid (>=1,<4)", "pytz (>=2024.1)", "semver (>=3.0.2)", "semver (>=3.0.2,<3.1.0)", "tzdata (>=2024.1)"] pendulum = ["pendulum (>=3.0.0,<4.0.0)"] phonenumbers = ["phonenumbers (>=8,<9)"] pycountry = ["pycountry (>=23)"] @@ -4309,13 +4312,13 @@ semver = ["semver (>=3.0.2)"] [[package]] name = "pydantic-settings" -version = "2.6.0" +version = "2.6.1" description = "Settings management using Pydantic" optional = true python-versions = ">=3.8" files = [ - {file = "pydantic_settings-2.6.0-py3-none-any.whl", hash = "sha256:4a819166f119b74d7f8c765196b165f95cc7487ce58ea27dec8a5a26be0970e0"}, - {file = "pydantic_settings-2.6.0.tar.gz", hash = "sha256:44a1804abffac9e6a30372bb45f6cafab945ef5af25e66b1c634c01dd39e0188"}, + {file = "pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87"}, + {file = "pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0"}, ] [package.dependencies] @@ -4438,13 +4441,13 @@ pylint = ">=1.7" [[package]] name = "pymdown-extensions" -version = "10.11.2" +version = "10.12" 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.12-py3-none-any.whl", hash = "sha256:49f81412242d3527b8b4967b990df395c89563043bc51a3d2d7d500e52123b77"}, + {file = "pymdown_extensions-10.12.tar.gz", hash = "sha256:b0ee1e0b2bef1071a47891ab17003bfe5bf824a398e13f49f8ed653b699369a7"}, ] [package.dependencies] @@ -4599,66 +4602,66 @@ files = [ [[package]] name = "pyuwsgi" -version = "2.0.27.post1" +version = "2.0.28.post1" description = "The uWSGI server" optional = false python-versions = "*" files = [ - {file = "pyuwsgi-2.0.27.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:293295cc1a340dd26499871a2e2afee05b03eee17162209ded5b32f089ccf114"}, - {file = "pyuwsgi-2.0.27.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4daccecaf11105ebe4ce69d8fa7fccb38a66eca5b0bfe508a07d1d935b79f1d"}, - {file = "pyuwsgi-2.0.27.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f422324d55b52ca928fe6e48e94a4fa18da613b59f586a0c882ba84981e8aab0"}, - {file = "pyuwsgi-2.0.27.post1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:354078fc4174683d0e52c02c19cf1646fae54c424d3506437c26bdcdb57980ef"}, - {file = "pyuwsgi-2.0.27.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c4e9c2524812bf0839af2a14379f0b89d8a31fd975e6fbdbad652cce94d2582"}, - {file = "pyuwsgi-2.0.27.post1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e73056e0564803ca0adfaa6a657a78cd9576e1ae8c3ab6a797fb28d4e87ce6e3"}, - {file = "pyuwsgi-2.0.27.post1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:35e03a040ef0d1b27b36b3f76fe42c47049f166682cb55da59afdc15c5326c42"}, - {file = "pyuwsgi-2.0.27.post1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a378b83d20884525344d18a84e806b7be7ada09e0f9fd4e4699e0c25ef535952"}, - {file = "pyuwsgi-2.0.27.post1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8addac8b24cc8c30f24875c8d4229deb872f64041a565e1bfbaa8fe14028bae3"}, - {file = "pyuwsgi-2.0.27.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a49ac13c50f6f3d5015d448b912d68c20c4750d45f8b58208d1e300e36b60fa3"}, - {file = "pyuwsgi-2.0.27.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3b072d74e1563e2e87c8f3899687227c5e4745637f2746e9ad2f9854fe2d0aa"}, - {file = "pyuwsgi-2.0.27.post1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7404ca6e03eab163140d39028cc5918ae7332e557d3edc550a19b9b1640914db"}, - {file = "pyuwsgi-2.0.27.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9483e8d5025e3c273efc8ac32243a0c6dce95bc33d7401233dd6f0af1b8c077"}, - {file = "pyuwsgi-2.0.27.post1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8d8e9e9d2dd44e72a0d99b0c5796fbfc3547e3dd4e7d7b71ca255f3af812839a"}, - {file = "pyuwsgi-2.0.27.post1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3d7cdc6aa3d42ab2d1f56136a9eccb9d9b0e7ca43c97bd514be69adc4705bc2d"}, - {file = "pyuwsgi-2.0.27.post1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b3048fb40dec61b9a4dba4dfda7d30f697c4d233b0475d27cea39619c7981b18"}, - {file = "pyuwsgi-2.0.27.post1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9eff11b2f7275e5c54e6b394a8b3c5d523159ea227a281b31ca93c41dfa206a9"}, - {file = "pyuwsgi-2.0.27.post1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f052d5c066e7818b483065b0cef579d662f14e56020f92c7fad224cc6ce48be"}, - {file = "pyuwsgi-2.0.27.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e52d67f330ce1f310896ee12fc57b92a1d65c839c512ab22de5789873e84206"}, - {file = "pyuwsgi-2.0.27.post1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97b6799f96e574a377b39d23817fd946d653daeaacac5e786d7f6f93211be0df"}, - {file = "pyuwsgi-2.0.27.post1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f19ad6e39415f82ed2d6c5318802148ec5ff9f760d25208b58013432c3a24e7c"}, - {file = "pyuwsgi-2.0.27.post1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:38559eb301add04b5fd765aa1b13517660926a603c27fe4a9c8a305ee6e65daf"}, - {file = "pyuwsgi-2.0.27.post1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:28523126a3e2593aef3b3d35e5c6dcf758a6139421e0c6509bea3bf1e70b59e3"}, - {file = "pyuwsgi-2.0.27.post1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07f1da1b46c006f54546d09950c49d5828655896c14235ac1169ed955de8c50e"}, - {file = "pyuwsgi-2.0.27.post1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fd3b42c638b2724e9fcc93241e31d6f15e7829ad23d144e914b1cea0d071d34a"}, - {file = "pyuwsgi-2.0.27.post1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:18a2e020e984a2f9fbb2733b52cafa3ad6317916d76a2ca7c531fcd2af54bacd"}, - {file = "pyuwsgi-2.0.27.post1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccae438085e7ad23dd745d0d89e6664925b0509642ff03d9d5160cc3727daa24"}, - {file = "pyuwsgi-2.0.27.post1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9af975c6a0667013e4699b7c43f2e9c9b852f4ce7c3bf91a4c594b4558ff651"}, - {file = "pyuwsgi-2.0.27.post1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38e7ecce19e400e00828204ebaa34dee9b57eb434c324428d109be6b0d4ade91"}, - {file = "pyuwsgi-2.0.27.post1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdc2b5788defc1adb796f23b358f81a1274486a8e3a8f1206491ed0313fdafc6"}, - {file = "pyuwsgi-2.0.27.post1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1cd43982443c2084677cb6f2545144b049d54b37d79607f5e9c6c80438f05562"}, - {file = "pyuwsgi-2.0.27.post1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d90da1ba15003c24e678803f88c194937b902ff8729861b380752dc4025be420"}, - {file = "pyuwsgi-2.0.27.post1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37561914526e661ed6be022c34fec609ac8ebaa374ee99b0c06bb9fdd84c27fb"}, - {file = "pyuwsgi-2.0.27.post1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fd638f9644ca32e914794ccfe87268a53a1c24dcc9cd709c3d936da6a6af175"}, - {file = "pyuwsgi-2.0.27.post1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:266c5f0bcc1659e27e504533208ccb66264a17439fbccc17507b363cf6fd21a1"}, - {file = "pyuwsgi-2.0.27.post1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91e5e3884c59f13eaadc41a774f72626eb77bf5dc7d9ab60c70446c76bf6af19"}, - {file = "pyuwsgi-2.0.27.post1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:2033cb5630bb411eded7864fa2eaf6c8f172d11a7182d8cd7d18585f50e8a9a9"}, - {file = "pyuwsgi-2.0.27.post1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:bd30983a6d001ad2a452853598f31e05c4113a6b4febb3288672e1cf8b335e68"}, - {file = "pyuwsgi-2.0.27.post1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:c7ab8b1b8a57f60a56691d080011a00bfc164d38454900d65867f6b6835984c8"}, - {file = "pyuwsgi-2.0.27.post1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b0acb872cf3780db11ff676b98f4995fba765479eef7ab9eec3ceeea79e2e1b"}, - {file = "pyuwsgi-2.0.27.post1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7616268720cead0c8ac384cfdd3837c7805c4a8a371908d203c1a4c1c04c2d1f"}, - {file = "pyuwsgi-2.0.27.post1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1597b2e4610a676df8ea5e91e2365589f21dd62e5ea01094b6237d6d4365421"}, - {file = "pyuwsgi-2.0.27.post1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e9feba9a85f4d4e006f7151c13aa74f0100bd4a148920046baa4bdd08318a5f"}, - {file = "pyuwsgi-2.0.27.post1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:889ca23e93d228570ee6704b66996fcd7bb9db34b0a13ebc845759a509881b84"}, - {file = "pyuwsgi-2.0.27.post1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:86522f0e68aac8137551f2c2fe6aee2e532060b10b00e7cb5f1737026c2f7f88"}, - {file = "pyuwsgi-2.0.27.post1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c42e565d19ff66f1235c6d01b2333ae17b0e614046570b88f3a432624dc02ab0"}, - {file = "pyuwsgi-2.0.27.post1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5a197cb9e59a9c8267a142d185a69ca743fbaeab040f3e70370f93718d27f3b"}, - {file = "pyuwsgi-2.0.27.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c4dc24d09406c281a411386827f9d86f004023e187a311e9bd24053bee18bdcc"}, - {file = "pyuwsgi-2.0.27.post1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd3bcb50b8b72e57c8bfb9bee7d8cfa2029c97f33bfbcb3609ab72659a80237"}, - {file = "pyuwsgi-2.0.27.post1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01910d5ebaef2f0730c3e72f9ef8bc3d7eeb586d0c40e89b34bcb3c60a45582e"}, - {file = "pyuwsgi-2.0.27.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:968363f71ae2059b85385a84b57a60207251282de8632294d79ea63519157c74"}, - {file = "pyuwsgi-2.0.27.post1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:808aade4f82b41509567b6e6a8c9ee232ccf2d0fcd3523af88409400a2d001f9"}, - {file = "pyuwsgi-2.0.27.post1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8de52a0e5d0d71c1bb572169d4850ca82d827f84b0efb0f60691327d74709122"}, - {file = "pyuwsgi-2.0.27.post1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8cda34be6ab18e9a58bbd668b5b382d321d16cffa36724afab4c58fa98d5e6b6"}, - {file = "pyuwsgi-2.0.27.post1.tar.gz", hash = "sha256:fc16abaaa0932758ecd5950c3927730a70ad0d5e4ad8189ff745455ec2a8ad21"}, + {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]] @@ -4772,105 +4775,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]] @@ -4980,114 +4983,114 @@ py = ">=1.4.26,<2.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]] @@ -5129,23 +5132,23 @@ files = [ [[package]] name = "setuptools" -version = "75.2.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.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8"}, - {file = "setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec"}, + {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" @@ -5373,13 +5376,13 @@ test = ["pytest"] [[package]] name = "sqlparse" -version = "0.5.1" +version = "0.5.2" 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.2-py3-none-any.whl", hash = "sha256:e99bc85c78160918c3e1d9230834ab8d80fc06c59d03f8db2618f65f65dda55e"}, + {file = "sqlparse-0.5.2.tar.gz", hash = "sha256:9e37b35e16d1cc652a2545f0997c1deb23ea28fa1f3eefe609eee3063c3b105f"}, ] [package.extras] @@ -5471,13 +5474,13 @@ files = [ [[package]] name = "tomli" -version = "2.0.2" +version = "2.1.0" 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.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, + {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, ] [[package]] @@ -5649,13 +5652,13 @@ files = [ [[package]] name = "wheel" -version = "0.44.0" +version = "0.45.0" 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.0-py3-none-any.whl", hash = "sha256:52f0baa5e6522155090a09c6bd95718cc46956d1b51d537ea5454249edb671c7"}, + {file = "wheel-0.45.0.tar.gz", hash = "sha256:a57353941a3183b3d5365346b567a260a0602a0f8a635926a7dede41b94c674a"}, ] [package.extras] @@ -5891,9 +5894,10 @@ type = ["pytest-mypy"] [extras] aci = ["PyYAML"] -all = ["Jinja2", "PyYAML", "cloudvision", "cvprac", "dnacentersdk", "dnspython", "ijson", "ipfabric", "meraki", "nautobot-device-lifecycle-mgmt", "netutils", "oauthlib", "python-magic", "pytz", "requests", "requests-oauthlib", "six"] +all = ["Jinja2", "PyYAML", "cloudvision", "cvprac", "dnacentersdk", "dnspython", "ijson", "ipfabric", "meraki", "nautobot-device-lifecycle-mgmt", "netutils", "oauthlib", "python-magic", "pytz", "requests", "requests-oauthlib", "six", "urllib3"] aristacv = ["cloudvision", "cvprac"] bootstrap = ["pytz"] +citrix-adm = ["netutils", "requests", "urllib3"] device42 = ["requests"] dna-center = ["dnacentersdk", "netutils"] infoblox = ["dnspython"] @@ -5906,4 +5910,4 @@ servicenow = ["Jinja2", "PyYAML", "ijson", "oauthlib", "python-magic", "pytz", " [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.13" -content-hash = "5b036b6ed33b03cabd905331cf784b8f83c4987f6d1827b6371c5d133db6646c" +content-hash = "468a33c56c433f76654463e4df45b30cb3c5efc2421f62ef7ae20d17b6bb4f42" diff --git a/pyproject.toml b/pyproject.toml index 7fa9e87f6..7883b9bed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ python-magic = { version = ">=0.4.15", optional = true } pytz = { version = ">=2019.3", optional = true } requests = { version = ">=2.21.0", optional = true } requests-oauthlib = { version = ">=1.3.0", optional = true } +urllib3 = { version = ">=2.2.3", optional = true } six = { version = ">=1.13.0", optional = true } httpx = { version = ">=0.23.3", optional = true } # Used by the Itential SSoT as a retry mechanism for HTTP failures in the AutomationGatewayClient. @@ -118,6 +119,7 @@ all = [ "requests", "requests-oauthlib", "six", + "urllib3", "dnacentersdk", "meraki" ] @@ -128,6 +130,11 @@ aristacv = [ bootstrap = [ "pytz" ] +citrix-adm = [ + "requests", + "urllib3", + "netutils", +] device42 = [ "requests", ]