diff --git a/.gitignore b/.gitignore
index acea9ed..b2d399e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -165,3 +165,4 @@ cython_debug/
# ignore local dev
assurance
logs
+pan_os_upgrade/settings.yaml
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 5fe3481..42e2eef 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,13 +1,25 @@
{
"cSpell.words": [
+ "adminpassword",
"apikey",
"defusedxml",
+ "dynaconf",
+ "Dynaconf",
+ "hhotfix",
"highavailability",
+ "hostnames",
"levelname",
+ "malformatted",
"nics",
+ "NXDOMAIN",
+ "proxied",
"pydantic",
"Pydantic",
"refreshall",
+ "typer",
+ "Typer",
+ "Typer's",
+ "unsynchronized",
"Xapi",
"xmltodict"
]
diff --git a/README.md b/README.md
index b5bd323..9b563ed 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
```console
-$ pan-os-upgrade batch
+pan-os-upgrade batch
Panorama hostname or IP: panorama.cdot.io
Panorama username: cdot
Panorama password:
Firewall target version (ex: 10.1.2): 10.2.3
Filter string (ex: hostname=Woodlands*) []: hostname=Woodlands*
Dry Run? [y/N]:
+===========================================================================
+Welcome to the PAN-OS upgrade tool
+
+You have selected to perform a batch upgrade of firewalls through Panorama.
+
+No settings.yaml file was found. Default values will be used.
+Create a settings.yaml file with 'pan-os-upgrade settings' command.
+===========================================================================
โ
panorama.cdot.io: Connection to Panorama established. Firewall connections will be proxied!
๐ Woodlands-fw2: 007954000123452 192.168.255.44
๐ Woodlands-fw1: 007954000123451 192.168.255.43
@@ -167,268 +175,14 @@ Device 007954000123451 installing version: 10.2.3
(back to top)
-
-## Getting Started
-
-There are two primary methods to utilize the `pan-os-upgrade` tool: through a Python virtual environment or via a Docker container. Both methods are outlined below to cater to different preferences or requirements.
-
-### Running with Python Virtual Environment
-
-This approach involves setting up a Python virtual environment on your local machine and running the `pan-os-upgrade` tool within this isolated environment.
-
-#### Python Prerequisites
-
-- Python 3.8 or newer.
-- Access to a Palo Alto Networks firewall or Panorama appliance.
-- An active internet connection to download the package from PyPI.
-
-#### Installation
-
-The `pan-os-upgrade` library is available on PyPI and can be installed within a Python virtual environment. A virtual environment is a self-contained directory that contains a Python installation for a particular version of Python, plus a number of additional packages.
-
-##### Creating a Python Virtual Environment
-
-The steps below highlight the process for creating, activating, and installing `pan-os-upgrade` into a Python virtual environment. If you're new to Python, it may be beneficial to understand why this is such an important step, [here is a good writeup](https://realpython.com/python-virtual-environments-a-primer/) to prime yourself.
-
-1. Create a Virtual Environment:
-
- ```bash
- python3 -m venv panos_env
- ```
-
- This command creates a new directory panos_env which contains a copy of the Python interpreter, the standard library, and various supporting files.
-
-2. Activate the Virtual Environment:
-
- On Windows:
-
- ```bash
- panos_env\Scripts\activate
- ```
-
- On macOS and Linux:
-
- ```bash
- source panos_env/bin/activate
- ```
-
- After activation, your command line will indicate that you are now in the virtual environment.
-
-3. Install `pan-os-upgrade`:
-
- Within the activated environment, use pip to install the package:
-
- ```bash
- pip install pan-os-upgrade
- ```
-
-### CLI Arguments vs. CLI Options
-
-In the context of the `pan-os-upgrade` application, it's important to distinguish between CLI arguments and CLI options:
-
-- **CLI Arguments** are the primary commands that determine the operation mode of the application. They are not prefixed by `--` or `-` and are essential for defining the core action the script should perform.
-- **CLI Options**, on the other hand, are additional modifiers or settings that further customize the behavior of the CLI arguments. They typically come with a `--` prefix (or `-` for shorthand) and are optional.
-
-#### CLI Arguments
-
-The following are the main commands (CLI arguments) for the `pan-os-upgrade` application, each tailored for specific upgrade scenarios:
-
-| CLI Argument | Description |
-| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `firewall` | Targets an individual firewall for upgrade. This command requires subsequent CLI options to specify the firewall details and desired actions. |
-| `panorama` | Targets an individual Panorama appliance for upgrade, necessitating further CLI options for execution details. |
-| `batch` | Utilizes a Panorama appliance to orchestrate bulk upgrades of managed firewalls, supporting up to ten concurrent operations. Requires additional CLI options for filtering and execution specifics. |
-
-#### CLI Options
-
-Below are the CLI options that can be used in conjunction with the above CLI arguments to customize the upgrade process:
-
-| CLI Option | Shorthand | Type | Description |
-| ------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
-| `--dry-run` | `-d` | Boolean | Executes all preparatory steps without applying the actual upgrade, useful for testing and verification purposes. |
-| `--filter` | `-f` | String | Specifies criteria for selecting devices when performing batch upgrades via Panorama, such as device hostname patterns or serial numbers. |
-| `--hostname` | `-h` | String | The IP address or DNS name of the target firewall or Panorama appliance. |
-| `--log-level` | `-l` | String | Determines the verbosity of log output, with levels including debug, info, and warning among others. |
-| `--password` | `-p` | String | The authentication password required for accessing the target device. |
-| `--username` | `-u` | String | The username for authentication with the target PAN-OS device. |
-| `--version` | `-v` | String | Specifies the target PAN-OS version for the upgrade operation. |
-
-Each CLI option has a specific role in tailoring the upgrade process, from defining the target device and authentication credentials to setting operational parameters like the target PAN-OS version and logging verbosity.
-
-#### Option 1: Execute `pan-os-upgrade` without Command-Line Arguments
-
-You can simply get started by issuing `pan-os-upgrade` from your current working directory, you will be guided to input the missing requirement arguments through an interactive shell.
-
-```console
-$ pan-os-upgrade firewall
-Firewall hostname or IP: houston.cdot.io
-Firewall username: cdot
-Firewall password:
-Target version: 10.2.4-h4
-Dry Run? [y/N]: N
-๐ houston: 007054000242050 192.168.255.211
-๐ houston: HA mode: disabled
-๐ houston: Current version: 10.2.4-h3
-๐ houston: Target version: 10.2.4-h4
-โ
houston: Upgrade required from 10.2.4-h3 to 10.2.4-h4
-โ
houston: version 10.2.4-h4 is available for download
-โ
houston: Base image for 10.2.4-h4 is already downloaded
-๐ houston: Performing test to see if 10.2.4-h4 is already downloaded...
-โ
houston: version 10.2.4-h4 already on target device.
-โ
houston: version 10.2.4-h4 has been downloaded.
-๐ houston: Performing snapshot of network state information...
-โ
houston: Network snapshot created successfully
-๐ houston: Performing readiness checks to determine if firewall is ready for upgrade...
-โ
houston: Passed Readiness Check: Check if there are pending changes on device
-โ
houston: Passed Readiness Check: No Expired Licenses
-โ
houston: Passed Readiness Check: Check if NTP is synchronized
-โ
houston: Passed Readiness Check: Check connectivity with the Panorama appliance
-โ
houston: Readiness Checks completed
-๐ houston: Performing backup of configuration to local filesystem...
-๐ houston: Not a dry run, continue with upgrade...
-๐ houston: Performing upgrade to version 10.2.4-h4...
-๐ houston: Attempting upgrade to version 10.2.4-h4 (Attempt 1 of 3)...
-Device 007054000242050 installing version: 10.2.4-h4
-โ
houston: Upgrade completed successfully
-๐ houston: Rebooting the standalone target device...
-๐ houston: Command succeeded with no output
-๐ง houston: Target device is rebooting...
-๐ง houston: Target device is rebooting...
-๐ง houston: Target device is rebooting...
-๐ง houston: Target device is rebooting...
-๐ง houston: Target device is rebooting...
-๐ houston: Target device version: 10.2.4-h4
-โ
houston: Target device rebooted in 448 seconds
-```
-
-##### Option 2: Execute `pan-os-upgrade` Using Command-Line Arguments
-
-Alternatively, you can pass these details as command-line arguments when running the script.
-
-> Note: You *can* pass your password as a CLI option with either `--password` or `-p`, but make sure you understand the risk of having your password in your terminal's history.
-
-```bash
-pan-os-upgrade firewall --hostname 192.168.1.1 --username admin --password secret --version 10.1.0
-```
-
-For a dry run:
-
-```bash
-pan-os-upgrade firewall --hostname 192.168.1.1 --username admin --password secret --version 10.1.0 --dry-run
-```
-
-If you're targeting a Panorama appliance to act as a proxy for communications to the firewall, make sure you include a filter pattern:
-
-```bash
-pan-os-upgrade batch --hostname panorama.cdot.io --username admin --password secret --version 10.1.0 --filter "hostname=Woodlands*"
-```
-
-
(back to top)
-
-### Running with Docker
-
-Alternatively, you can run `pan-os-upgrade` as a Docker container. This method ensures that the tool runs in an isolated environment with all its dependencies packaged together.
-
-#### Docker Prerequisites
-
-- Docker installed on your system. You can download it from [Docker's official site](https://www.docker.com/products/docker-desktop).
-
-#### Pulling the Docker Image
-
-First, pull the `pan-os-upgrade` image from GitHub Packages:
-
-```bash
-docker pull ghcr.io/cdot65/pan-os-upgrade:latest
-```
-
-#### Running the Container
-
-To run the container and mount local directories for `assurance` and `logs`, use the following commands:
-
-On macOS and Linux:
-
-```bash
-docker run -v $(pwd)/assurance:/app/assurance -v $(pwd)/logs:/app/logs -it pan-os-upgrade firewall
-```
-
-On Windows:
-
-```bash
-docker run -v %CD%/assurance:/app/assurance -v %CD%/logs:/app/logs -it pan-os-upgrade panorama
-```
-
-These commands mount the current directory's `assurance` and `logs` subdirectories to the corresponding directories in the container. If these directories don't exist on your host, Docker will create them.
-
-#### Interactive Mode
-
-The container will start in interactive mode, prompting you for the necessary input like IP address, username, password, and target PAN-OS version.
-
-#### Accessing Logs and Output
-
-After the container stops, you can find the logs and other output files in the `assurance` and `logs` directories of your current working directory on your host machine.
-
-
(back to top)
-
-
+
## Usage
-The script can be run from the command line with various options.
-
-You can view all arguments by passing the `--help` flag:
-
-```bash
-$ pan-os-upgrade --help
-
- Usage: upgrade.py [OPTIONS] COMMAND [ARGS]...
-
- PAN-OS Upgrade script
-
-โญโ Options โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
-โ --install-completion Install completion for the current shell. โ
-โ --show-completion Show completion for the current shell, to copy it or customize the installation. โ
-โ --help Show this message and exit. โ
-โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
-โญโ Commands โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
-โ batch Executes a batch upgrade of firewalls managed by a Panorama appliance based on specified criteria. โ
-โ firewall Initiates the upgrade process for a specified firewall appliance. โ
-โ panorama Initiates the upgrade process for a specified Panorama appliance. โ
-โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
+There are two primary methods to utilize the `pan-os-upgrade` tool: through a Python virtual environment or via a Docker container.
+Please refer to the dedicated documentation website to understand how to use this tool.
-```
-
-
(back to top)
-
-Refer to the [documentation](https://cdot65.github.io/pan-os-upgrade/) for more details on usage.
-
-
(back to top)
-
-
-## Output
-
-The script generates several files containing the state of the firewall and readiness checks. These files are stored in the `assurance` directory with the following structure:
-
-- `snapshots`: Contains the pre and post-upgrade network state snapshots in JSON format.
-- `readiness_checks`: Contains the results of readiness checks in JSON format.
-- `configurations`: Contains the backup of the firewall's configuration in XML format.
-
-
-## Logging
-
-Log messages are printed to the console and saved to a rotating log file located in the `logs` directory. The log level can be set via the `--log-level` argument.
-
-
-## Troubleshooting
-
-Encountered an issue? Here are some common problems and solutions:
-
-- **Problem**: Script fails to connect to the PAN-OS device.
- - **Solution**: Check if the hostname and credentials are correct. Ensure network connectivity to the PAN-OS device.
-
-- **Problem**: Script hangs during execution.
- - **Solution**: Check the firewall and network settings. Ensure the PAN-OS device is responding correctly.
-
-For more troubleshooting tips, visit our [FAQ section](https://cdot65.github.io/pan-os-upgrade/).
+Documentation Site: [https://cdot65.github.io/pan-os-upgrade/](https://cdot65.github.io/pan-os-upgrade/)
## Contributing
@@ -459,8 +213,6 @@ This project is licensed under the Apache 2.0 License - see the [LICENSE](https:
Email Address - cremsburg.dev at gmail.com
-Project Link: [https://github.com/cdot65/pan-os-upgrade](https://github.com/cdot65/pan-os-upgrade)
-
(back to top)
diff --git a/docker/Dockerfile b/docker/Dockerfile
index e3f3c02..03f6fc7 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -10,9 +10,12 @@ RUN apk add --no-cache gcc musl-dev libffi-dev make
# Set the working directory in the container to /app
WORKDIR /app
+# Add settings.yaml to the container at /app
+ADD settings.yaml /app
+
# Install any needed packages specified in requirements.txt
-# Note: The requirements.txt should contain pan-os-upgrade==0.4.1
-RUN pip install --no-cache-dir pan-os-upgrade==0.4.1
+# Note: The requirements.txt should contain pan-os-upgrade==0.4.2
+RUN pip install --no-cache-dir pan-os-upgrade==0.4.2
# Set the locale to avoid issues with emoji rendering
ENV LANG C.UTF-8
diff --git a/docker/settings.yaml b/docker/settings.yaml
new file mode 100644
index 0000000..ed97d53
--- /dev/null
+++ b/docker/settings.yaml
@@ -0,0 +1 @@
+---
diff --git a/docs/about/release-notes.md b/docs/about/release-notes.md
index dd7c70f..740bb18 100644
--- a/docs/about/release-notes.md
+++ b/docs/about/release-notes.md
@@ -2,10 +2,21 @@
Welcome to the release notes for the `pan-os-upgrade` tool. This document provides a detailed record of changes, enhancements, and fixes in each version of the tool.
+## Version 0.4.2
+
+**Release Date:** *<20240127>*
+
+### What's New
+
+- Created mechanism to override the default settings of `pan-os-upgrade`
+- Added support for new CLI argument, `pan-os-upgrade settings`
+- Created a banner message to help with usability
+
## Version 0.4.1
**Release Date:** *<20240127>*
+
### What's New
- Resolved an issue where missing fields in session snapshots for older PAN-OS versions caused errors in Pydantic models
@@ -133,4 +144,4 @@ Welcome to the release notes for the `pan-os-upgrade` tool. This document provid
---
-For more detailed information on each release, visit the [GitHub repository](https://github.com/cdot65/pan-os-upgrade/releases) or check the [commit history](https://github.com/cdot65/pan-os-upgrade/commits/main).
+For more detailed information on each release, visit the [GitHub repository](https://github.com/cdot65/pan-os-upgrade/releases) or check the [commit history](https://github.com/cdot65/pan-os-upgrade/commits/main).
\ No newline at end of file
diff --git a/docs/user-guide/docker/execution.md b/docs/user-guide/docker/execution.md
index a7f5942..5e5a1b6 100644
--- a/docs/user-guide/docker/execution.md
+++ b/docs/user-guide/docker/execution.md
@@ -1,45 +1,180 @@
# Docker Execution for pan-os-upgrade
-The `pan-os-upgrade` tool can be conveniently run using Docker, offering a consistent and streamlined setup process across different systems. This guide will walk you through configuring and executing the tool within a Docker container, including steps for connecting to firewalls through Panorama as a proxy.
+Run `pan-os-upgrade` in Docker for a consistent setup across systems. This guide details the steps for Docker configuration and execution, including Panorama proxy connections.
## Pulling the Docker Image
-If you haven't already done so, start off by pulling the `pan-os-upgrade` Docker image from GitHub Packages:
+Pull the `pan-os-upgrade` image from GitHub Packages:
-```bash
+
+
+```console
docker pull ghcr.io/cdot65/pan-os-upgrade:latest
```
-## Setting Up the Docker Environment
+
+
+## Docker Setup
Before executing the tool, ensure your Docker environment is correctly set up.
-### Directory Setup
+### Prepare Directories
Create `assurance` and `logs` directories in your working directory to store outputs and logs:
-```bash
+
+
+```console
mkdir assurance logs
```
-### Running the Docker Container
+
+
+If you plan on customizing the settings of the script, create an empty `settings.yaml` in your working directory. This file will be filled out correctly when you run the `settings` argument when running the container image (see the `Advanced Setttings` section):
+
+
+
+```console
+touch settings.yaml
+```
+
+
+
+### Run Docker Container
+
+Before we get the execution, let's take a moment to review the flags that we need to pass at runtime.
+
+#### Docker CLI flags
+
+| flag | description | required? |
+| ------ | ----------------------------------------------------------------------- | --------- |
+| -v | mount files / folders from your local computer into the container | yes |
+| -it | let Docker know that you need an interactive session with the container | yes |
+| --rm | remove the container after it completes its execution, good for hygiene | no |
+| --name | assign a name to the container instance | no |
+
+##### Volume Mounts
+
+We will need to create at least two volume mounts with Docker, this workflow allows us to have our local files available within the Docker container. When a Docker container completes its execution, the default behavior is to stop the container and this will prevent us from viewing the logs, snapshots, configuration backups, and readiness checks.
+
+##### Interactive Teletype
+
+Since we are using a CLI tool that requires interaction from us during its execution, we also need to flag the container to work in an interactive teletype session.
+
+##### Optional Flags
+
+If you'd like, you can assign a name to your container. This can help you create a friendly name for the container instance that will help you revisit it, should that prove necessary.
+
+If you decide to assign your container a name that be common across multiple executions, you will want to also pass the `--rm` flag to remove the container after its execution completes. This is a good practice to reduce the amount of duplicate containers, but will prevent you from revisiting a specific container instance later on; this shouldn't be an issue since all logs, snapshots, checks, and backups are available in your host's current directory, thanks to the volume mounts.
+
+#### Example Execution on macOS and Linux
+
+In this example we will upgrade a firewall directly by using the `firewall` argument when executing the container. The `$(pwd)` on macOS and Linux is a shortcut for `full path to your current working directory`; feel free to simply run `pwd` from your terminal to get an understanding of the response.
+
+
+
+```console
+docker run \
+-v $(pwd)/assurance:/app/assurance \
+-v $(pwd)/logs:/app/logs \
+-it \
+ghcr.io/cdot65/pan-os-upgrade:latest firewall
+Firewall password:
+===================================================================
+Welcome to the PAN-OS upgrade tool
+
+You have selected to upgrade a single Firewall appliance.
+
+No settings.yaml file was found. Default values will be used.
+Create a settings.yaml file with 'pan-os-upgrade settings' command.
+===================================================================
+๐ houston: 007054000242050 192.168.255.211
+๐ houston: HA mode: disabled
+๐ houston: Current version: 10.2.5
+๐ houston: Target version: 10.2.6
+โ
houston: Upgrade required from 10.2.5 to 10.2.6
+โ
houston: version 10.2.6 is available for download
+โ
houston: Base image for 10.2.6 is already downloaded
+๐ houston: Performing test to see if 10.2.6 is already downloaded...
+๐ houston: version 10.2.6 is not on the target device
+๐ houston: version 10.2.6 is beginning download
+Device 007054000242050 downloading version: 10.2.6
+๐ง houston: Downloading version 10.2.6 - Elapsed time: 5 seconds
+๐ง houston: Downloading version 10.2.6 - Elapsed time: 41 seconds
+๐ง houston: Downloading version 10.2.6 - Elapsed time: 76 seconds
+โ
houston: 10.2.6 downloaded in 109 seconds
+โ
houston: version 10.2.6 has been downloaded.
+๐ houston: Performing snapshot of network state information...
+โ
houston: Network snapshot created successfully
+๐ houston: Performing readiness checks to determine if firewall is ready for upgrade...
+โ
houston: Passed Readiness Check: Check if there are pending changes on device
+โ
houston: Passed Readiness Check: No Expired Licenses
+โ
houston: Passed Readiness Check: Check if NTP is synchronized
+โ
houston: Passed Readiness Check: Check if the clock is synchronized between dataplane and management plane
+โ
houston: Passed Readiness Check: Check connectivity with the Panorama appliance
+โ
houston: Readiness Checks completed
+๐ houston: Performing backup of configuration to local filesystem...
+โ
houston: Dry run complete, exiting...
+๐ houston: Halting script.
+```
+
+
-Run `pan-os-upgrade` in Docker using the following commands:
+In the example I am using the `\` at execution to allow me to split the flags on separate lines, this is completely optional but I have found that it helps me review all flags without going cross-eyed. You can absolutely execute all commands on a single line
-#### On macOS and Linux
+
-```bash
+```console
docker run -v $(pwd)/assurance:/app/assurance -v $(pwd)/logs:/app/logs -it ghcr.io/cdot65/pan-os-upgrade:latest firewall
```
-This mounts your host's `assurance` and `logs` directories to the container.
+
+
+#### Example Execution on Windows
-#### On Windows
+The volume mount flags need to point to a different shortcut to reference your current working directory on Windows:
-```bash
+
+
+```console
docker run -v %CD%/assurance:/app/assurance -v %CD%/logs:/app/logs -it ghcr.io/cdot65/pan-os-upgrade:latest panorama
```
+
+
+### CLI Arguments vs. CLI Options
+
+In the context of the `pan-os-upgrade` application, it's important to distinguish between CLI arguments and CLI options:
+
+- **CLI Arguments** are the primary commands that determine the operation mode of the application. They are not prefixed by `--` or `-` and are essential for defining the core action the script should perform.
+- **CLI Options**, on the other hand, are additional modifiers or settings that further customize the behavior of the CLI arguments. They typically come with a `--` prefix (or `-` for shorthand) and are optional.
+
+#### CLI Arguments
+
+The following are the main commands (CLI arguments) for the `pan-os-upgrade` application, each tailored for specific upgrade scenarios:
+
+| CLI Argument | Description |
+| ------------ | --------------------------------------------------------------------------------------------------------- |
+| `firewall` | Targets an individual firewall for upgrade. |
+| `panorama` | Targets an individual Panorama appliance for upgrade. |
+| `batch` | Utilizes a Panorama appliance to orchestrate bulk upgrades of managed firewalls. |
+| `settings` | Creates a `settings.yaml` that will allow users to customize the script's default settings and behaviors. |
+
+#### CLI Options
+
+Below are the CLI options that can be used in conjunction with the above CLI arguments to customize the upgrade process:
+
+| CLI Option | Shorthand | Type | Description |
+| ------------ | --------- | ------- | --------------------------------------------------------------------------------------- |
+| `--dry-run` | `-d` | Boolean | Executes all preparatory steps without applying the actual upgrade, useful for testing. |
+| `--filter` | `-f` | String | Specifies criteria for selecting devices when performing batch upgrades via Panorama. |
+| `--hostname` | `-h` | String | The IP address or DNS name of the target firewall or Panorama appliance. |
+| `--password` | `-p` | String | The authentication password required for accessing the target device. |
+| `--username` | `-u` | String | The username for authentication with the target PAN-OS device. |
+| `--version` | `-v` | String | Specifies the target PAN-OS version for the upgrade operation. |
+
+Each CLI option has a specific role in tailoring the upgrade process, from defining the target device and authentication credentials to setting operational parameters like the target PAN-OS version and logging verbosity.
+
## Interacting with the Docker Container
The container runs interactively, prompting you for details like IP address, username, password, and target PAN-OS version. If connecting to firewalls through Panorama as a proxy, you will also be prompted to provide a `--filter` option to specify the criteria for selecting the managed firewalls to upgrade.
@@ -47,13 +182,26 @@ The container runs interactively, prompting you for details like IP address, use
```console
-$ docker run -v $(pwd)/assurance:/app/assurance -v $(pwd)/logs:/app/logs -it ghcr.io/cdot65/pan-os-upgrade:latest batch
+$ docker run \
+-v $(pwd)/assurance:/app/assurance \
+-v $(pwd)/logs:/app/logs \
+-it \
+ghcr.io/cdot65/pan-os-upgrade:latest batch
+
Panorama hostname or IP: panorama.cdot.io
Panorama username: cdot
Panorama password:
Firewall target version (ex: 10.1.2): 10.2.3
Filter string (ex: hostname=Woodlands*) []: hostname=Woodlands*
Dry Run? [y/N]:
+===========================================================================
+Welcome to the PAN-OS upgrade tool
+
+You have selected to perform a batch upgrade of firewalls through Panorama.
+
+No settings.yaml file was found. Default values will be used.
+Create a settings.yaml file with 'pan-os-upgrade settings' command.
+===========================================================================
โ
panorama.cdot.io: Connection to Panorama established. Firewall connections will be proxied!
๐ Woodlands-fw2: 007954000123452 192.168.255.44
๐ Woodlands-fw1: 007954000123451 192.168.255.43
@@ -147,110 +295,122 @@ Device 007954000123451 installing version: 10.2.3
-### CLI Arguments vs. CLI Options
+## Advanced Settings
-In the context of the `pan-os-upgrade` application, it's important to distinguish between CLI arguments and CLI options:
+If you would like to change the default settings of `pan-os-upgrade` tool, you can create a `settings.yaml` file and run the `settings` CLI argument. This will walk you through a series of options to change.
-- **CLI Arguments** are the primary commands that determine the operation mode of the application. They are not prefixed by `--` or `-` and are essential for defining the core action the script should perform.
-- **CLI Options**, on the other hand, are additional modifiers or settings that further customize the behavior of the CLI arguments. They typically come with a `--` prefix (or `-` for shorthand) and are optional.
-
-#### CLI Arguments
-
-The following are the main commands (CLI arguments) for the `pan-os-upgrade` application, each tailored for specific upgrade scenarios:
-
-| CLI Argument | Description |
-| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `firewall` | Targets an individual firewall for upgrade. This command requires subsequent CLI options to specify the firewall details and desired actions. |
-| `panorama` | Targets an individual Panorama appliance for upgrade, necessitating further CLI options for execution details. |
-| `batch` | Utilizes a Panorama appliance to orchestrate bulk upgrades of managed firewalls, supporting up to ten concurrent operations. Requires additional CLI options for filtering and execution specifics. |
-
-#### CLI Options
+Create the empty `settings.yaml` file within your current working directory
-Below are the CLI options that can be used in conjunction with the above CLI arguments to customize the upgrade process:
-
-| CLI Option | Shorthand | Type | Description |
-| ------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
-| `--dry-run` | `-d` | Boolean | Executes all preparatory steps without applying the actual upgrade, useful for testing and verification purposes. |
-| `--filter` | `-f` | String | Specifies criteria for selecting devices when performing batch upgrades via Panorama, such as device hostname patterns or serial numbers. |
-| `--hostname` | `-h` | String | The IP address or DNS name of the target firewall or Panorama appliance. |
-| `--log-level` | `-l` | String | Determines the verbosity of log output, with levels including debug, info, and warning among others. |
-| `--password` | `-p` | String | The authentication password required for accessing the target device. |
-| `--username` | `-u` | String | The username for authentication with the target PAN-OS device. |
-| `--version` | `-v` | String | Specifies the target PAN-OS version for the upgrade operation. |
+
-Each CLI option has a specific role in tailoring the upgrade process, from defining the target device and authentication credentials to setting operational parameters like the target PAN-OS version and logging verbosity.
+```console
+touch settings.yaml
+```
-#### Option 1: Execute `pan-os-upgrade` without Command-Line Arguments
+
-You can simply get started by issuing `pan-os-upgrade` from your current working directory, you will be guided to input the missing requirement arguments through an interactive shell.
+> Note: Make sure that you created an empty `settings.yaml` file *before* you run the `settings` CLI argument, or else Docker will create `settings.yaml` as a folder instead of a file.
```console
-$ pan-os-upgrade firewall
-Firewall hostname or IP: houston.cdot.io
-Firewall username: cdot
-Firewall password:
-Target version: 10.2.4-h4
-Dry Run? [y/N]: N
-๐ houston: 007054000242050 192.168.255.211
-๐ houston: HA mode: disabled
-๐ houston: Current version: 10.2.4-h3
-๐ houston: Target version: 10.2.4-h4
-โ
houston: Upgrade required from 10.2.4-h3 to 10.2.4-h4
-โ
houston: version 10.2.4-h4 is available for download
-โ
houston: Base image for 10.2.4-h4 is already downloaded
-๐ houston: Performing test to see if 10.2.4-h4 is already downloaded...
-โ
houston: version 10.2.4-h4 already on target device.
-โ
houston: version 10.2.4-h4 has been downloaded.
-๐ houston: Performing snapshot of network state information...
-โ
houston: Network snapshot created successfully
-๐ houston: Performing readiness checks to determine if firewall is ready for upgrade...
-โ
houston: Passed Readiness Check: Check if there are pending changes on device
-โ
houston: Passed Readiness Check: No Expired Licenses
-โ
houston: Passed Readiness Check: Check if NTP is synchronized
-โ
houston: Passed Readiness Check: Check connectivity with the Panorama appliance
-โ
houston: Readiness Checks completed
-๐ houston: Performing backup of configuration to local filesystem...
-๐ houston: Not a dry run, continue with upgrade...
-๐ houston: Performing upgrade to version 10.2.4-h4...
-๐ houston: Attempting upgrade to version 10.2.4-h4 (Attempt 1 of 3)...
-Device 007054000242050 installing version: 10.2.4-h4
-โ
houston: Upgrade completed successfully
-๐ houston: Rebooting the standalone target device...
-๐ houston: Command succeeded with no output
-๐ง houston: Target device is rebooting...
-๐ง houston: Target device is rebooting...
-๐ง houston: Target device is rebooting...
-๐ง houston: Target device is rebooting...
-๐ง houston: Target device is rebooting...
-๐ houston: Target device version: 10.2.4-h4
-โ
houston: Target device rebooted in 448 seconds
+โฏ docker run \
+-v $(pwd)/settings.yaml:/app/settings.yaml \
+-it \
+ghcr.io/cdot65/pan-os-upgrade:latest settings
+===============================================================================
+Welcome to the PAN-OS upgrade settings menu
+
+You'll be presented with configuration items, press enter for default settings.
+
+This will create a `settings.yaml` file in your current working directory.
+===============================================================================
+Number of concurrent threads [10]: 35
+Logging level [INFO]: debug
+Path for log files [logs/upgrade.log]:
+Maximum log file size (MB) [10]:
+Number of upgrade logs to retain [10]:
+Reboot retry interval (seconds) [60]:
+Maximum reboot tries [30]: 45
+Would you like to customize readiness checks? [y/N]:
+Location to save readiness checks [assurance/readiness_checks/]:
+Would you like to customize snapshots? [y/N]:
+Location to save snapshots [assurance/snapshots/]:
+Connection timeout (seconds) [30]:
+Command timeout (seconds) [120]:
+Configuration saved to /app/settings.yaml
```
-##### Option 2: Execute `pan-os-upgrade` Using Command-Line Arguments
+Once you have a `settings.yaml` file in your current working directory, and you have reviewed its contents to make sure all of the settings match your expectations, then we must add it to the list of volume mounts in order to make the file accessible by the script within the container.
+
+Example `settings.yaml` file
+
+```yaml
+concurrency:
+ threads: 34
+logging:
+ file_path: logs/upgrade.log
+ level: INFO
+ max_size: 10
+ upgrade_log_count: 10
+readiness_checks:
+ checks: {}
+ customize: false
+ location: assurance/readiness_checks/
+reboot:
+ max_tries: 4
+ retry_interval: 10
+snapshots:
+ customize: true
+ location: assurance/snapshots/
+ state:
+ arp_table: true
+ content_version: true
+ ip_sec_tunnels: false
+ license: false
+ nics: true
+ routes: true
+ session_stats: false
+timeout_settings:
+ command_timeout: 120
+ connection_timeout: 30
+```
-Alternatively, you can pass these details as command-line arguments when running the script.
+You will be able to confirm that the file was discovered by the message within the banner `Custom configuration loaded from: /app/settings.yaml`. If you do *not* see this message in the banner, then you can assume that your `settings.yaml` file was not properly mounted to the container.
-> Note: You *can* pass your password as a CLI option with either `--password` or `-p`, but make sure you understand the risk of having your password in your terminal's history.
+
-```bash
-pan-os-upgrade firewall --hostname 192.168.1.1 --username admin --password secret --version 10.1.0
-```
+```console
+$ docker run \
+-v $(pwd)/assurance:/app/assurance \
+-v $(pwd)/logs:/app/logs \
+-v $(pwd)/settings.yaml:/app/settings.yaml \
+-it \
+ghcr.io/cdot65/pan-os-upgrade:latest firewall -v 10.2.5 -u cdot -h houston.cdot.io
+Firewall password:
+Dry Run? [y/N]:
+=========================================================
+Welcome to the PAN-OS upgrade tool
-For a dry run:
+You have selected to upgrade a single Firewall appliance.
-```bash
-pan-os-upgrade firewall --hostname 192.168.1.1 --username admin --password secret --version 10.1.0 --dry-run
+Custom configuration loaded from:
+/app/settings.yaml
+=========================================================
+๐ houston: 007054000242050 192.168.255.211
+๐ houston: HA mode: disabled
+๐ houston: Current version: 10.2.4-h4
+๐ houston: Target version: 10.2.5
+โ
houston: Upgrade required from 10.2.4-h4 to 10.2.5
+... shortened for brevity ...
+๐ง houston: Retry attempt 4 due to error: URLError: reason: [Errno 111] Connection refused
+๐ houston: Current device version: 10.2.5
+โ
houston: Device rebooted to the target version successfully.
```
-If you're targeting a Panorama appliance to act as a proxy for communications to the firewall, make sure you include a filter pattern:
-
-```bash
-pan-os-upgrade batch --hostname panorama.cdot.io --username admin --password secret --version 10.1.0 --filter "hostname=Woodlands*"
-```
+
## Troubleshooting Panorama Proxy Connections
diff --git a/docs/user-guide/python/execution.md b/docs/user-guide/python/execution.md
index a50adbb..5ea2707 100644
--- a/docs/user-guide/python/execution.md
+++ b/docs/user-guide/python/execution.md
@@ -11,12 +11,20 @@ You can start the script interactively by simply issuing `pan-os-upgrade` from y
```console
-$ pan-os-upgrade firewall
+pan-os-upgrade firewall
Firewall hostname or IP: houston.cdot.io
Firewall username: cdot
Firewall password:
Target version: 10.2.4-h4
Dry Run? [y/N]: N
+===================================================================
+Welcome to the PAN-OS upgrade tool
+
+You have selected to upgrade a single Firewall appliance.
+
+No settings.yaml file was found. Default values will be used.
+Create a settings.yaml file with 'pan-os-upgrade settings' command.
+===================================================================
๐ houston: 007054000242050 192.168.255.211
๐ houston: HA mode: disabled
๐ houston: Current version: 10.2.4-h3
@@ -61,7 +69,7 @@ Alternatively, you can pass these details as command-line arguments. This method
#### Direct Firewall Targeting
```bash
-$ pan-os-upgrade firewall --hostname 192.168.255.1 --username admin --password secret --version 10.1.0
+pan-os-upgrade firewall --hostname 192.168.255.1 --username admin --password secret --version 10.1.0
INFO - โ
Connection to firewall established
... shortened output for brevity ...
```
@@ -71,21 +79,66 @@ INFO - โ
Connection to firewall established
When using Panorama as a proxy, the `--filter` argument is necessary to specify the criteria for selecting the managed firewalls to upgrade.
```bash
-$ pan-os-upgrade panorama --hostname panorama.cdot.io --filter 'hostname=houston' --username admin --password secret --version 10.1.0
+pan-os-upgrade panorama --hostname panorama.cdot.io --filter 'hostname=houston' --username admin --password secret --version 10.1.0
โ
Connection to Panorama established. Firewall connections will be proxied!
... shortened output for brevity ...
```
+### CLI Arguments vs. CLI Options
+
+In the context of the `pan-os-upgrade` application, it's important to distinguish between CLI arguments and CLI options:
+
+- **CLI Arguments** are the primary commands that determine the operation mode of the application. They are not prefixed by `--` or `-` and are essential for defining the core action the script should perform.
+- **CLI Options**, on the other hand, are additional modifiers or settings that further customize the behavior of the CLI arguments. They typically come with a `--` prefix (or `-` for shorthand) and are optional.
+
+#### CLI Arguments
+
+The following are the main commands (CLI arguments) for the `pan-os-upgrade` application, each tailored for specific upgrade scenarios:
+
+| CLI Argument | Description |
+| ------------ | --------------------------------------------------------------------------------------------------------- |
+| `firewall` | Targets an individual firewall for upgrade. |
+| `panorama` | Targets an individual Panorama appliance for upgrade. |
+| `batch` | Utilizes a Panorama appliance to orchestrate bulk upgrades of managed firewalls. |
+| `settings` | Creates a `settings.yaml` that will allow users to customize the script's default settings and behaviors. |
+
+#### CLI Options
+
+Below are the CLI options that can be used in conjunction with the above CLI arguments to customize the upgrade process:
+
+| CLI Option | Shorthand | Type | Description |
+| ------------ | --------- | ------- | --------------------------------------------------------------------------------------- |
+| `--dry-run` | `-d` | Boolean | Executes all preparatory steps without applying the actual upgrade, useful for testing. |
+| `--filter` | `-f` | String | Specifies criteria for selecting devices when performing batch upgrades via Panorama. |
+| `--hostname` | `-h` | String | The IP address or DNS name of the target firewall or Panorama appliance. |
+| `--password` | `-p` | String | The authentication password required for accessing the target device. |
+| `--username` | `-u` | String | The username for authentication with the target PAN-OS device. |
+| `--version` | `-v` | String | Specifies the target PAN-OS version for the upgrade operation. |
+
+Each CLI option has a specific role in tailoring the upgrade process, from defining the target device and authentication credentials to setting operational parameters like the target PAN-OS version and logging verbosity.
+
+#### Option 1: Execute `pan-os-upgrade` without Command-Line Arguments
+
+You can simply get started by issuing `pan-os-upgrade` from your current working directory, you will be guided to input the missing requirement arguments through an interactive shell.
+
```console
-$ pan-os-upgrade batch
+pan-os-upgrade batch
Panorama hostname or IP: panorama.cdot.io
Panorama username: cdot
Panorama password:
Firewall target version (ex: 10.1.2): 10.2.3
Filter string (ex: hostname=Woodlands*) []: hostname=Woodlands*
Dry Run? [y/N]:
+===========================================================================
+Welcome to the PAN-OS upgrade tool
+
+You have selected to perform a batch upgrade of firewalls through Panorama.
+
+No settings.yaml file was found. Default values will be used.
+Create a settings.yaml file with 'pan-os-upgrade settings' command.
+===========================================================================
โ
panorama.cdot.io: Connection to Panorama established. Firewall connections will be proxied!
๐ Woodlands-fw2: 007954000123452 192.168.255.44
๐ Woodlands-fw1: 007954000123451 192.168.255.43
@@ -179,110 +232,124 @@ Device 007954000123451 installing version: 10.2.3
-### CLI Arguments vs. CLI Options
-
-In the context of the `pan-os-upgrade` application, it's important to distinguish between CLI arguments and CLI options:
-
-- **CLI Arguments** are the primary commands that determine the operation mode of the application. They are not prefixed by `--` or `-` and are essential for defining the core action the script should perform.
-- **CLI Options**, on the other hand, are additional modifiers or settings that further customize the behavior of the CLI arguments. They typically come with a `--` prefix (or `-` for shorthand) and are optional.
+##### Option 2: Execute `pan-os-upgrade` Using Command-Line Arguments
-#### CLI Arguments
+Alternatively, you can pass these details as command-line arguments when running the script.
-The following are the main commands (CLI arguments) for the `pan-os-upgrade` application, each tailored for specific upgrade scenarios:
+> Note: You *can* pass your password as a CLI option with either `--password` or `-p`, but make sure you understand the risk of having your password in your terminal's history.
-| CLI Argument | Description |
-| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `firewall` | Targets an individual firewall for upgrade. This command requires subsequent CLI options to specify the firewall details and desired actions. |
-| `panorama` | Targets an individual Panorama appliance for upgrade, necessitating further CLI options for execution details. |
-| `batch` | Utilizes a Panorama appliance to orchestrate bulk upgrades of managed firewalls, supporting up to ten concurrent operations. Requires additional CLI options for filtering and execution specifics. |
+```bash
+pan-os-upgrade firewall --hostname 192.168.1.1 --username admin --password secret --version 10.1.0
+```
-#### CLI Options
+For a dry run:
-Below are the CLI options that can be used in conjunction with the above CLI arguments to customize the upgrade process:
+```bash
+pan-os-upgrade firewall --hostname 192.168.1.1 --username admin --password secret --version 10.1.0 --dry-run
+```
-| CLI Option | Shorthand | Type | Description |
-| ------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
-| `--dry-run` | `-d` | Boolean | Executes all preparatory steps without applying the actual upgrade, useful for testing and verification purposes. |
-| `--filter` | `-f` | String | Specifies criteria for selecting devices when performing batch upgrades via Panorama, such as device hostname patterns or serial numbers. |
-| `--hostname` | `-h` | String | The IP address or DNS name of the target firewall or Panorama appliance. |
-| `--log-level` | `-l` | String | Determines the verbosity of log output, with levels including debug, info, and warning among others. |
-| `--password` | `-p` | String | The authentication password required for accessing the target device. |
-| `--username` | `-u` | String | The username for authentication with the target PAN-OS device. |
-| `--version` | `-v` | String | Specifies the target PAN-OS version for the upgrade operation. |
+If you're targeting a Panorama appliance to act as a proxy for communications to the firewall, make sure you include a filter pattern:
-Each CLI option has a specific role in tailoring the upgrade process, from defining the target device and authentication credentials to setting operational parameters like the target PAN-OS version and logging verbosity.
+```bash
+pan-os-upgrade batch --hostname panorama.cdot.io --username admin --password secret --version 10.1.0 --filter "hostname=Woodlands*"
+```
-#### Option 1: Execute `pan-os-upgrade` without Command-Line Arguments
+## Advanced Settings
-You can simply get started by issuing `pan-os-upgrade` from your current working directory, you will be guided to input the missing requirement arguments through an interactive shell.
+If you would like to change the default settings of `pan-os-upgrade` tool, you can run the `settings` CLI argument. This will walk you through a series of options to change.
```console
-$ pan-os-upgrade firewall
-Firewall hostname or IP: houston.cdot.io
-Firewall username: cdot
-Firewall password:
-Target version: 10.2.4-h4
-Dry Run? [y/N]: N
-๐ houston: 007054000242050 192.168.255.211
-๐ houston: HA mode: disabled
-๐ houston: Current version: 10.2.4-h3
-๐ houston: Target version: 10.2.4-h4
-โ
houston: Upgrade required from 10.2.4-h3 to 10.2.4-h4
-โ
houston: version 10.2.4-h4 is available for download
-โ
houston: Base image for 10.2.4-h4 is already downloaded
-๐ houston: Performing test to see if 10.2.4-h4 is already downloaded...
-โ
houston: version 10.2.4-h4 already on target device.
-โ
houston: version 10.2.4-h4 has been downloaded.
-๐ houston: Performing snapshot of network state information...
-โ
houston: Network snapshot created successfully
-๐ houston: Performing readiness checks to determine if firewall is ready for upgrade...
-โ
houston: Passed Readiness Check: Check if there are pending changes on device
-โ
houston: Passed Readiness Check: No Expired Licenses
-โ
houston: Passed Readiness Check: Check if NTP is synchronized
-โ
houston: Passed Readiness Check: Check connectivity with the Panorama appliance
-โ
houston: Readiness Checks completed
-๐ houston: Performing backup of configuration to local filesystem...
-๐ houston: Not a dry run, continue with upgrade...
-๐ houston: Performing upgrade to version 10.2.4-h4...
-๐ houston: Attempting upgrade to version 10.2.4-h4 (Attempt 1 of 3)...
-Device 007054000242050 installing version: 10.2.4-h4
-โ
houston: Upgrade completed successfully
-๐ houston: Rebooting the standalone target device...
-๐ houston: Command succeeded with no output
-๐ง houston: Target device is rebooting...
-๐ง houston: Target device is rebooting...
-๐ง houston: Target device is rebooting...
-๐ง houston: Target device is rebooting...
-๐ง houston: Target device is rebooting...
-๐ houston: Target device version: 10.2.4-h4
-โ
houston: Target device rebooted in 448 seconds
+pan-os-upgrade settings
+===============================================================================
+Welcome to the PAN-OS upgrade settings menu
+
+You'll be presented with configuration items, press enter for default settings.
+
+This will create a `settings.yaml` file in your current working directory.
+===============================================================================
+Number of concurrent threads [10]: 35
+Logging level [INFO]: debug
+Path for log files [logs/upgrade.log]:
+Maximum log file size (MB) [10]:
+Number of upgrade logs to retain [10]:
+Reboot retry interval (seconds) [60]:
+Maximum reboot tries [30]: 45
+Would you like to customize readiness checks? [y/N]:
+Location to save readiness checks [assurance/readiness_checks/]:
+Would you like to customize snapshots? [y/N]:
+Location to save snapshots [assurance/snapshots/]:
+Connection timeout (seconds) [30]:
+Command timeout (seconds) [120]:
+Configuration saved to /app/settings.yaml
```
-##### Option 2: Execute `pan-os-upgrade` Using Command-Line Arguments
+Once you have a `settings.yaml` file in your current working directory, take a moment to review its contents to make sure all of the settings match your expectations.
+
+Example `settings.yaml` file
+
+```yaml
+concurrency:
+ threads: 34
+logging:
+ file_path: logs/upgrade.log
+ level: INFO
+ max_size: 10
+ upgrade_log_count: 10
+readiness_checks:
+ checks: {}
+ customize: false
+ location: assurance/readiness_checks/
+reboot:
+ max_tries: 4
+ retry_interval: 10
+snapshots:
+ customize: true
+ location: assurance/snapshots/
+ state:
+ arp_table: true
+ content_version: true
+ ip_sec_tunnels: false
+ license: false
+ nics: true
+ routes: true
+ session_stats: false
+timeout_settings:
+ command_timeout: 120
+ connection_timeout: 30
+```
-Alternatively, you can pass these details as command-line arguments when running the script.
+You will be able to confirm that the file was discovered by the message within the banner `Custom configuration loaded from: /path/to/your/settings.yaml`. If you do *not* see this message in the banner, then you can assume that your `settings.yaml` file was not properly discovered by the script.
-> Note: You *can* pass your password as a CLI option with either `--password` or `-p`, but make sure you understand the risk of having your password in your terminal's history.
+
-```bash
-pan-os-upgrade firewall --hostname 192.168.1.1 --username admin --password secret --version 10.1.0
-```
+```console
+pan-os-upgrade firewall -v 10.2.5 -u cdot -h houston.cdot.io
+Firewall password:
+Dry Run? [y/N]:
+=========================================================
+Welcome to the PAN-OS upgrade tool
-For a dry run:
+You have selected to upgrade a single Firewall appliance.
-```bash
-pan-os-upgrade firewall --hostname 192.168.1.1 --username admin --password secret --version 10.1.0 --dry-run
+Custom configuration loaded from:
+/Users/cdot/development/pan-os-upgrade/settings.yaml
+=========================================================
+๐ houston: 007054000242050 192.168.255.211
+๐ houston: HA mode: disabled
+๐ houston: Current version: 10.2.4-h4
+๐ houston: Target version: 10.2.5
+โ
houston: Upgrade required from 10.2.4-h4 to 10.2.5
+... shortened for brevity ...
+๐ง houston: Retry attempt 4 due to error: URLError: reason: [Errno 111] Connection refused
+๐ houston: Current device version: 10.2.5
+โ
houston: Device rebooted to the target version successfully.
```
-If you're targeting a Panorama appliance to act as a proxy for communications to the firewall, make sure you include a filter pattern:
-
-```bash
-pan-os-upgrade batch --hostname panorama.cdot.io --username admin --password secret --version 10.1.0 --filter "hostname=Woodlands*"
-```
+
## Output and Assurance Functions
@@ -298,7 +365,7 @@ The script performs various assurance functions like readiness checks, snapshots
### Log Files and Levels
-Log entries are recorded in the `logs/` directory. The verbosity of logs can be controlled with the `--log-level` argument, with available options being `debug`, `info`, `warning`, `error`, and `critical`.
+Log entries are recorded in the `logs/` directory. The verbosity of logs can be controlled by creating a `settings.yaml` file with `pan-os-upgrade settings` CLI command. Available options being `debug`, `info`, `warning`, `error`, and `critical`.
## Next Steps
diff --git a/pan_os_upgrade/upgrade.py b/pan_os_upgrade/upgrade.py
index 441b480..31579f3 100644
--- a/pan_os_upgrade/upgrade.py
+++ b/pan_os_upgrade/upgrade.py
@@ -1,21 +1,14 @@
"""
-Upgrade.py: Automating the Upgrade Process for Palo Alto Networks Firewalls and Panorama
+upgrade.py: Automating the Upgrade Process for Palo Alto Networks Devices
-This module provides a comprehensive framework for automating the upgrade processes of Palo Alto Networks firewalls
-and Panorama appliances. It is designed to facilitate both standalone operations and integration into larger workflows,
-offering a range of features essential for successful PAN-OS upgrades. The script utilizes Typer for creating a
-command-line interface, allowing for direct input of parameters, and supports username/password-based authentication.
+This script automates the upgrade process for Palo Alto Networks Firewalls and Panorama appliances, providing a seamless and efficient way to perform upgrades. It leverages Typer for a command-line interface, enabling users to specify parameters directly. The script supports upgrading standalone firewalls, Panorama appliances, and batch upgrading firewalls managed by Panorama. Additionally, it offers a settings command to generate a `settings.yaml` file, allowing users to override default script settings.
Features
--------
-- Automated Upgrade Procedures: Supports both standalone firewalls and those managed by Panorama, streamlining the
- upgrade process with minimal manual intervention.
-- Error Handling: Implements extensive error handling mechanisms tailored to PAN-OS, ensuring the upgrade process is
- robust across various scenarios.
-- panos-upgrade-assurance Integration: Leverages the panos-upgrade-assurance tool for conducting pre and post-upgrade
- checks, enhancing the reliability of the upgrade.
-- Command-line Interface: Employs Typer for CLI creation, enabling direct parameter input and reducing reliance on
- environment files.
+- **Automated Upgrades**: Simplifies the upgrade process for firewalls and Panorama appliances, minimizing the need for manual intervention.
+- **Comprehensive Error Handling**: Incorporates extensive error handling to manage common and unforeseen issues during the upgrade process.
+- **Integration with panos-upgrade-assurance**: Utilizes the panos-upgrade-assurance tool to perform pre and post-upgrade checks, ensuring device readiness.
+- **Flexible Configuration**: Allows users to customize the upgrade process through a `settings.yaml` file, providing control over readiness checks, snapshots, and more.
Imports
-------
@@ -24,27 +17,55 @@
- ipaddress: Provides utilities for IP address manipulation.
- logging: Enables detailed logging throughout the upgrade process.
- os, sys: Interfaces with the operating system for file and directory operations.
- - time: Manages time-related functions such as delays and timeouts.
- - RotatingFileHandler (logging.handlers): Manages log file rotation to limit file sizes and maintain log history.
+ - time, re: Manages time-related functions and regular expression operations.
+ - yaml: Handles YAML file parsing for settings configuration.
+ - RemoteDisconnected, RotatingFileHandler: Manages HTTP connections and log file rotation.
+ - Path, Lock, typing: Provides file path utilities, synchronization primitives, and type annotations.
External Libraries:
- xml.etree.ElementTree (ET): Handles XML tree structure manipulation, essential for parsing PAN-OS API responses.
+ - dns.resolver: Facilitates DNS lookups for hostname resolution.
+ - Dynaconf: Manages dynamic configuration and settings for the script.
+ - typer: Simplifies the creation of command-line interfaces, enhancing user interaction.
+
+Palo Alto Networks libraries:
- panos: Offers interfaces to Palo Alto Networks devices for direct API interaction.
- - PanDevice, SystemSettings (panos.base, panos.device): Facilitates operations on base PAN-OS devices and system settings.
- - Error handling modules (panos.errors): Provides specific error management capabilities for PAN-OS.
- - Firewall (panos.firewall): Specializes in firewall-specific operations and configurations.
+ - PanDevice, SystemSettings: Facilitates operations on base PAN-OS devices and system settings.
+ - Firewall, Panorama: Specializes in firewall and Panorama-specific operations and configurations.
+ - Error handling modules: Provides specific error management capabilities for PAN-OS.
panos-upgrade-assurance package:
- CheckFirewall, FirewallProxy: Essential for performing readiness checks and serving as intermediaries to the firewall.
-Third-party libraries:
- - xmltodict: Simplifies the conversion of XML data into Python dictionaries, aiding in data parsing and manipulation.
- - typer: Simplifies the creation of command-line interfaces, enhancing user interaction.
- - BaseModel (pydantic): Enables the definition of Pydantic models for structured data handling.
-
Project-specific imports:
- - SnapshotReport, ReadinessCheckReport (pan_os_upgrade.models): Utilized for managing and storing snapshot and
- readiness check reports in a structured format.
+ - SnapshotReport, ReadinessCheckReport: Utilized for managing and storing snapshot and readiness check reports in a structured format.
+ - ManagedDevice, ManagedDevices: Models for handling device information and collections.
+
+Subcommands
+-----------
+- `firewall`: Initiates the upgrade process for a single firewall device.
+- `panorama`: Upgrades a Panorama appliance.
+- `batch`: Conducts batch upgrades for firewalls managed by a Panorama appliance.
+- `settings`: Generates a `settings.yaml` file for customizing script settings.
+
+Usage
+-----
+The script can be executed with various subcommands and options to tailor the upgrade process to specific needs. For example, to upgrade a firewall:
+
+ python upgrade.py firewall --hostname
--username --password --version
+
+To perform a batch upgrade of firewalls using Panorama as a communication proxy:
+
+ python upgrade.py batch --hostname --username --password --version
+
+To generate a custom `settings.yaml` file:
+
+ python upgrade.py settings
+
+Notes
+-----
+- Ensure network connectivity and valid credentials before initiating the upgrade process.
+- The `settings.yaml` file provides an opportunity to customize various aspects of the upgrade process, including which readiness checks to perform and snapshot configurations.
"""
@@ -55,9 +76,11 @@
import sys
import time
import re
+import yaml
from concurrent.futures import ThreadPoolExecutor, as_completed
from http.client import RemoteDisconnected
from logging.handlers import RotatingFileHandler
+from pathlib import Path
from threading import Lock
from typing import Dict, List, Optional, Tuple, Union
from typing_extensions import Annotated
@@ -84,6 +107,7 @@
# third party imports
import dns.resolver
+from dynaconf import Dynaconf
import typer
# project imports
@@ -96,64 +120,54 @@
)
-# ----------------------------------------------------------------------------
-# Define logging levels
-# ----------------------------------------------------------------------------
-LOGGING_LEVELS = {
- "debug": logging.DEBUG,
- "info": logging.INFO,
- "warning": logging.WARNING,
- "error": logging.ERROR,
- "critical": logging.CRITICAL,
-}
-
-
# ----------------------------------------------------------------------------
# Define panos-upgrade-assurance options
# ----------------------------------------------------------------------------
class AssuranceOptions:
"""
- A class encapsulating configuration options for the panos-upgrade-assurance process in appliances.
+ Defines configuration options for readiness checks, reports, and state snapshots for the upgrade assurance process.
- This class is a central repository for various configurations used in the upgrade assurance process.
- It includes definitions for readiness checks, state snapshots, and report types, which are crucial
- components in managing and ensuring the successful upgrade of appliances.
+ This class serves as a central repository for configurations related to the upgrade assurance process of Palo Alto Networks
+ devices. It specifies the available readiness checks, report types, and state snapshot categories that can be utilized
+ during the device upgrade process. These configurations can be adjusted through a `settings.yaml` file, offering flexibility
+ to customize the upgrade process according to specific needs and preferences.
Attributes
----------
READINESS_CHECKS : dict
- A dictionary mapping the names of readiness checks to their properties. Each property is a
- dictionary containing a description of the check, the log level to use when reporting the
- outcome of the check, and a flag indicating whether to exit the process upon failure of the check.
- - `active_support`: Verifies if active support is available for the appliance.
- - `arp_entry_exist`: Checks for a specific ARP entry in the ARP table.
- - `candidate_config`: Checks for pending changes on the device.
- - `certificates_requirements`: Verifies if certificates' keys meet minimum size requirements.
- - ... (other checks follow a similar structure)
-
+ Maps names of readiness checks to their attributes, including descriptions, associated log levels, and flags indicating
+ whether to exit the process upon check failure. These checks aim to ensure the device's readiness for an upgrade by
+ validating various operational and configuration aspects.
REPORTS : list of str
- A list of strings where each string represents a type of report that can be generated
- for the appliance. These reports provide insight into various aspects of the appliance's state.
- Includes reports like 'arp_table', 'content_version', 'ip_sec_tunnels', etc.
-
+ Enumerates types of reports that can be generated to provide insights into the device's state pre- and post-upgrade.
+ These reports cover various aspects such as ARP tables, content versions, IPsec tunnels, licenses, network interfaces,
+ routing tables, and session statistics.
STATE_SNAPSHOTS : list of str
- A list of strings where each string represents a type of state snapshot that can be captured
- from the appliance. These snapshots record essential data about the appliance's current state,
- such as 'arp_table', 'content_version', 'ip_sec_tunnels', etc.
+ Lists categories of state snapshots that can be captured to document crucial data about the device's current state.
+ These snapshots are valuable for diagnostics and for verifying the device's operational status before proceeding with
+ the upgrade.
Examples
--------
- To access the log level for the 'active_support' readiness check:
+ Accessing the log level for 'active_support' readiness check:
>>> log_level = AssuranceOptions.READINESS_CHECKS['active_support']['log_level']
>>> print(log_level)
- warning
+ 'warning'
- To iterate over all report types:
- >>> for report in AssuranceOptions.REPORTS:
- >>> print(report)
- arp_table
- content_version
+ Iterating through all available report types:
+ >>> for report_type in AssuranceOptions.REPORTS:
+ ... print(report_type)
+ 'arp_table'
+ 'content_version'
...
+
+ Notes
+ -----
+ - The configurations for readiness checks, report types, and state snapshots outlined in this class can be selectively
+ enabled or customized through the `settings.yaml` file, allowing users to tailor the upgrade assurance process to their
+ specific requirements.
+ - Default settings for all options are provided within this class, but they can be overridden by custom configurations
+ specified in the `settings.yaml` file, enhancing the script's adaptability to various upgrade scenarios.
"""
READINESS_CHECKS = {
@@ -255,13 +269,6 @@ class AssuranceOptions:
]
-# ----------------------------------------------------------------------------
-# Global list and lock for storing HA active firewalls and Panorama to revisit
-# ----------------------------------------------------------------------------
-target_devices_to_revisit = []
-target_devices_to_revisit_lock = Lock()
-
-
# ----------------------------------------------------------------------------
# Core Upgrade Functions
# ----------------------------------------------------------------------------
@@ -273,49 +280,51 @@ def backup_configuration(
"""
Backs up the current running configuration of a specified target device to a local file.
- This function interacts with either a Firewall or Panorama appliance to retrieve its current running
- configuration. The configuration is then saved to a specified file path in XML format. It includes validations
- to ensure the integrity of the retrieved XML data and logs the process's outcome. The function is designed
- to be flexible, accommodating both Firewall and Panorama devices by utilizing their common base class.
+ This function retrieves the running configuration from the target device, either a Firewall or Panorama, and saves it to
+ a specified file path in XML format. It validates the integrity of the retrieved XML data and provides detailed logging
+ of each step in the process. The backup is crucial for ensuring a recovery point before making significant changes, such
+ as upgrades or policy modifications.
Parameters
----------
target_device : Union[Firewall, Panorama]
- An instance of the Firewall or Panorama class from which the running configuration will be backed up.
+ The device from which the running configuration will be backed up. It must be an initialized and connected
+ instance of either a Firewall or Panorama class.
hostname : str
- The hostname of the target device, used for logging and identification purposes.
+ The hostname or IP address of the target device, utilized for identification and logging purposes.
file_path : str
- The local filesystem path where the backup configuration file will be stored.
+ The local filesystem path where the configuration file will be saved. The function ensures the existence
+ of the target directory, creating it if necessary.
Returns
-------
bool
- True if the backup process is successful and the configuration is saved to the specified file.
- False if an error occurs during the backup process.
+ True if the backup process completes successfully and the configuration is accurately saved to the file.
+ False if any error occurs during the backup process, including retrieval or file writing issues.
Raises
------
Exception
- If an unexpected error occurs during the retrieval or saving of the configuration, an exception is raised
- with a descriptive error message.
+ An exception is raised if an unexpected error occurs during the configuration retrieval or file writing process,
+ accompanied by a descriptive error message for troubleshooting.
Examples
--------
- Backing up the configuration of a firewall:
- >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password')
- >>> backup_configuration(firewall, 'firewall1', '/path/to/firewall_config.xml')
- True # Indicates successful backup.
+ Backing up a Firewall's configuration:
+ >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='adminpassword')
+ >>> backup_configuration(firewall, 'firewall1', '/backups/firewall1_config.xml')
+ True # Indicates a successful backup
- Backing up the configuration of a Panorama appliance:
- >>> panorama = Panorama(hostname='panorama.example.com', api_username='admin', api_password='password')
- >>> backup_configuration(panorama, 'panorama1', '/path/to/panorama_config.xml')
- True # Indicates successful backup.
+ Backing up a Panorama appliance's configuration:
+ >>> panorama = Panorama(hostname='panorama.example.com', api_username='admin', api_password='adminpassword')
+ >>> backup_configuration(panorama, 'panorama1', '/backups/panorama1_config.xml')
+ True # Indicates a successful backup
Notes
-----
- - The function verifies the existence of the target directory for the backup file, creating it if necessary.
- - The backup process involves retrieving the XML structure of the running configuration and writing it to a file.
- - Proper error handling is implemented to catch and log potential issues during the backup process.
+ - The configuration is stored in XML format, reflecting the device's current configuration state.
+ - This function forms part of a broader suite of utilities aimed at facilitating the upgrade and management process of
+ Palo Alto Networks devices, providing administrators with a means to preserve configurations for recovery and compliance.
"""
try:
# Run operational command to retrieve configuration
@@ -370,44 +379,52 @@ def determine_upgrade(
target_maintenance: Union[int, str],
) -> None:
"""
- Evaluates if an upgrade is necessary for a target device based on the specified version.
+ Determines the necessity of an upgrade for the target device based on the specified version components.
- This function assesses the need for upgrading the target device (either a Firewall or Panorama appliance) to a specified PAN-OS version. It compares the device's current version against the desired target version, which is defined by major, minor, and maintenance (or hotfix) components. If the current version is older, an upgrade is deemed necessary. If it's the same or newer, the function logs a message indicating no upgrade is needed or that a downgrade attempt was detected, and then exits the script.
+ This function evaluates whether the target device, either a Firewall or Panorama, requires an upgrade to the
+ specified PAN-OS version. It compares the device's current software version against the desired version, defined
+ by major, minor, and maintenance (or hotfix) components. An upgrade is considered necessary if the target version
+ is newer than the current version. If the device's version is already equal to or newer than the target version,
+ the function logs an appropriate message and terminates the script to prevent unnecessary upgrades or downgrades.
Parameters
----------
target_device : Union[Firewall, Panorama]
- The device (Firewall or Panorama) whose PAN-OS version is to be evaluated for upgrading.
+ An instance of the Firewall or Panorama class representing the device to be evaluated for an upgrade.
hostname : str
- The hostname or IP address of the target device, used for logging purposes.
+ The hostname or IP address of the target device, used primarily for logging and identification purposes.
target_major : int
- The major version number of the target PAN-OS version to upgrade to.
+ The major version number of the target PAN-OS version.
target_minor : int
The minor version number of the target PAN-OS version.
target_maintenance : Union[int, str]
- The maintenance version number of the target PAN-OS version, which can also include a hotfix designation (e.g., "4-h1").
+ The maintenance or hotfix version of the target PAN-OS version. Can be an integer for maintenance versions
+ or a string for hotfix versions (e.g., '1-h1').
Raises
------
SystemExit
- Exits the script if no upgrade is required, indicating either the current version is already adequate or a downgrade attempt was made.
+ Terminates the script execution if an upgrade is deemed unnecessary, either due to the current version being
+ up-to-date or a downgrade attempt being detected.
Examples
--------
- Evaluating if an upgrade is needed for a firewall:
- >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password')
+ Evaluating the need for an upgrade on a firewall device:
+ >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='adminpassword')
>>> determine_upgrade(firewall, 'firewall1', 9, 1, 0)
- # Logs the current version and whether an upgrade to 9.1.0 is necessary.
+ # This will log the current version and the decision on whether an upgrade to version 9.1.0 is necessary.
- Evaluating if an upgrade is needed for a Panorama appliance:
- >>> panorama = Panorama(hostname='panorama.example.com', api_username='admin', api_password='password')
+ Evaluating the need for an upgrade on a Panorama appliance:
+ >>> panorama = Panorama(hostname='panorama.example.com', api_username='admin', api_password='adminpassword')
>>> determine_upgrade(panorama, 'panorama1', 10, 0, '1-h1')
- # Logs the current version and whether an upgrade to 10.0.1-h1 is necessary.
+ # This will log the current version and the decision on whether an upgrade to version 10.0.1-h1 is necessary.
Notes
-----
- - The function uses version parsing to accurately compare the current device version with the target version.
- - In case of a downgrade attempt or if the device is already at the target version, the script will log an appropriate message and exit to prevent unintended downgrades or redundant upgrades.
+ - The function parses the current and target versions into a comparable format to accurately determine the need for
+ an upgrade.
+ - This evaluation is crucial to avoid unnecessary upgrades or downgrades, ensuring the device's software remains
+ stable and secure.
"""
current_version = parse_version(target_device.version)
@@ -444,41 +461,41 @@ def get_ha_status(
hostname: str,
) -> Tuple[str, Optional[dict]]:
"""
- Retrieves the High-Availability (HA) status and configuration details of a target device.
+ Retrieves the High Availability (HA) status and configuration details of a target device.
- This function queries a Palo Alto Networks device (Firewall or Panorama) to determine its HA status,
- identifying whether it is in standalone mode, part of an active/passive HA pair, an active/active HA pair,
- or a cluster configuration. It returns both the HA deployment type as a string and, if applicable,
- a dictionary containing detailed HA configuration information, such as local and peer device info.
+ This function queries the HA status of a specified Palo Alto Networks device, either a Firewall or Panorama.
+ It identifies the device's HA mode, such as standalone, active/passive, active/active, or cluster. The function
+ returns the HA mode along with a dictionary of detailed HA configuration if the device is part of an HA setup.
Parameters
----------
target_device : Union[Firewall, Panorama]
- The device instance (Firewall or Panorama) whose HA status is being queried.
+ The device instance from which the HA status is to be retrieved. This can be either a Firewall or Panorama
+ instance, initialized with proper credentials and connectivity.
hostname : str
- The hostname or IP address of the target device, used for logging and contextual purposes in the output.
+ The hostname or IP address of the target device. Used for logging purposes to identify the device in logs.
Returns
-------
Tuple[str, Optional[dict]]
- A tuple where the first element is a string describing the HA deployment type (e.g., 'standalone',
- 'active/passive', 'active/active'), and the second element is an optional dictionary containing detailed
- HA configuration information if the device is part of an HA setup, otherwise None.
+ A tuple containing the HA mode as a string and an optional dictionary with HA configuration details.
+ The dictionary is provided if the device is in an HA setup; otherwise, None is returned.
Example
-------
- Retrieving HA status for a firewall:
- >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password')
- >>> ha_status, ha_details = get_ha_status(firewall, 'firewall1')
- >>> print(ha_status) # e.g., 'active/passive'
- >>> print(ha_details) # e.g., {'local-info': {...}, 'peer-info': {...}}
+ Retrieving HA status for a firewall device:
+ >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
+ >>> ha_mode, ha_config = get_ha_status(firewall, 'fw-hostname')
+ >>> print(ha_mode) # e.g., 'active/passive'
+ >>> if ha_config:
+ ... print(ha_config) # Detailed HA configuration information
Notes
-----
- - This function is essential for assessing the HA readiness and configuration of a device before performing
- operations that could be affected by HA status, such as upgrades or maintenance tasks.
- - It relies on the 'show_highavailability_state' operational command and parses the XML response to extract
- meaningful HA status information, enhancing the automation and monitoring capabilities for administrators.
+ - Understanding the HA status is crucial before performing certain operations like upgrades or maintenance to
+ ensure they are done safely without affecting the device's operational status.
+ - The function parses the device's operational command response to extract the HA status and configuration,
+ making it a valuable tool for network administrators and automation scripts.
"""
logging.debug(
f"{get_emoji('start')} {hostname}: Getting {target_device.serial} deployment information..."
@@ -504,43 +521,41 @@ def handle_ha_logic(
dry_run: bool,
) -> Tuple[bool, Optional[Union[Firewall, Panorama]]]:
"""
- Handles High Availability (HA) logic for a target device during the upgrade process.
+ Manages the High Availability (HA) logic for a target device during the upgrade process.
- This function evaluates the HA status of the target device to determine the appropriate upgrade strategy.
- It assesses whether the device is part of an HA pair and its role (active or passive). Based on the HA
- configuration and the dry run flag, it decides whether to proceed with the upgrade and whether any HA-specific
- actions are required (e.g., suspending HA on the active device). In dry run mode, the function simulates these
- decisions without making any changes to the device.
+ Evaluates the HA configuration of the target device to determine the appropriate upgrade approach.
+ This includes understanding the device's role in an HA setup (active, passive, or standalone) and making
+ decisions based on this status and the dry_run flag. The function guides whether the upgrade should proceed
+ and if any HA-specific actions are needed, such as suspending HA synchronization on the active device.
Parameters
----------
target_device : Union[Firewall, Panorama]
- The device (Firewall or Panorama) being considered for upgrade, which may be part of an HA pair.
+ The device being evaluated, which can be a Firewall or Panorama instance. This device may be part of an HA configuration.
hostname : str
- The hostname or IP address of the target device, used for logging and contextual information.
+ The hostname or IP address of the target device, utilized for identification in logging outputs.
dry_run : bool
- Specifies whether to simulate the HA logic without making actual changes. Useful for testing and verification.
+ A flag indicating whether the logic should be simulated (True) without making actual changes, or executed (False).
Returns
-------
Tuple[bool, Optional[Union[Firewall, Panorama]]]
- A tuple containing a boolean indicating whether to proceed with the upgrade, and an optional device instance
- (either Firewall or Panorama) representing the HA peer that should be targeted for the upgrade, if applicable.
+ A tuple where the first element is a boolean indicating if the upgrade should proceed, and the second element is
+ an optional device instance (Firewall or Panorama) representing the HA peer, if the upgrade involves HA considerations.
Example
-------
- Evaluating HA logic for upgrading a target device:
- >>> target_device = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password')
- >>> proceed_with_upgrade, ha_peer_device = handle_ha_logic(target_device, 'firewall1', dry_run=True)
- >>> print(proceed_with_upgrade) # True if upgrade should proceed, False otherwise
- >>> print(ha_peer_device) # None if no HA peer or upgrade not directed at peer, otherwise Firewall or Panorama instance
+ Evaluating HA logic for a device upgrade:
+ >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
+ >>> proceed, ha_peer = handle_ha_logic(firewall, 'firewall1', dry_run=False)
+ >>> print(proceed) # Indicates if upgrade should continue
+ >>> print(ha_peer) # None if no HA peer involved or if not targeting peer, otherwise device instance
Notes
-----
- - The function first retrieves the HA status of the target device to make informed decisions.
- - It compares the software versions between the HA pair to determine the upgrade path and ensure consistency.
- - The `dry_run` flag allows administrators to assess the potential impact of HA logic on the upgrade process
- without affecting the device's state, providing a safe environment for planning and testing upgrade strategies.
+ - The function initially retrieves the HA status to make informed decisions regarding the upgrade process.
+ - It accounts for HA roles and version discrepancies between HA peers to ensure a coherent upgrade strategy.
+ - The dry_run option allows for a non-disruptive evaluation of the HA logic, aiding in planning and testing.
"""
deploy_info, ha_details = get_ha_status(
target_device,
@@ -610,51 +625,50 @@ def perform_ha_sync_check(
strict_sync_check: bool = True,
) -> bool:
"""
- Checks the HA synchronization status between HA peers in a Palo Alto Networks device setup.
+ Verifies synchronization status between HA peers for a Palo Alto Networks device.
- This function evaluates the High Availability (HA) synchronization status between a target device and its HA peer
- based on the provided HA details. It ensures that both devices in an HA pair are synchronized to prevent issues
- during operations such as upgrades. The function allows for a strict or lenient approach to handling
- synchronization failures, controlled by the `strict_sync_check` parameter. In strict mode, a failure in
- synchronization results in script termination, while in lenient mode, a warning is logged, and the script continues.
+ This function checks if the High Availability (HA) peers are synchronized, which is crucial before performing
+ operations that could affect the device state, such as upgrades. It uses HA details to determine synchronization
+ status and supports a strict mode that halts the script on synchronization failures.
Parameters
----------
hostname : str
- The hostname or IP address of the target device for which the HA synchronization status is being checked.
- Used primarily for logging purposes.
+ The hostname or IP address of the target device. Used for logging to identify the device being checked.
ha_details : dict
- A dictionary containing detailed information about the HA status of the target device, including its
- synchronization state with its HA peer.
+ Detailed HA information for the target device, including synchronization status with its HA peer.
strict_sync_check : bool, optional
- A flag indicating whether the synchronization check should be enforced strictly. If True, the script will
- exit upon detecting unsynchronized HA peers. Defaults to True.
+ Determines the handling of synchronization failures: if True (default), the script exits on failure;
+ if False, it logs a warning and continues.
Returns
-------
bool
- Returns True if the HA peers are synchronized, False otherwise. In strict mode, the script exits instead
- of returning False when synchronization fails.
+ True if HA peers are synchronized, indicating it's safe to proceed with sensitive operations. False indicates
+ a lack of synchronization, with the script's response depending on the `strict_sync_check` setting.
Raises
------
SystemExit
- If `strict_sync_check` is True and the HA peers are not synchronized, this function will terminate the script.
+ If `strict_sync_check` is True and synchronization fails, the script will terminate to prevent potential issues.
Example
-------
- Performing an HA synchronization check in strict mode:
+ Performing a strict HA synchronization check:
>>> ha_details = {'result': {'group': {'running-sync': 'synchronized'}}}
- >>> perform_ha_sync_check('firewall1', ha_details, strict_sync_check=True)
- True # Indicates that the HA peers are synchronized
+ >>> perform_ha_sync_check('firewall1', ha_details)
+ True # HA peers are synchronized
+
+ Performing a lenient HA synchronization check:
+ >>> perform_ha_sync_check('firewall1', ha_details, strict_sync_check=False)
+ False # HA peers are not synchronized, but script continues
Notes
-----
- - This function is critical in scenarios where configuration changes or upgrades are being applied to HA pairs,
- ensuring both devices are at the same configuration state.
- - The `ha_details` parameter is expected to contain specific keys and values that represent the HA status
- as retrieved from the device, which this function parses to determine synchronization status.
+ - Synchronization checks are essential in HA environments to ensure consistency between devices before making changes.
+ - The function enhances automation scripts' robustness by preventing actions that could disrupt unsynchronized HA setups.
"""
+
logging.info(f"{get_emoji('start')} {hostname}: Checking if HA peer is in sync...")
if ha_details and ha_details["result"]["group"]["running-sync"] == "synchronized":
logging.info(
@@ -681,25 +695,22 @@ def perform_readiness_checks(
file_path: str,
) -> None:
"""
- Conducts a comprehensive readiness assessment on a Palo Alto Networks Firewall before initiating upgrade procedures.
+ Performs a series of readiness checks on a Palo Alto Networks Firewall to ensure it is prepared for an upgrade.
- This function evaluates the firewall's current state by performing a series of checks designed to ascertain its
- readiness for an upgrade. These checks encompass various operational and configuration aspects, including but not
- limited to, candidate configuration, content version, license status, High Availability (HA) setup, disk space,
- NTP synchronization, and connection to Panorama. The results are meticulously logged and compiled into a detailed
- JSON report, which is then saved to the specified file path.
+ This function assesses the firewall's readiness by executing various checks related to configuration, licensing,
+ software versions, and more. The results are aggregated into a comprehensive JSON report that is saved to the
+ provided file path. The checks to be performed can be customized via the `settings.yaml` file, allowing for
+ flexibility based on specific operational requirements.
Parameters
----------
firewall : Firewall
- The Firewall object representing the device to be checked. This object should be initialized with the
- necessary credentials and connection details.
+ The Firewall instance to be checked, initialized with appropriate credentials and connectivity.
hostname : str
- The hostname or IP address of the firewall device. This identifier is primarily used for logging purposes
- to provide clear and contextual information in the log output.
+ The hostname or IP address of the firewall, used for logging and identification purposes.
file_path : str
- The absolute or relative path where the JSON-formatted readiness report will be saved. If the specified
- directory does not exist, it will be created.
+ The file path where the JSON report of the readiness checks will be saved. The directory will be created
+ if it does not exist.
Returns
-------
@@ -708,33 +719,41 @@ def perform_readiness_checks(
Raises
------
IOError
- If there is an issue writing the readiness report to the specified file path, an IOError is raised, indicating
- a problem with file creation or disk access.
+ An IOError is raised if there is an issue with writing the readiness report to the specified file path,
+ indicating a problem with file access or disk space.
+
+ Examples
+ --------
+ Executing readiness checks and saving the report:
+ >>> firewall_instance = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
+ >>> perform_readiness_checks(firewall_instance, 'firewall1', '/path/to/firewall1_readiness.json')
+ # This executes the readiness checks and saves the report to the specified path.
Notes
-----
- - This function is a critical precursor to upgrade operations, ensuring that all necessary conditions are met and
- potential issues are identified and addressed beforehand.
- - The JSON report generated provides a structured and easily parsable record of the firewall's readiness, useful
- for automated workflows and auditing purposes.
+ - The readiness checks are crucial for identifying potential issues that could impact the upgrade process,
+ ensuring a smooth and successful upgrade.
+ - The checks to be executed can be defined in the `settings.yaml` file, allowing for customization based on
+ the environment and operational policies. If the `settings.yaml` file exists and specifies custom checks
+ under `readiness_checks.customize`, those checks will be used; otherwise, a default set of checks is applied.
- Example
- -------
- Performing readiness checks on a firewall and saving the report:
- >>> firewall_instance = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
- >>> perform_readiness_checks(firewall_instance, 'firewall1', '/var/reports/firewall1_readiness.json')
- # This will execute the readiness checks and save the output in the specified JSON file.
"""
- logging.debug(
- f"{get_emoji('start')} {hostname}: Performing readiness checks of target firewall..."
- )
-
- readiness_check = run_assurance(
- firewall,
- hostname,
- operation_type="readiness_check",
- actions=[
+ # Determine readiness checks to perform based on settings.yaml
+ if settings_file_path.exists() and settings_file.get(
+ "readiness_checks.customize", False
+ ):
+ # Extract checks where value is True
+ selected_checks = [
+ check
+ for check, enabled in settings_file.get(
+ "readiness_checks.checks", {}
+ ).items()
+ if enabled
+ ]
+ else:
+ # Default checks to run if settings.yaml does not exist or customize is False
+ selected_checks = [
"candidate_config",
"content_version",
"expired_licenses",
@@ -744,13 +763,22 @@ def perform_readiness_checks(
"ntp_sync",
"panorama",
"planes_clock_sync",
- ],
+ ]
+
+ logging.debug(
+ f"{get_emoji('start')} {hostname}: Performing readiness checks of target firewall..."
+ )
+
+ readiness_check = run_assurance(
+ firewall,
+ hostname,
+ operation_type="readiness_check",
+ actions=selected_checks,
config={},
)
# Check if a readiness check was successfully created
if isinstance(readiness_check, ReadinessCheckReport):
- # Do something with the readiness check report, e.g., log it, save it, etc.
logging.info(f"{get_emoji('success')} {hostname}: Readiness Checks completed")
readiness_check_report_json = readiness_check.model_dump_json(indent=4)
logging.debug(
@@ -778,38 +806,38 @@ def perform_reboot(
ha_details: Optional[dict] = None,
) -> None:
"""
- Initiates a reboot process on the specified firewall or Panorama device and verifies the successful restart with the desired target version.
+ Initiates a reboot of the specified target device to ensure it operates on the desired PAN-OS version.
- This function orchestrates the reboot operation for the given target device, ensuring that it comes back online running the specified target version of the software. In scenarios involving High Availability (HA) configurations, additional validations are performed to ensure that the HA pair is synchronized post-reboot. The function logs each step of the process and handles various states and potential errors that might occur during the reboot.
+ This function triggers a reboot on the target device and monitors it to confirm it restarts with the specified target version. It is particularly crucial in the context of upgrades where a reboot might be necessary to apply new configurations or complete the upgrade process. The function also accounts for High Availability (HA) configurations, ensuring the device and its HA peer remain synchronized post-reboot.
Parameters
----------
target_device : Union[Firewall, Panorama]
- The Firewall or Panorama device object that is to be rebooted. This object must be initialized with the necessary connection parameters.
+ The device (Firewall or Panorama) to reboot. Must be initialized with proper credentials and connection details.
hostname : str
- The hostname or IP address of the target device, used primarily for logging purposes to provide contextual information within the log output.
+ Hostname or IP address of the target device, utilized for logging and identification throughout the reboot process.
target_version : str
- The software version that the target device should be running after the reboot process completes.
+ The version that the target device should be running post-reboot.
ha_details : Optional[dict], optional
- A dictionary containing the HA configuration details of the target device, if applicable. This information is used to assess and verify HA synchronization status post-reboot. Defaults to None.
+ HA configuration details of the target device, if part of an HA pair. This is used to ensure HA synchronization post-reboot. Defaults to None.
Raises
------
SystemExit
- The script will terminate if the target device fails to reboot into the target version, if there are issues achieving HA synchronization post-reboot, or if other critical errors occur during the reboot process.
+ Exits the script if the device fails to reboot to the target version or if HA synchronization post-reboot cannot be verified.
+
+ Examples
+ --------
+ Rebooting a firewall and verifying its version post-reboot:
+ >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
+ >>> perform_reboot(firewall, 'firewall1', '9.1.0')
+ # Initiates a reboot and ensures the firewall is running version 9.1.0 afterwards.
Notes
-----
- - The function actively monitors the reboot sequence and performs version verification once the device is back online.
- - In the case of HA configurations, it ensures that the device and its HA peer are in a synchronized state post-reboot.
- - The process is designed to abort if the device does not reboot to the target version or achieve synchronization within a predefined timeout period, typically 30 minutes.
-
- Example
- -------
- Triggering a reboot on a device and verifying its version post-reboot:
- >>> device = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
- >>> perform_reboot(device, 'device123', '10.0.1')
- # This will initiate a reboot on the device 'device123' and ensure it starts up with version '10.0.1'.
+ - The reboot process involves sending a reboot command to the device and then repeatedly checking its availability and version.
+ - The function implements a retry mechanism with a fixed number of attempts and delay between them to accommodate the device's reboot time.
+ - For devices in an HA setup, additional checks are performed to ensure the HA pair's synchronization state is maintained post-reboot.
"""
rebooted = False
@@ -876,36 +904,66 @@ def perform_snapshot(
file_path: str,
) -> None:
"""
- Captures a comprehensive snapshot of the current network state from a specified firewall and saves it to a JSON file.
+ Captures and saves a comprehensive snapshot of the current network state of a specified firewall to a JSON file.
- This function initiates a series of network state information retrievals from the firewall, including but not limited to ARP tables, content versions, IPsec tunnel statuses, licenses, network interfaces, routing tables, and session statistics. The information gathered is serialized into a JSON format and stored in the specified file location. The function provides logging feedback throughout the process to indicate the start, successful completion, or failure of the snapshot operation.
+ This function gathers detailed network state information from the firewall, such as ARP tables, content versions,
+ IPsec tunnel statuses, license information, network interfaces, routing tables, and session statistics. The
+ collected data is serialized into JSON format and saved to the provided file path. This snapshot serves as a
+ valuable diagnostic tool for assessing the firewall's state before and after significant events like upgrades
+ or configuration changes.
Parameters
----------
firewall : Firewall
- The instance of the Firewall class from which network state information will be gathered. The firewall instance must be initialized with appropriate credentials and connection details.
+ An instance of the Firewall class, representing the device from which the network state information is
+ collected. This object must be initialized with the necessary authentication details and connection parameters.
hostname : str
- The identifier for the firewall, used for logging purposes to provide context during the operation.
+ The hostname or IP address of the firewall, utilized for logging and identification purposes throughout
+ the snapshot process.
file_path : str
- The file system path where the resulting JSON file containing the snapshot will be written. The function will verify or create the necessary directory structure for this path.
+ The filesystem path where the snapshot JSON file will be saved. The function ensures the existence of
+ the target directory, creating it if necessary.
Raises
------
IOError
- If there's an error in writing the snapshot data to the file system, an IOError will be raised indicating the problem.
+ Raised if an error occurs while writing the snapshot data to the filesystem, indicating issues with file
+ creation or disk access.
- Notes
- -----
- - The snapshot includes critical diagnostic information useful for troubleshooting and verifying the operational state before and after significant changes or upgrades.
- - The snapshot operation is non-disruptive and can be performed during normal firewall operation without impacting traffic.
-
- Example
+ Examples
--------
- Taking a network state snapshot of a firewall:
+ Taking and saving a network state snapshot of a firewall:
>>> firewall_instance = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
>>> perform_snapshot(firewall_instance, 'fw-hostname', '/backups/fw-snapshot.json')
- # This will collect the network state from 'fw-hostname' and save it to '/backups/fw-snapshot.json'.
- """
+ # Gathers and saves the network state of 'fw-hostname' to '/backups/fw-snapshot.json'.
+
+ Notes
+ -----
+ - The function is designed to be non-disruptive and can be executed during normal firewall operations without
+ affecting network traffic.
+ - The selection of information to include in the snapshot can be customized via a `settings.yaml` file, allowing
+ administrators to tailor the snapshot content to specific requirements.
+ """
+
+ # Determine snapshot actions to perform based on settings.yaml
+ if settings_file_path.exists() and settings_file.get("snapshots.customize", False):
+ # Extract state actions where value is True
+ selected_actions = [
+ action
+ for action, enabled in settings_file.get("snapshots.state", {}).items()
+ if enabled
+ ]
+ else:
+ # Default actions to take if settings.yaml does not exist or customize is False
+ selected_actions = [
+ "arp_table",
+ "content_version",
+ "ip_sec_tunnels",
+ "license",
+ "nics",
+ "routes",
+ "session_stats",
+ ]
logging.info(
f"{get_emoji('start')} {hostname}: Performing snapshot of network state information..."
@@ -916,15 +974,7 @@ def perform_snapshot(
firewall,
hostname,
operation_type="state_snapshot",
- actions=[
- "arp_table",
- "content_version",
- "ip_sec_tunnels",
- "license",
- "nics",
- "routes",
- "session_stats",
- ],
+ actions=selected_actions,
config={},
)
@@ -955,47 +1005,50 @@ def perform_upgrade(
hostname: str,
target_version: str,
ha_details: Optional[dict] = None,
- max_retries: int = 3,
- retry_interval: int = 60,
) -> None:
"""
- Conducts an upgrade of the specified target device to the desired PAN-OS version, with considerations for HA configurations and error resilience through retry mechanisms.
+ Initiates an upgrade of a specified target device to a desired PAN-OS version, with optional consideration for HA configurations.
- This function initiates the upgrade process for the target device, ensuring the device transitions to the specified PAN-OS version. The procedure accommodates High Availability (HA) setups by utilizing provided HA details, if available. To counter transient errors such as software manager busy states, the function employs a retry mechanism with customizable parameters. Progress and critical events throughout the upgrade process are logged for monitoring and troubleshooting purposes. The script will exit if it encounters irrecoverable errors or depletes the allotted retry attempts.
+ This function triggers the upgrade process for a given target device to the specified PAN-OS version. It accounts for potential HA configurations by utilizing provided HA details. The process incorporates retry mechanisms to handle transient errors, such as when the software manager is busy. Detailed logging is provided throughout the process for monitoring and troubleshooting. The function terminates the script if it encounters critical errors or exhausts the allowed number of retry attempts.
Parameters
----------
target_device : Union[Firewall, Panorama]
- An instance of the Firewall or Panorama class representing the device to upgrade.
+ The device (Firewall or Panorama) to be upgraded. This object must be initialized with the necessary credentials and connection details.
hostname : str
- Identifier for the target device, used for logging and contextual information during the upgrade process.
+ The hostname or IP address of the target device, used for identification and logging purposes.
target_version : str
- The version string to which the target device will be upgraded, e.g., '10.1.0'.
+ The PAN-OS version to which the target device is to be upgraded, formatted as a string (e.g., '10.1.0').
ha_details : Optional[dict], optional
- A dictionary containing details about the target device's HA configuration, if applicable. This information is used to tailor the upgrade process to HA environments.
- max_retries : int, optional
- Maximum number of retry attempts allowed for transient errors during the upgrade process. Defaults to 3.
- retry_interval : int, optional
- Time in seconds to wait between consecutive retry attempts. Defaults to 60 seconds.
+ Optional dictionary containing HA configuration details of the target device, if applicable. This information is leveraged to tailor the upgrade process to HA environments.
Raises
------
SystemExit
- Terminates the script execution if the upgrade process fails or encounters critical errors.
+ If the upgrade process fails or encounters an unrecoverable error, resulting in script termination.
Notes
-----
- - Incorporates error handling strategies to address common issues like 'software manager is currently in use'.
- - The upgrade process is designed to be resilient, with parameters to control retry behavior in the face of operational errors.
+ - Retry mechanisms are employed to mitigate transient operational errors, enhancing the resilience of the upgrade process.
+ - The function supports customization of retry parameters through a `settings.yaml` file, allowing for adjustments to the retry behavior based on specific operational requirements.
Example
-------
- Initiating an upgrade process with retry logic:
- >>> target_device_instance = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password')
- >>> perform_upgrade(target_device_instance, 'firewall1', '10.2.0', max_retries=2, retry_interval=30)
- # Initiates the upgrade of 'firewall1' to PAN-OS version 10.2.0, with up to 2 retry attempts in case of errors.
+ Executing the upgrade process with a specified target version:
+ >>> target_device = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
+ >>> perform_upgrade(target_device, 'firewall1', '10.2.0')
+ # Triggers the upgrade of 'firewall1' to PAN-OS version '10.2.0'.
"""
+ # Check if settings.yaml exists and use its values for max_retries and retry_interval
+ if settings_file_path.exists():
+ max_retries = settings_file.get("reboot.max_tries", 30)
+ retry_interval = settings_file.get("reboot.retry_interval", 60)
+ else:
+ # Default values if settings.yaml does not exist or does not specify these settings
+ max_retries = 30
+ retry_interval = 60
+
logging.info(
f"{get_emoji('start')} {hostname}: Performing upgrade to version {target_version}..."
)
@@ -1052,44 +1105,44 @@ def run_assurance(
config: Dict[str, Union[str, int, float, bool]],
) -> Union[SnapshotReport, ReadinessCheckReport, None]:
"""
- Conducts specified operational checks or captures snapshots on the firewall, based on the operation type.
+ Executes specified operational checks or captures state snapshots on a firewall based on the given operation type.
- This function is versatile, supporting various operations like readiness checks and state snapshots on the specified firewall. It executes a set of actions based on the 'operation_type' parameter, using 'actions' and 'config' to tailor the operation. The function returns a report object relevant to the operation or None in case of failure. It ensures the actions are valid for the operation type and handles exceptions gracefully, logging errors and exiting the script for critical issues.
+ Depending on the 'operation_type', this function conducts various operations like readiness checks or state snapshots on the provided firewall. It processes a list of 'actions' according to the operation type and uses 'config' for additional parameters. The function returns a relevant report object upon success or None if the operation fails. It validates the actions against the operation type and provides detailed logging, including error handling.
Parameters
----------
firewall : Firewall
- The firewall instance on which the assurance tasks will be executed.
+ The firewall instance for which the assurance operations will be executed.
hostname : str
The hostname or IP address of the firewall, used for logging purposes.
operation_type : str
- The type of assurance operation to perform, such as 'readiness_check' or 'state_snapshot'.
+ Specifies the type of operation to perform, such as 'readiness_check' or 'state_snapshot'.
actions : List[str]
- A list of specific actions to execute as part of the operation, such as checking licenses or capturing ARP tables.
+ Defines the specific actions to execute within the operation, like checking for pending changes or capturing ARP tables.
config : Dict[str, Union[str, int, float, bool]]
- Additional configuration options for the actions, specifying details like thresholds or specific parameters.
+ Provides additional configuration options for the actions, detailing parameters such as thresholds or specific checks.
Returns
-------
Union[SnapshotReport, ReadinessCheckReport, None]
- A report object corresponding to the operation type, or None if the operation fails or is invalid.
+ Depending on the operation, returns a SnapshotReport or ReadinessCheckReport object, or None if the operation encounters an error or is invalid.
Raises
------
SystemExit
- If an invalid action for the specified operation is encountered or if an exception occurs during execution.
+ Exits the script if an invalid action is specified for the given operation type or if an error occurs during the operation execution.
Notes
-----
- - The function is designed to be flexible, accommodating additional operation types and actions as needed.
- - It is critical for maintaining the operational integrity and readiness of the firewall for upgrades or routine checks.
+ - This function is designed to support extensibility for new operation types and actions as needed.
+ - It plays a crucial role in maintaining the firewall's operational integrity and readiness, especially before upgrade activities.
Example
-------
- Performing a readiness check operation on a firewall:
+ Executing readiness checks on a firewall:
>>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password')
- >>> result = run_assurance(firewall, 'firewall1', 'readiness_check', ['config_status', 'license_status'], {})
- # The result is either a ReadinessCheckReport object or None if the operation fails.
+ >>> result = run_assurance(firewall, 'firewall1', 'readiness_check', ['candidate_config', 'license_status'], {})
+ # Result is a ReadinessCheckReport object or None if the operation fails.
"""
# setup Firewall client
@@ -1185,46 +1238,43 @@ def software_download(
ha_details: dict,
) -> bool:
"""
- Initiates the download of a specific software version on the target device and monitors its progress.
+ Initiates and monitors the download of a specified software version on a target device.
- This function triggers the download of the specified software version on the target device, which can be either a Firewall or a Panorama appliance. It checks if the desired version is already available on the device. If not, it starts the download and continuously monitors the progress, providing updates through logging. The function returns True if the download completes successfully. If the download fails or encounters errors, the function logs the issues and returns False. In the event of an exception, the script is terminated to prevent further issues.
+ This function checks if the desired software version is already present on the target device. If not, it starts the download process and monitors its progress, providing real-time feedback through logging. The function is designed to handle various download states and errors, ensuring robust error handling and logging for diagnostics. It supports devices in High Availability (HA) configurations, taking HA details into account during the process.
Parameters
----------
target_device : Union[Firewall, Panorama]
- The Firewall or Panorama instance where the software download is to be initiated.
+ The Firewall or Panorama device on which the software version is to be downloaded.
hostname : str
- The hostname or IP address of the target device, used for identification in logs.
+ The hostname or IP address of the target device, utilized for logging purposes.
target_version : str
- The software version to be downloaded to the target device.
+ The PAN-OS version intended to be downloaded onto the target device.
ha_details : dict
- A dictionary containing High Availability (HA) details of the target device, if applicable.
+ High Availability (HA) details of the target device, relevant for devices in an HA setup.
Returns
-------
bool
- True if the download completes successfully, False otherwise.
+ Indicates whether the download was successful (True) or not (False).
Raises
------
SystemExit
- Terminates the script if a critical error occurs during the download process or if an exception is raised.
+ Exits the script with an error message if the download process encounters a critical error.
Example
-------
- Initiating a software download on a firewall:
- >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password')
- >>> successful = software_download(firewall, 'firewall1', '9.1.3', ha_details={})
- >>> if successful:
- ... print("Download successful")
- ... else:
- ... print("Download failed")
+ Downloading software on a firewall device:
+ >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
+ >>> success = software_download(firewall, 'firewall1', '10.0.0', ha_details={})
+ >>> print("Download Successful" if success else "Download Failed")
Notes
-----
- - The function first checks if the desired version is already downloaded, skipping the download process if so.
- - It handles potential errors gracefully and logs all significant events for troubleshooting.
- - The download process is monitored, with periodic checks every 30 seconds to update on the progress.
+ - The function performs an initial check to avoid redundant downloads if the target version is already available on the device.
+ - In the case of HA configurations, the function ensures that HA synchronization considerations are taken into account during the download process.
+ - The download process is monitored continuously, with updates logged every 30 seconds to provide visibility into the progress.
"""
if target_device.software.versions[target_version]["downloaded"]:
@@ -1304,46 +1354,43 @@ def software_update_check(
ha_details: dict,
) -> bool:
"""
- Checks the availability and readiness of a specified software version for installation on a target device.
+ Verifies if a specified software version is available for upgrade on the target device, considering HA setup.
- This function assesses whether the specified version is a viable upgrade for the target device, considering both its current software state and High-Availability (HA) setup. It first refreshes the device's system info to ensure up-to-date data. Then, it validates whether the specified version is an upgrade using a version comparison function. If the target version is available in the device's software repository and its base image is already downloaded, the function returns True, indicating readiness for upgrade. Otherwise, it returns False, logging the reason for unavailability or incompatibility.
+ This function determines the feasibility of upgrading a target device to a specified software version. It performs a series of checks, including current software version assessment, target version availability in the device's software repository, and the presence of the required base image for the upgrade. The function accounts for High Availability (HA) setups by evaluating the upgrade compatibility within the HA context. Detailed logging provides insight into each step of the verification process.
Parameters
----------
target_device : Union[Firewall, Panorama]
- The Firewall or Panorama instance to check for software update availability.
+ The Firewall or Panorama device to check for the specified software version's availability.
hostname : str
- The hostname or IP address of the target device, used for identification in logs and output.
+ The hostname or IP address of the target device for logging purposes.
version : str
- The target software version to check for availability on the target device.
+ The target software version to check for availability and readiness for upgrade.
ha_details : dict
- A dictionary containing details about the target device's HA configuration, if applicable.
+ Details of the HA setup of the target device, if applicable.
Returns
-------
bool
- True if the target version is available for installation and is a valid upgrade; False otherwise.
+ True if the specified software version is available and constitutes a valid upgrade; False otherwise.
Raises
------
SystemExit
- Exits the script if the target version represents a downgrade or if it's not suitable for an upgrade based on the device's current software state.
+ Exits the script if the specified version represents a downgrade or is not appropriate for upgrade due to other criteria.
Example
-------
- Verifying the availability of a software version for upgrade:
- >>> target_device = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password')
- >>> is_available = software_update_check(target_device, 'fw01', '9.1.3', {})
- >>> if is_available:
- ... print("Version 9.1.3 is available for upgrade.")
- ... else:
- ... print("Version 9.1.3 is not available for upgrade.")
+ Checking software version availability for upgrade:
+ >>> device = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
+ >>> available = software_update_check(device, 'device123', '10.1.0', ha_details={})
+ >>> print("Upgrade Available" if available else "Upgrade Not Available")
Notes
-----
- - Ensures the specified version is an upgrade, not a downgrade.
- - Verifies the presence of the required base image for the target version, crucial for successful installation.
- - HA considerations are taken into account, particularly ensuring HA pair compatibility if applicable.
+ - The function ensures that the target version is not a downgrade compared to the current version on the device.
+ - It checks the device's software repository for the target version and verifies the presence of the required base image.
+ - In HA configurations, the function assesses upgrade viability while considering HA synchronization and compatibility requirements.
"""
# parse version
@@ -1398,39 +1445,38 @@ def suspend_ha_active(
hostname: str,
) -> bool:
"""
- Temporarily deactivates the High Availability (HA) functionality of an active device in an HA pair.
+ Suspends High Availability (HA) functionality on an active device within an HA pair.
- In an HA configuration, temporarily suspending the HA functionality on the active device can be necessary for maintenance or upgrade procedures. This function sends a command to the target device to suspend its HA operations, effectively making the device passive and allowing its HA peer to take over as the active device. The function logs the outcome and returns a boolean value indicating the success or failure of the operation.
+ This function is used to temporarily disable HA functionality on an active device, facilitating maintenance or upgrade activities by preventing failover events during the process. It issues an operational command to the target device to suspend HA, effectively transitioning the active device to a non-participative state in HA operations. The function verifies the operation's success through the device's response and logs the outcome.
Parameters
----------
target_device : Union[Firewall, Panorama]
- The device instance (Firewall or Panorama) that is currently active in an HA pair and needs its HA functionality suspended.
+ The active device in an HA pair where HA functionality needs to be suspended. It could be either a Firewall or a Panorama appliance.
hostname : str
- The network hostname or IP address of the target device, used for identification and logging purposes.
+ The hostname or IP address of the target device, utilized for logging and identification purposes.
Returns
-------
bool
- Returns True if the HA suspension command is successfully executed, and False if the operation fails or an error occurs.
+ True if the HA suspension command is successfully executed on the target device, indicating that HA functionality has been temporarily disabled. False if the operation encounters an error or fails.
Raises
------
Exception
- An exception is logged and False is returned if an error occurs during the command execution.
+ Raises a generic exception and logs the error if the operational command fails or encounters an issue during execution.
Example
-------
- Suspending HA on an active device:
- >>> target_device = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
- >>> suspend_success = suspend_ha_active(target_device, 'fw01.example.com')
- >>> print(suspend_success)
- True # This would indicate that the HA suspension was successful.
+ Suspending HA on the active device of an HA pair:
+ >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
+ >>> suspension_result = suspend_ha_active(firewall, 'firewall-active.example.com')
+ >>> print("HA suspension successful" if suspension_result else "HA suspension failed")
Notes
-----
- - Suspending HA on an active device is a significant operation that can affect network traffic and redundancy mechanisms.
- - Ensure that the implications of this action are fully understood and that it is coordinated within the context of network operations and maintenance schedules.
+ - This operation is critical in HA environments, especially when performing system upgrades or maintenance that requires preventing automatic failover.
+ - It's important to coordinate this action with network management policies and potentially with the suspension of the counterpart HA device to manage network redundancy effectively.
"""
try:
@@ -1460,39 +1506,38 @@ def suspend_ha_passive(
hostname: str,
) -> bool:
"""
- Temporarily deactivates the High Availability (HA) functionality of a passive device in an HA pair.
+ Suspends High Availability (HA) functionality on a passive device within an HA pair.
- This function is designed to suspend the HA functionality on a device designated as passive within an HA configuration. The suspension is particularly useful during maintenance or upgrade processes to prevent the passive device from becoming active. The process involves issuing a specific command to the device, with the function logging the operation's outcome. A successful operation returns True, indicating the HA functionality has been suspended, while a failure results in False.
+ This function is utilized to temporarily disable HA functionality on a device designated as passive in an HA configuration. The suspension is critical during certain operations, such as system upgrades or maintenance, to prevent the passive device from taking over as the active device. It sends a command to the target device to suspend HA activities and evaluates the command's success based on the device's response, logging the outcome accordingly.
Parameters
----------
target_device : Union[Firewall, Panorama]
- The device instance (Firewall or Panorama) representing the passive target in an HA configuration.
+ The device (Firewall or Panorama) in an HA pair that is currently in a passive state and requires HA suspension.
hostname : str
- The network hostname or IP address of the target device, serving as an identifier for logging purposes.
+ The hostname or IP address of the target device, used for logging and identification purposes.
Returns
-------
bool
- True if the HA suspension command is executed successfully, indicating the passive device's HA functionality has been temporarily deactivated. False indicates a failure in the suspension process.
+ Returns True if the HA suspension command is successfully executed on the passive device, indicating the temporary deactivation of its HA functionality. Returns False if the operation encounters an error or fails.
Raises
------
Exception
- An error is logged, and False is returned if an exception occurs during the execution of the HA suspension command.
+ Logs an error and returns False if an exception occurs during the execution of the HA suspension command.
Example
-------
- Suspending HA functionality on a passive device:
- >>> target_device = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
- >>> suspension_success = suspend_ha_passive(target_device, 'fw02.example.com')
- >>> print(suspension_success)
- True # This would indicate that the HA suspension was successfully executed.
+ Suspending HA on the passive device of an HA pair:
+ >>> panorama = Panorama(hostname='panorama.example.com', api_username='admin', api_password='admin')
+ >>> suspension_result = suspend_ha_passive(panorama, 'panorama-passive.example.com')
+ >>> print("HA suspension successful" if suspension_result else "HA suspension failed")
Notes
-----
- - Suspending HA on a passive device is an important operation that might be required to maintain network stability during critical maintenance or upgrade tasks.
- - Care should be taken to ensure the operation's impact on network redundancy and traffic handling is fully understood and planned for.
+ - Temporarily suspending HA on a passive device is a significant operation that should be undertaken with caution, especially in terms of its impact on network redundancy and traffic flow.
+ - It is recommended to perform this operation in coordination with overall network management and maintenance plans.
"""
try:
@@ -1523,44 +1568,44 @@ def upgrade_firewall(
dry_run: bool,
) -> None:
"""
- Orchestrates the comprehensive upgrade process for a Palo Alto Networks firewall to a specified version.
+ Orchestrates the upgrade process for a Palo Alto Networks firewall to a specified version, considering HA configurations and supporting a dry run mode.
- This function encapsulates the entire sequence of operations necessary to upgrade a firewall, starting from pre-upgrade readiness checks to the final reboot into the new version. It is designed to accommodate firewalls in various configurations, including standalone units and those in High Availability (HA) setups. The function supports a 'dry run' mode that simulates the upgrade process without applying any changes, providing a safe way to validate the upgrade plan.
+ This function manages the upgrade of a firewall, including pre-upgrade checks, software download, and system reboot to the target version. It handles firewalls in both standalone and High Availability (HA) configurations. The dry run mode allows for testing the upgrade process without making any changes, ideal for planning and validation.
Parameters
----------
firewall : Firewall
- The instance of the firewall to be upgraded.
+ The firewall instance to be upgraded, initialized with connection details.
target_version : str
- The desired PAN-OS version to upgrade the firewall to.
+ The target PAN-OS version to upgrade the firewall to (e.g., '10.1.0').
dry_run : bool
- Specifies whether to simulate the upgrade process (True) or to execute the upgrade (False).
-
- Workflow
- --------
- 1. System Information Refresh: Ensures up-to-date information about the firewall's current state.
- 2. HA Status Check: Determines the firewall's role in an HA configuration and handles HA-specific logic.
- 3. Readiness Assessment: Validates the firewall's readiness for the upgrade through a series of checks.
- 4. Software Preparation: Downloads the required PAN-OS version and associated content updates.
- 5. Pre-upgrade Steps: Includes configuration backup and network state snapshots.
- 6. Upgrade Execution: Applies the upgrade and reboots the firewall, completing the transition to the target version.
+ If True, the function simulates the upgrade steps without performing the actual upgrade.
Raises
------
SystemExit
- Terminates the script if a critical failure is encountered at any stage of the upgrade process, ensuring safety and consistency.
+ Exits the script if the upgrade process encounters a critical failure at any stage.
Example
-------
- Initiating an upgrade on a firewall:
- >>> firewall_instance = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
- >>> upgrade_firewall(firewall_instance, '10.0.1', dry_run=False)
- # This command will start the upgrade process to PAN-OS version 10.0.1.
+ Executing an upgrade on a firewall:
+ >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='admin')
+ >>> upgrade_firewall(firewall, '10.1.0', dry_run=False)
+ # Initiates the upgrade to version 10.1.0, with actual changes applied.
Notes
-----
- - The dry run mode is particularly useful for verifying upgrade steps and ensuring operational readiness without risking system stability.
- - The function integrates detailed logging throughout the upgrade process, providing transparency and aiding in troubleshooting.
+ - It is recommended to perform a dry run before executing the actual upgrade to ensure operational readiness.
+ - The function provides detailed logging at each step for monitoring the upgrade progress and diagnosing issues.
+
+ Workflow
+ --------
+ 1. Validates the current system state and HA configuration.
+ 2. Performs readiness checks to ensure the firewall is prepared for upgrade.
+ 3. Downloads the necessary software version if not already available.
+ 4. Takes pre-upgrade snapshots and backups for rollback purposes.
+ 5. Executes the upgrade and reboots the firewall to the target version.
+ 6. Verifies post-upgrade status and functionality.
"""
# Refresh system information to ensure we have the latest data
@@ -1898,46 +1943,46 @@ def check_readiness_and_log(
test_info: dict,
) -> None:
"""
- Evaluates and logs the results of firewall upgrade readiness tests, highlighting any issues that may impede the upgrade.
+ Evaluates and logs the outcomes of specific readiness tests for a firewall, crucial for assessing upgrade feasibility.
- This function analyzes the outcome of specified readiness tests conducted on a firewall, in preparation for an upgrade, and logs the results. It categorizes the test outcomes based on their severity, ranging from informational to critical. For tests deemed critical ('exit_on_failure' set to True), the script will terminate upon failure to prevent proceeding with an upgrade that is likely to encounter significant issues.
+ This function inspects the results of predefined readiness tests that have been executed on a firewall to determine its preparedness for an upgrade. It logs the outcomes of these tests, emphasizing the severity of any failures and their potential impact on the upgrade process. For tests marked as critical, a failure will lead to the termination of the script to avert a problematic upgrade.
Parameters
----------
result : dict
- A dictionary containing the results of readiness tests, where each key corresponds to a test name, and its value is another dictionary detailing the test's outcome ('state') and a descriptive 'reason'.
+ The outcomes of the readiness tests, structured as a dictionary where each key is a test name and its value is a dictionary containing the test's 'state' (True for pass, False for fail) and a 'reason' for the test outcome.
hostname : str
- The identifier of the firewall undergoing the readiness check, primarily used for logging purposes.
+ The identifier for the firewall being evaluated, used to contextualize log entries.
test_name : str
- The specific readiness test being evaluated, corresponding to a key in the 'result' dictionary.
+ The name of the specific test being assessed, which should correspond to a key in the 'result' dictionary.
test_info : dict
- Metadata about the test, including a human-readable 'description', the 'log_level' indicating the severity of a failed test, and a boolean 'exit_on_failure' flag indicating whether a test failure should halt the upgrade process.
+ Detailed information about the test, including a descriptive 'description', the 'log_level' for logging the outcome, and an 'exit_on_failure' boolean that dictates whether a failed test should halt further script execution.
Workflow
--------
- 1. Retrieve the result for the specified test from the 'result' dictionary.
- 2. Construct a log message incorporating the test's description and outcome reason.
- 3. Log the message at an appropriate severity level based on the test outcome and defined 'log_level'.
- 4. If a critical test fails ('exit_on_failure' is True), log an error message and terminate the script to prevent a potentially problematic upgrade.
+ 1. Extract the outcome of the specified test from the 'result' dictionary.
+ 2. Formulate a log entry combining the test's description and the reason for its outcome.
+ 3. Log the entry, adjusting the log level according to the test's importance and the nature of its outcome.
+ 4. If a critical test fails (as indicated by 'exit_on_failure' being True), log a critical error and stop the script execution.
Raises
------
SystemExit
- Triggered when a critical test fails, indicating an upgrade-blocking issue, to halt script execution.
+ If a test deemed critical to the upgrade's success fails, the script will terminate to prevent potentially adverse actions.
Example
-------
- Logging the result of a connectivity test:
- >>> result = {'connectivity_check': {'state': False, 'reason': 'No response from server'}}
- >>> test_name = 'connectivity_check'
- >>> test_info = {'description': 'Connectivity Check', 'log_level': 'error', 'exit_on_failure': True}
- >>> check_readiness_and_log(result, 'firewall1', test_name, test_info)
- # Outputs an error log and terminates the script due to the failed critical connectivity check.
+ Assessing and logging a hypothetical connectivity test result:
+ >>> result = {'connectivity_test': {'state': False, 'reason': 'Network unreachable'}}
+ >>> test_name = 'connectivity_test'
+ >>> test_info = {'description': 'Network Connectivity Test', 'log_level': 'error', 'exit_on_failure': True}
+ >>> check_readiness_and_log(result, 'fw01', test_name, test_info)
+ # This would log an error regarding the failed connectivity test and terminate the script due to its critical nature.
Notes
-----
- - The function is integral to the pre-upgrade validation phase, ensuring the firewall's readiness for a successful upgrade.
- - Emphasizes clear, actionable logging to facilitate issue resolution and upgrade decision-making.
+ - Integral to the pre-upgrade phase, ensuring only firewalls meeting all readiness criteria proceed to upgrade.
+ - The function's logging strategy is designed to provide clarity on each test's outcome, facilitating informed decision-making regarding the upgrade.
"""
test_result = result.get(
@@ -1971,38 +2016,39 @@ def compare_versions(
version2: str,
) -> str:
"""
- Determines the relative newer, older, or equality status between two software version strings.
+ Compares two software version strings to determine their relative ordering.
- This utility function is essential in upgrade workflows, enabling a clear comparison between two version strings to ascertain which is newer, older, or if both versions are identical. It adeptly handles versions in the format of major.minor.maintenance, with optional hotfix levels, facilitating nuanced comparisons crucial in software version management and decision-making processes related to upgrades or compatibility checks.
+ This function is a critical component in upgrade and compatibility workflows, allowing for the comparison of two software version strings. It parses and evaluates the major, minor, maintenance, and optional hotfix components of each version string to ascertain their relative ordering. This utility facilitates decision-making in scenarios such as software upgrades, patch applications, and compatibility checks by determining if one version is newer, older, or equal to another.
Parameters
----------
version1 : str
- A version string to be compared, adhering to a 'major.minor.maintenance' or 'major.minor.maintenance-hotfix' format.
+ The first version string in the comparison, following the 'major.minor.maintenance' or 'major.minor.maintenance-hotfix' format.
version2 : str
- Another version string for comparison, in a similar format as 'version1'.
+ The second version string in the comparison, using a similar format as 'version1'.
Returns
-------
str
- A string representing the comparison outcome: 'older' if 'version1' precedes 'version2', 'newer' if 'version1' succeeds 'version2', or 'equal' if both versions are the same.
+ The result of the comparison: 'older' if 'version1' is older than 'version2', 'newer' if 'version1' is newer than 'version2', or 'equal' if both versions are identical.
Examples
--------
- Comparing two version strings:
- >>> compare_versions('10.0.1', '10.0.2')
- 'older' # '10.0.1' is older than '10.0.2'
+ Comparing two version strings to determine their relative ordering:
+ >>> compare_versions('9.0.0', '9.1.0')
+ 'older' # '9.0.0' is older than '9.1.0'
- >>> compare_versions('10.1.0-h3', '10.1.0')
- 'newer' # '10.1.0-h3' is considered newer than '10.1.0' due to the hotfix
+ >>> compare_versions('10.0.1-h2', '10.0.1')
+ 'newer' # '10.0.1-h2' is considered newer than '10.0.1' due to the hotfix
- >>> compare_versions('9.1.3-h3', '9.1.3-h3')
- 'equal' # Both versions are identical
+ >>> compare_versions('8.1.3', '8.1.3')
+ 'equal' # Both versions are the same
Notes
-----
- - The function employs a systematic approach to parse and compare version components, ensuring accuracy even with complex version strings.
- - It is particularly useful in environments where precise version control and compatibility assessments are pivotal.
+ - The function provides a precise mechanism for software version comparison, essential for managing software lifecycles and ensuring system integrity.
+ - It is designed to handle standard versioning schemes, making it adaptable to various software and systems.
+ - While primarily used for version comparison, it also serves as a foundational utility in broader system management and operational scripts.
"""
parsed_version1 = parse_version(version1)
@@ -2021,40 +2067,56 @@ def configure_logging(
encoding: str = "utf-8",
) -> None:
"""
- Sets up the logging configuration for the application with specified verbosity and file encoding.
+ Initializes the application's logging system with specified verbosity and encoding.
- This function initializes the logging system to include both console and file handlers, facilitating real-time monitoring and persistent logging. It allows for setting the logging verbosity through predefined levels, impacting the granularity of logged information. The rotating file handler ensures log file size management by archiving older entries, thereby maintaining a balance between log detail and file size.
+ This function configures the application's logging framework to capture logs at the specified verbosity level. It sets up both console and file handlers, ensuring that logs are appropriately displayed in the console and stored in files for later analysis. The file handler employs a rotating mechanism to manage log file sizes by archiving old logs and maintaining a manageable current log file size. The encoding parameter allows for log files to be written in the specified character encoding, accommodating internationalization requirements.
Parameters
----------
level : str
- Defines the logging level, influencing the verbosity of the logs. Acceptable values are 'DEBUG', 'INFO', 'WARNING', 'ERROR', and 'CRITICAL'. The function defaults to 'INFO' if an unrecognized level is provided.
+ The logging level to set for the application. Valid options include 'DEBUG', 'INFO', 'WARNING', 'ERROR', and 'CRITICAL'. This parameter controls the verbosity of the log output.
encoding : str, optional
- Specifies the character encoding for the log files, defaulting to 'utf-8'. This ensures compatibility with a wide range of characters and symbols, accommodating diverse logging content.
+ The character encoding for log files. Defaults to 'utf-8', ensuring broad compatibility with various characters and symbols used in log messages.
Raises
------
ValueError
- Triggered if the provided `level` is not a recognized logging level, ensuring the integrity of logging configuration.
+ If the `level` parameter does not correspond to a recognized logging level, ensuring that logs are captured at a valid verbosity.
Examples
--------
- Configuring logging at the DEBUG level with default encoding:
+ Setting up logging with DEBUG level and default UTF-8 encoding:
>>> configure_logging('DEBUG')
- # Sets the logger to DEBUG level, capturing detailed logs for diagnostic purposes.
+ # Configures logging to capture detailed debug information, outputting to both the console and a UTF-8 encoded file.
- Configuring logging at the INFO level with a specific encoding:
+ Setting up logging with INFO level and ISO-8859-1 encoding:
>>> configure_logging('INFO', 'iso-8859-1')
- # Configures the logger to INFO level with ISO-8859-1 encoding, suitable for environments requiring this character set.
+ # Initializes logging to capture informational messages and above, with log files encoded in ISO-8859-1.
Notes
-----
- - The logging setup is designed to provide a balance between real-time insights and historical log preservation, catering to both immediate debugging needs and retrospective analysis.
- - The rotating log files maintain a manageable log size, preventing excessive disk space consumption while preserving essential log history.
+ - The logging configuration is crucial for monitoring application behavior and troubleshooting issues, providing insights into operational status and potential errors.
+ - The rotating file handler ensures that log storage remains efficient over time, preventing unbounded growth of log files and facilitating easier log management and review.
+ - This function supports customization through the `settings.yaml` file, allowing default settings to be overridden based on specific operational needs or preferences.
"""
- logging_level = getattr(logging, level.upper(), None)
+ # Use the provided log_level parameter if given, otherwise fall back to settings file or default
+ log_level = (
+ level.upper() if level else settings_file.get("logging.level", "INFO").upper()
+ )
+
+ # Use the provided log_file_path parameter if given, otherwise fall back to settings file or default
+ log_file_path = settings_file.get("logging.file_path", "logs/upgrade.log")
+
+ # Convert MB to bytes
+ log_max_size = settings_file.get("logging.max_size", 10) * 1024 * 1024
+
+ # Use the provided log_upgrade_log_count parameter if given, otherwise fall back to settings file or default
+ log_upgrade_log_count = settings_file.get("logging.upgrade_log_count", 3)
- # Get the root logger
+ # Set the logging level
+ logging_level = getattr(logging, log_level, logging.INFO)
+
+ # Set up logging
logger = logging.getLogger()
logger.setLevel(logging_level)
@@ -2062,17 +2124,17 @@ def configure_logging(
for handler in logger.handlers[:]:
logger.removeHandler(handler)
- # Create handlers (console and file handler)
+ # Create handlers
console_handler = logging.StreamHandler()
file_handler = RotatingFileHandler(
- "logs/upgrade.log",
- maxBytes=1024 * 1024,
- backupCount=3,
+ log_file_path,
+ maxBytes=log_max_size,
+ backupCount=log_upgrade_log_count,
encoding=encoding,
)
# Create formatters and add them to the handlers
- if level == "debug":
+ if log_level == "DEBUG":
console_format = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
@@ -2099,43 +2161,44 @@ def connect_to_host(
api_password: str,
) -> PanDevice:
"""
- Establishes a connection to a Palo Alto Networks device or Panorama using API credentials.
+ Establishes an API connection to a Palo Alto Networks device, returning a device-specific object.
- This function attempts to connect to the specified Palo Alto Networks device (Firewall or Panorama) using the provided hostname, API username, and password. It auto-detects the type of device based on the successful connection response and returns an object representing the connected device. The returned `PanDevice` object serves as a gateway for further interactions with the device through the API.
+ This function connects to a Palo Alto Networks firewall or Panorama management server using the provided API credentials. It determines the type of device (firewall or Panorama) based on the API response and returns an object representing the connected device. This object can then be used for further API calls to the device. The function includes error handling to gracefully manage connection failures and provides clear error messages to assist with troubleshooting.
Parameters
----------
hostname : str
- The IP address or DNS hostname of the target device or Panorama to connect to.
+ The hostname or IP address of the Palo Alto Networks device or Panorama to connect to.
api_username : str
- The API username for authentication with the target device.
+ The username used for API authentication.
api_password : str
- The API password for authentication with the target device.
+ The password associated with the API username.
Returns
-------
PanDevice
- An object representing the connected Palo Alto Networks device or Panorama, facilitating further API interactions.
+ An object representing the connected Palo Alto Networks device, either a Firewall or Panorama instance.
Raises
------
SystemExit
- Exits the script with an error message if the connection fails due to incorrect credentials, network issues, or other unexpected errors.
+ If the connection cannot be established, the script will log an appropriate error message and exit. This could be due to incorrect credentials, network issues, or an unreachable host.
Examples
--------
- Establishing a connection to a firewall:
- >>> firewall = connect_to_host('192.168.0.1', 'admin', 'password')
- # Returns an instance of the Firewall class upon successful connection.
+ Connecting to a firewall device:
+ >>> device = connect_to_host('192.168.1.1', 'apiuser', 'apipassword')
+ # Returns a Firewall object if the connection is successful.
- Establishing a connection to Panorama:
- >>> panorama = connect_to_host('panorama.example.com', 'admin', 'password')
- # Returns an instance of the Panorama class upon successful connection.
+ Connecting to a Panorama device:
+ >>> panorama = connect_to_host('panorama.company.com', 'apiuser', 'apipassword')
+ # Returns a Panorama object if the connection is successful.
Notes
-----
- - The function provides a unified interface for connecting to different types of Palo Alto Networks devices, abstracting the complexities of device-specific connection details.
- - Adequate error handling is implemented to ensure that the script does not proceed without a successful connection, maintaining operational integrity and preventing subsequent errors.
+ - This function abstracts the connection details and device type determination, simplifying the process of starting interactions with Palo Alto Networks devices.
+ - It is essential to handle connection errors gracefully in scripts to ensure reliability and provide clear feedback for operational troubleshooting.
+ - Default settings for connection parameters, such as timeouts and retries, can be overridden by the `settings.yaml` file if `settings_file_path` is used within the function.
"""
try:
@@ -2162,30 +2225,127 @@ def connect_to_host(
sys.exit(1)
-def ensure_directory_exists(file_path: str) -> None:
+def console_welcome_banner(
+ mode: str,
+ config_path: Optional[Path] = None,
+) -> None:
"""
- Ensures the existence of the directory structure for a specified file path.
+ Displays a welcome banner in the console with mode-specific messages and configuration information.
- This utility function checks if the directory for a given file path exists, and if not, it creates the directory along with any necessary intermediate directories. This is particularly useful to prepare the file system for file operations such as saving or retrieving files, preventing errors related to non-existent directories.
+ This function dynamically generates a welcome banner based on the operational mode selected by the user. It provides contextual information about the chosen mode (e.g., settings, firewall upgrade, panorama upgrade, batch upgrade) and indicates the usage of a custom configuration file if provided. The banner aims to guide users through the initial steps of the tool's usage and set expectations for the subsequent workflow.
Parameters
----------
- file_path : str
- The full file path for which the directory structure needs to be verified or created. The function extracts the directory path component from this parameter.
+ mode : str
+ The operational mode of the tool, which can be 'settings', 'firewall', 'panorama', or 'batch', dictating the content of the welcome message.
+ config_path : Optional[Path], optional
+ The file path to a custom configuration file (settings.yaml) if used, influencing the configuration message. Defaults to None, indicating no custom configuration file is used.
+
+ Workflow
+ --------
+ 1. Determine the welcome and banner messages based on the operational mode.
+ 2. If a custom configuration file is used, include its path in the message; otherwise, note the default configuration usage.
+ 3. Calculate the border length for the banner based on the longest message line.
+ 4. Construct and print the banner with optional ANSI color codes for emphasis.
+
+ Examples
+ --------
+ Displaying the welcome banner for the firewall upgrade mode with a custom configuration:
+ >>> console_welcome_banner('firewall', Path('/path/to/settings.yaml'))
+ # Displays a welcome message specific to firewall upgrades and notes the use of a custom configuration.
+
+ Displaying the welcome banner for the settings mode without a custom configuration:
+ >>> console_welcome_banner('settings')
+ # Displays a welcome message specific to settings configuration, without mentioning a custom configuration file.
Notes
-----
- - The function is designed to be safe for concurrent use; it will not raise an error if the directory already exists.
- - It relies on `os.makedirs` with the `exist_ok` flag set to True, which is efficient for ensuring the existence of complex nested directories.
+ - The function enhances user experience by providing clear, mode-specific guidance at the start of the tool's execution.
+ - The inclusion of configuration file information assists users in understanding the current configuration context, especially when overriding default settings with a `settings.yaml` file.
+ - ANSI color codes are used to enhance readability and draw attention to the banner, but they are designed to degrade gracefully in environments that do not support them.
+ """
- Example
- -------
- Preparing a directory for log files:
- >>> log_file_path = '/logs/system/2024_01_01/event.log'
- >>> ensure_directory_exists(log_file_path)
- # Ensures the '/logs/system/2024_01_01/' directory exists, creating it and any intermediate directories if necessary.
+ # Customize messages based on the mode
+ if mode == "settings":
+ welcome_message = "Welcome to the PAN-OS upgrade settings menu"
+ banner_message = (
+ "You'll be presented with configuration items, press enter for default settings."
+ "\n\nThis will create a `settings.yaml` file in your current working directory."
+ )
+ # No config message for settings mode
+ config_message = ""
+ else:
+ if mode == "firewall":
+ welcome_message = "Welcome to the PAN-OS upgrade tool"
+ banner_message = "You have selected to upgrade a single Firewall appliance."
+ elif mode == "panorama":
+ welcome_message = "Welcome to the PAN-OS upgrade tool"
+ banner_message = "You have selected to upgrade a single Panorama appliance."
+ elif mode == "batch":
+ welcome_message = "Welcome to the PAN-OS upgrade tool"
+ banner_message = "You have selected to perform a batch upgrade of firewalls through Panorama."
+
+ # Configuration file message
+ if config_path:
+ config_message = f"Custom configuration loaded from:\n{config_path}"
+ else:
+ config_message = (
+ "No settings.yaml file was found. Default values will be used.\n"
+ "Create a settings.yaml file with 'pan-os-upgrade settings' command."
+ )
+
+ # Calculate border length based on the longer message
+ border_length = max(
+ len(welcome_message),
+ max(len(line) for line in banner_message.split("\n")),
+ max(len(line) for line in config_message.split("\n")) if config_message else 0,
+ )
+ border = "=" * border_length
+
+ # ANSI escape codes for styling
+ color_start = "\033[1;33m" # Bold Orange
+ color_end = "\033[0m" # Reset
+
+ # Construct and print the banner
+ banner = f"{color_start}{border}\n{welcome_message}\n\n{banner_message}"
+ if config_message: # Only add config_message if it's not empty
+ banner += f"\n\n{config_message}"
+ banner += f"\n{border}{color_end}"
+ typer.echo(banner)
+
+
+def ensure_directory_exists(file_path: str) -> None:
+ """
+ Checks and creates the directory structure for a given file path if it does not exist.
+
+ This utility function is essential for file operations that require writing to a file, ensuring the directory path exists before file creation or modification. It extracts the directory path from the given file path and creates the directory, along with any necessary intermediate directories, if they do not exist. This preemptive check and creation process helps avoid file operation errors due to non-existent directories.
+
+ Parameters
+ ----------
+ file_path : str
+ The file path for which the directory structure needs to be ensured. The function extracts the directory part of this path to check and create the directory.
+
+ Workflow
+ --------
+ 1. Extract the directory path from the provided file path.
+ 2. Check if the directory exists.
+ 3. If the directory does not exist, create it along with any required intermediate directories.
+
+ Raises
+ ------
+ OSError
+ If the directory cannot be created due to permissions or other filesystem errors, an OSError is raised, providing details about the failure.
+
+ Examples
+ --------
+ Ensuring the existence of a directory for log storage:
+ >>> ensure_directory_exists('/var/log/my_app/events.log')
+ # If '/var/log/my_app/' does not exist, it will be created to ensure a valid path for 'events.log'.
- No return value and exceptions are expected under normal operation. If an error occurs due to permissions or filesystem limitations, an OSError may be raised by the underlying `os.makedirs` call.
+ Notes
+ -----
+ - Utilizes `os.makedirs` with `exist_ok=True` to safely create the directory without raising an error if it already exists.
+ - This function is filesystem-agnostic and should work across different operating systems and environments, making it a versatile tool for file path management in Python applications.
"""
directory = os.path.dirname(file_path)
@@ -2195,43 +2355,43 @@ def ensure_directory_exists(file_path: str) -> None:
def filter_string_to_dict(filter_string: str) -> dict:
"""
- Parses a filter string into a dictionary by converting comma-separated key-value pairs.
+ Converts a filter string with comma-separated key-value pairs into a dictionary.
- This utility function takes a string containing key-value pairs separated by commas, with each key and value within a pair delimited by an equal sign ('='). It is designed to facilitate the conversion of query parameters or similar formatted strings into a dictionary for easier manipulation and access within the code. The function ensures graceful handling of edge cases, such as empty strings or strings not adhering to the expected format, by returning an empty dictionary.
+ This function is designed to parse strings that represent filters or parameters in a 'key1=value1,key2=value2' format, turning them into a more accessible Python dictionary. It's particularly useful in scenarios where parameters need to be dynamically extracted from string formats, such as URL query parameters or configuration settings. The function gracefully handles cases where the input string is empty or improperly formatted by returning an empty dictionary.
Parameters
----------
filter_string : str
- The input string containing the key-value pairs, e.g., 'key1=value1,key2=value2'. The format expected is consistent, where each key-value pair is separated by a comma, and the key is separated from its value by an equal sign.
+ A string containing key-value pairs separated by commas, where each key and its corresponding value are delimited by an equal sign ('='). Example: 'key1=value1,key2=value2'.
Returns
-------
dict
- A dictionary with keys and values derived from the `filter_string`. If `filter_string` is empty, malformatted, or if no valid key-value pairs are identified, the function returns an empty dictionary.
+ A dictionary where each key-value pair from the `filter_string` is represented as a dictionary item. If the `filter_string` is empty, malformed, or contains no valid key-value pairs, an empty dictionary is returned.
Examples
--------
- Converting a filter string into a dictionary:
- >>> filter_string_to_dict('status=active,region=us-west')
- {'status': 'active', 'region': 'us-west'}
-
- Handling an improperly formatted string:
- >>> filter_string_to_dict('status:active,region=us-west')
- {}
+ Parsing a well-formed filter string:
+ >>> filter_string_to_dict('type=firewall,location=us-west')
+ {'type': 'firewall', 'location': 'us-west'}
- Handling an empty string:
+ Dealing with an empty filter string:
>>> filter_string_to_dict('')
{}
+ Handling a malformed filter string:
+ >>> filter_string_to_dict('type-firewall,location=us-west')
+ ValueError: Malformed filter string. Expected key-value pairs separated by '='.
+
Notes
-----
- - The function assumes that the input string is properly formatted. Malformed key-value pairs, where the '=' delimiter is missing, result in the omission of those pairs from the output.
- - Duplicate keys will result in the value of the last occurrence of the key being preserved in the output dictionary.
+ - The function expects a well-formed input string. Malformed key-value pairs (e.g., missing '=' delimiter) will lead to a ValueError.
+ - In case of duplicate keys, the value of the last key-value pair in the string will be used in the resulting dictionary.
Raises
------
ValueError
- If the input string contains key-value pairs without an '=' delimiter, a ValueError is raised to indicate the incorrect format.
+ Raised when the input string contains key-value pairs that are not delimited by an '=', indicating a malformed filter string.
"""
result = {}
@@ -2244,44 +2404,44 @@ def filter_string_to_dict(filter_string: str) -> dict:
def flatten_xml_to_dict(element: ET.Element) -> dict:
"""
- Converts an XML ElementTree element into a nested dictionary structure.
+ Transforms an XML ElementTree element into a nested dictionary, preserving structure and content.
- This function traverses an XML ElementTree element, converting it and all its child elements into a nested dictionary. Each element tag becomes a dictionary key, with the element's text content as the value, or a further nested dictionary if the element has child elements. When encountering multiple child elements with the same tag at the same level, these are grouped into a list within the dictionary. This conversion is particularly useful for simplifying the handling of XML data structures within Python, making them more accessible and easier to work with.
+ This function is designed to parse an XML element, along with its children, into a nested dictionary format, where each element's tag becomes a key, and its text content becomes the corresponding value. For elements containing child elements, a new nested dictionary is created. When multiple child elements share the same tag, they are grouped into a list within the parent dictionary. This utility is particularly useful for processing XML data into a more manageable and Pythonic structure.
Parameters
----------
element : ET.Element
- The root XML element to be converted. This element can contain multiple levels of child elements, which will be recursively processed into the nested dictionary.
+ The XML ElementTree element to be converted. This can be the root element or any subelement within an XML tree.
Returns
-------
dict
- The dictionary representation of the XML structure. Keys in the dictionary correspond to XML tags, and values are either the text content of the elements, nested dictionaries for elements with children, or lists of dictionaries for repeated elements with the same tag.
+ A nested dictionary representation of the input XML element. The dictionary structure mirrors the XML hierarchy, with tags as keys and text content, further nested dictionaries, or lists of dictionaries as values.
Examples
--------
- Converting an XML element to a dictionary:
- >>> xml_string = '- Value1
- Value2
'
+ Converting a simple XML element with no children:
+ >>> xml_string = 'active'
>>> element = ET.fromstring(xml_string)
>>> flatten_xml_to_dict(element)
- {'item': [{'id': '1', '_text': 'Value1'}, {'id': '2', '_text': 'Value2'}]}
+ {'status': 'active'}
- Handling nested XML structures:
- >>> xml_string = ''
+ Converting a complex XML element with nested children:
+ >>> xml_string = '- Value1
- Value2
'
>>> element = ET.fromstring(xml_string)
>>> flatten_xml_to_dict(element)
- {'section': {'name': 'Settings', 'value': 'Enabled'}}
+ {'config': {'item': [{'key': '1', '_text': 'Value1'}, {'key': '2', '_text': 'Value2'}]}}
Notes
-----
- - The function ignores XML attributes and focuses on the hierarchy and text content of the elements.
- - Elements with the same tag at the same level are compiled into a list to maintain the structure of the XML.
- - Special treatment is given to elements with the tag 'entry', which are always placed in a list to reflect common patterns in XML structures, particularly in API responses.
+ - XML attributes are not included in the output dictionary; only tags and text content are processed.
+ - Repeated tags at the same level are stored as a list to preserve the XML structure within the dictionary format.
+ - The function provides a simplified view of the XML content, suitable for data manipulation and extraction tasks.
Raises
------
ValueError
- If the input XML structure contains complex attributes or nested elements that cannot be represented as a simple key-value pair, a ValueError is raised to highlight the conversion limitation.
+ In cases where the XML structure is too complex to be represented as a dictionary without loss of information (e.g., significant use of attributes or mixed content), a ValueError may be raised to indicate the potential for data loss or misinterpretation.
"""
result = {}
@@ -2311,40 +2471,37 @@ def flatten_xml_to_dict(element: ET.Element) -> dict:
def get_emoji(action: str) -> str:
"""
- Retrieves an emoji character based on a specified action keyword, enhancing the visual appeal of log messages.
+ Retrieves a corresponding emoji for a given action keyword, intended to enhance readability and visual appeal in log messages or user interfaces.
- This function serves as a utility to associate specific action keywords with corresponding emojis, adding a visual dimension to text outputs such as logs or console messages. It simplifies the inclusion of emojis in various parts of the application by providing a centralized mapping of actions to emojis. The function supports a predefined set of action keywords, each mapped to an intuitive emoji. If an action keyword is not recognized, the function returns an empty string, ensuring that the absence of an emoji does not disrupt the application's functionality.
+ This function maps a set of predefined action keywords to their respective emoji symbols, facilitating the inclusion of visual cues in textual outputs. It is particularly useful for enhancing the user experience in console applications or logs by providing immediate, visually distinct feedback for various operations or statuses. If an action keyword is not recognized, the function gracefully returns an empty string, thus maintaining the continuity of the application's output without introducing errors.
Parameters
----------
action : str
- A keyword representing a particular action or outcome. Accepted keywords include 'success', 'warning', 'error', 'working', 'report', 'search', 'save', 'stop', and 'start'. The choice of keywords and emojis is designed to cover common scenarios in application logging and user notifications.
+ The action keyword representing the context or outcome for which an emoji is desired. Supported keywords include 'success', 'warning', 'error', 'working', 'report', 'search', 'save', 'stop', and 'start', each associated with a relevant emoji.
Returns
-------
str
- The emoji character corresponding to the given action keyword. If the keyword is not found in the predefined list, an empty string is returned.
+ An emoji character corresponding to the provided action keyword. If the keyword is not recognized, an empty string is returned, ensuring the calling code's execution is not disrupted.
Examples
--------
- Incorporating emojis into log messages for enhanced readability:
- >>> logging.info(f"{get_emoji('success')} Data processing completed.")
- >>> logging.warning(f"{get_emoji('warning')} Low disk space detected.")
- >>> logging.error(f"{get_emoji('error')} Failed to connect to the database.")
+ Enhancing log messages with emojis for better visibility:
+ >>> logging.info(f"{get_emoji('success')} Operation completed successfully.")
+ >>> logging.warning(f"{get_emoji('warning')} Potential issue detected.")
+ >>> logging.error(f"{get_emoji('error')} Error encountered during execution.")
- Using emojis to enrich console outputs:
- >>> print(f"{get_emoji('start')} Initialization started...")
- >>> print(f"{get_emoji('stop')} Shutdown sequence initiated.")
+ Improving user interface prompts with emojis for clarity:
+ >>> print(f"{get_emoji('start')} Starting process...")
+ >>> print(f"{get_emoji('stop')} Stopping process...")
Notes
-----
- - The function aims to standardize the use of emojis across the application, making it easier to maintain consistency in visual cues.
- - While the current set of action keywords and emojis is based on typical use cases, the function can be easily extended to include additional mappings as required by the application's needs.
+ - This function supports a limited set of action keywords. Extension to include more keywords and corresponding emojis can be easily achieved by updating the internal mapping.
+ - The use of emojis is intended to complement textual information, not replace it, ensuring that the essential message remains clear even in environments where emojis may not be displayed correctly.
- Raises
- ------
- KeyError
- In the event of an unrecognized action keyword, a KeyError could be raised if not handled by the function. However, the current implementation avoids this by returning an empty string for unknown keywords.
+ No exceptions are expected to be raised directly by this function. Unrecognized action keywords will simply result in an empty string, avoiding potential disruptions in the application's output.
"""
emoji_map = {
@@ -2366,35 +2523,37 @@ def get_firewalls_from_panorama(
**filters,
) -> list[Firewall]:
"""
- Fetches firewalls managed by Panorama, with optional filtering based on attributes like model and version.
+ Retrieves a list of Firewall objects managed by a Panorama appliance, optionally filtered by specified attributes.
- This function queries a Panorama appliance for its managed firewalls, returning a list of `Firewall` objects. It supports filtering based on firewall attributes, such as model, serial number, and software version, among others. The filters are applied using regular expressions, offering precise control over the selection of firewalls. The function is particularly useful for operations needing to target specific subsets of firewalls within a larger fleet managed by Panorama.
+ This function communicates with a Panorama appliance to obtain a list of all firewalls under its management. It allows for the application of filters based on firewall attributes such as model, serial number, software version, etc., to refine the selection of firewalls returned. The function utilizes the Panorama API to fetch the relevant data, which is then parsed and converted into Firewall objects for easy manipulation within the script. This is particularly useful for scenarios where operations need to be targeted at specific subsets of firewalls, such as upgrades, configurations, or reporting.
Parameters
----------
panorama : Panorama
- The `Panorama` instance through which the firewalls are managed. Must be properly authenticated and capable of making API requests.
+ An instance of the Panorama class, representing the Panorama appliance from which the managed firewalls are to be retrieved. This instance must be properly initialized and authenticated.
**filters : dict, optional
- Keyword arguments representing the filtering criteria. Each key should correspond to an attribute of a firewall (e.g., 'model', 'serial'), and the value should be a regular expression pattern to match against the attribute value.
+ Arbitrary keyword arguments where each key represents a firewall attribute to filter by (e.g., 'model', 'serial', 'version'), and the corresponding value is a regular expression string used to match against the firewall's attribute value.
Returns
-------
list[Firewall]
- A list of `Firewall` objects, each representing a managed firewall that matches the specified filtering criteria. If no filters are specified, the function returns all managed firewalls.
+ A list containing Firewall objects for each firewall managed by the specified Panorama appliance that matches the provided filtering criteria. If no filters are specified, all managed firewalls are returned.
Examples
--------
- Retrieving all firewalls managed by a Panorama instance:
- >>> all_firewalls = get_firewalls_from_panorama(panorama)
+ Fetching all firewalls managed by Panorama:
+ >>> all_firewalls = get_firewalls_from_panorama(panorama_instance)
- Retrieving firewalls of a specific model:
- >>> pa220_firewalls = get_firewalls_from_panorama(panorama, model='PA-220')
+ Fetching firewalls of a specific model managed by Panorama:
+ >>> specific_model_firewalls = get_firewalls_from_panorama(panorama_instance, model='PA-220')
Notes
-----
- - The function's flexibility in filtering allows for targeted operations on specific groups of firewalls, enhancing efficiency in large-scale environments.
- - Filters leverage regular expressions for pattern matching, providing robust and versatile matching capabilities.
- - The returned `Firewall` objects are ready for further operations, with each linked back to the `Panorama` instance for context and API call routing.
+ - This function is essential for scripts that perform batch operations on multiple firewalls managed by Panorama, allowing for precise targeting of devices.
+ - The filtering feature supports complex selection criteria, making the function highly versatile in managing large deployments.
+ - If `settings_file_path` is specified and contains relevant filters, those filters can override or augment the filters provided as arguments to this function.
+
+ No exceptions are explicitly raised by this function; however, API call failures due to network issues, authentication errors, or invalid filter syntax can result in runtime errors that should be handled by the calling code.
"""
firewalls = []
@@ -2411,35 +2570,37 @@ def get_managed_devices(
**filters,
) -> list[ManagedDevice]:
"""
- Fetches a list of devices managed by a specified Panorama, with optional filtering based on attributes.
+ Retrieves devices managed by Panorama, optionally filtered by device attributes.
- This function queries a Panorama appliance for its managed devices, returning a list of `ManagedDevice` objects. It supports filtering based on device attributes, such as hostname, model, serial number, and software version, among others. The filters are applied using regular expressions, offering precise control over the selection of devices. The function is particularly useful for operations needing to target specific subsets of devices within a larger fleet managed by Panorama.
+ This function interacts with Panorama to obtain a list of managed devices, such as firewalls, and can filter these devices based on specified attributes like model, serial number, or software version. The filtering is performed using regular expressions, providing a powerful mechanism for precisely targeting specific devices. This capability is invaluable for scenarios requiring operations on particular groups of devices, such as updates, configurations, or monitoring.
Parameters
----------
panorama : Panorama
- The `Panorama` instance through which the devices are managed. Must be properly authenticated and capable of making API requests.
+ An instance of the Panorama class, representing the Panorama management server from which the list of managed devices is to be fetched. This instance should be properly authenticated to enable API communications.
**filters : dict, optional
- Keyword arguments representing the filtering criteria. Each key should correspond to a device attribute (e.g., 'model', 'serial'), and the value should be a regular expression pattern to match against the attribute value.
+ A set of keyword arguments where each key represents a device attribute to filter by (e.g., 'model', 'serial', 'version'), and the corresponding value is a regular expression pattern used to match against the device's attribute value.
Returns
-------
list[ManagedDevice]
- A list of `ManagedDevice` objects, each representing a managed device that matches the specified filtering criteria. If no filters are specified, the function returns all managed devices.
+ A list of `ManagedDevice` objects that represent each device managed by Panorama and matching the provided filtering criteria. If no filters are specified, the function returns a list of all managed devices.
Examples
--------
- Retrieving all devices managed by a Panorama instance:
- >>> all_devices = get_managed_devices(panorama)
+ Fetching all devices managed by Panorama:
+ >>> all_devices = get_managed_devices(panorama_instance)
- Retrieving devices of a specific model:
- >>> pa220_devices = get_managed_devices(panorama, model='PA-220')
+ Fetching devices of a specific model managed by Panorama:
+ >>> specific_model_devices = get_managed_devices(panorama_instance, model='PA-220')
Notes
-----
- - The function's flexibility in filtering allows for targeted operations on specific groups of devices, enhancing efficiency in large-scale environments.
- - Filters leverage regular expressions for pattern matching, providing robust and versatile matching capabilities.
- - The returned `ManagedDevice` objects are ready for further operations, with each linked back to the `Panorama` instance for context and API call routing.
+ - The function's use of regular expressions for filtering provides a flexible and powerful means to specify complex selection criteria.
+ - While primarily used for retrieving device lists, the function's output can serve as the basis for further device-specific operations or queries.
+ - If the `settings_file_path` is specified and includes relevant filters, those settings can be used to augment or override the function's filters.
+
+ The implementation should handle Panorama API interactions and apply the filters to the retrieved device list, ensuring that only devices matching all provided filters are included in the returned list.
"""
managed_devices = model_from_api_response(
@@ -2458,43 +2619,41 @@ def get_managed_devices(
def ip_callback(value: str) -> str:
"""
- Validates a given string as either a resolvable hostname or a valid IP address.
+ Validates and returns a given string if it's a resolvable hostname or a valid IP address.
- This function serves as a callback for command-line interface inputs, ensuring that provided
- network addresses are either valid IP addresses (IPv4 or IPv6) or hostnames that can be resolved
- to IP addresses. It first attempts to validate the input as an IP address using the 'ipaddress'
- module. If the input is not a valid IP address, it then attempts to resolve the input as a hostname.
- If both checks fail, the function raises an error, prompting the user to provide a valid input.
+ This function is primarily intended as a callback for CLI input validation, ensuring that provided
+ values are either valid IPv4/IPv6 addresses or hostnames that can be resolved. It first checks if the
+ input is a valid IP address using Python's 'ipaddress' module. If not, it attempts to resolve the string
+ as a hostname. If both checks fail, the function raises a Typer error to prompt the user for a valid input.
+ This ensures that subsequent operations receive only valid network endpoint identifiers.
Parameters
----------
value : str
- The input string provided by the user, expected to be an IP address or a hostname.
+ The user-provided string intended to represent either a hostname or an IP address.
Returns
-------
str
- The validated input string if it is a resolvable hostname or a valid IP address.
+ The original input string if it is successfully validated as either a resolvable hostname or a valid IP address.
Raises
------
typer.BadParameter
- Raised when the input value is neither a valid IP address nor a resolvable hostname, indicating
- the need for the user to provide a corrected input.
+ If the input string cannot be validated as either a resolvable hostname or a valid IP address, this exception is raised to signal to the user that the provided input is invalid and needs correction.
Example
-------
- Using the callback in a Typer command to validate user input:
+ Using `ip_callback` as a Typer option callback to ensure valid network endpoint input:
>>> @app.command()
- >>> def network_ping(host: str = typer.Option(..., callback=ip_callback)):
- >>> # Function body to ping the provided host
+ >>> def check_endpoint(host: str = typer.Option(..., callback=ip_callback)):
+ >>> print(f"Checking endpoint: {host}")
Notes
-----
- - Utilization of this callback in a CLI application aids in early validation of network addresses,
- enhancing user experience by preventing further processing of invalid inputs.
- - This function integrates seamlessly with Typer CLI applications, leveraging Typer's exception handling
- to provide informative feedback to the user.
+ - The function leverages both DNS resolution checks and the 'ipaddress' module for comprehensive validation.
+ - It is particularly useful in CLI applications where early validation of network-related user input is crucial.
+ - If `settings_file_path` is specified and contains relevant network settings, these can potentially be used to adjust or bypass validation based on the application's requirements.
"""
# First, try to resolve as a hostname
@@ -2517,46 +2676,53 @@ def model_from_api_response(
model: type[FromAPIResponseMixin],
) -> FromAPIResponseMixin:
"""
- Converts an XML element or tree from an API response into a structured object based on a Pydantic model.
+ Transforms an XML element or tree from an API response into a structured Pydantic model instance.
- This function takes an XML element or an entire XML tree, typically obtained from an API response, and
- converts it into a structured Pydantic model. It employs a flattening process to convert the XML into a
- dictionary, which is then mapped to a Pydantic model. This model is expected to inherit from
- FromAPIResponseMixin, indicating its suitability for instantiation with API response data. The conversion
- facilitates structured and type-checked access to the data contained in the XML, enhancing data handling
- and integration in Python applications.
+ This utility function is designed to facilitate the conversion of XML data, typically retrieved from API
+ responses, into structured Pydantic model instances. It streamlines the process of parsing XML and mapping
+ the extracted data to predefined Pydantic model fields, enabling more efficient and type-safe data handling
+ within Python applications. The function assumes that the provided Pydantic model includes a mixin or
+ inherits from a base class designed to handle data deserialization from API responses.
Parameters
----------
element : Union[ET.Element, ET.ElementTree]
- The XML element or tree to be transformed. This can be a single XML element or an entire XML document
- tree, as obtained from parsing an XML-based API response.
+ The root XML element or an entire XML document tree that is to be converted. This data structure usually
+ represents a portion or the entirety of an API response in XML format.
model : type[FromAPIResponseMixin]
- The Pydantic model class, inheriting from FromAPIResponseMixin, which defines the structure into which
- the XML data will be mapped. This model outlines the expected fields and their types, based on the API
- response structure.
+ A Pydantic model class that inherits from `FromAPIResponseMixin`, defining the schema for the expected
+ data structure. The model should include fields corresponding to the XML data to be processed, facilitating
+ the mapping of XML elements to model attributes.
Returns
-------
FromAPIResponseMixin
- An instance of the specified Pydantic model, populated with data extracted from the XML element or tree.
- This structured object provides typed fields corresponding to the elements within the XML, according to
- the model's definition.
+ An instance of the specified Pydantic model, populated with the data extracted from the provided XML
+ element or tree. The model instance offers a structured, type-safe interface to the API response data,
+ aligned with the model's schema.
Example
-------
- Converting XML API response to a Pydantic model:
- >>> xml_data = ET.fromstring('123active')
- >>> DeviceModel = type('DeviceModel', (FromAPIResponseMixin, BaseModel), {'id': int, 'status': str})
- >>> device = model_from_api_response(xml_data, DeviceModel)
- # 'device' now is an instance of 'DeviceModel' with 'id' and 'status' attributes populated from 'xml_data'.
+ Parsing an XML API response into a Pydantic model:
+ >>> xml_response = ET.fromstring('101John Doe')
+ >>> UserModel = type('UserModel', (FromAPIResponseMixin, BaseModel), {'id': int, 'name': str})
+ >>> user = model_from_api_response(xml_response, UserModel)
+ # The 'user' object is an instance of 'UserModel' with 'id' and 'name' populated from the XML response.
Notes
-----
- - The function simplifies the extraction of data from XML API responses, making it readily usable within
- Python applications by providing a structured and type-checked interface to the data.
- - Care should be taken to ensure the Pydantic model accurately reflects the structure and data types present
- in the XML response to avoid data mapping errors or loss.
+ - This function abstracts the complexities of XML parsing and data mapping, enabling developers to work with
+ API response data in a more Pythonic and object-oriented manner.
+ - It is important that the Pydantic model accurately reflects the structure of the XML response to ensure
+ correct data mapping. Discrepancies between the model and the XML structure may result in incomplete or
+ incorrect data representation.
+
+ Raises
+ ------
+ ValueError
+ If the XML-to-dictionary conversion results in a structure that cannot be directly mapped to the provided
+ Pydantic model, a ValueError may be raised, indicating a potential mismatch between the model schema and
+ the XML data structure.
"""
result_dict = flatten_xml_to_dict(element)
@@ -2565,43 +2731,49 @@ def model_from_api_response(
def parse_version(version: str) -> Tuple[int, int, int, int]:
"""
- Extracts numerical components from a version string, including optional hotfix, into a tuple.
+ Parses a version string into a tuple of integers representing its major, minor, maintenance, and hotfix components.
- This function interprets a version string formatted as 'major.minor.maintenance' or
- 'major.minor.maintenance-hhotfix', where each segment represents numerical values for major, minor,
- and maintenance versions, with an optional hotfix number following a '-h'. The function splits these
- components and converts them into a tuple of integers for easy comparison and processing. In cases where
- a segment is absent, it defaults to 0, ensuring consistency in the tuple structure returned.
+ This utility function analyzes a version string, expected to be in the format of 'major.minor.maintenance' or
+ 'major.minor.maintenance-hhotfix', and extracts the numerical components. The hotfix component is optional and
+ denoted by a '-h' followed by its number. If a component is missing, it defaults to 0 to maintain a consistent
+ tuple structure. This parsing facilitates version comparison and sorting by converting version strings into
+ a uniform tuple format.
Parameters
----------
version : str
- A string representing the version in the format 'major.minor.maintenance' or
- 'major.minor.maintenance-hhotfix'. Each part of the version (major, minor, maintenance, hotfix)
- should be a numerical value.
+ The version string to parse, adhering to the 'major.minor.maintenance' or
+ 'major.minor.maintenance-hhotfix' format. Each segment (major, minor, maintenance, hotfix) should be
+ a numerical value.
Returns
-------
Tuple[int, int, int, int]
- A 4-tuple where each element represents major, minor, maintenance, and hotfix versions as integers.
- If the hotfix is not specified in the version string, it defaults to 0.
+ A tuple containing four integers representing the major, minor, maintenance versions, and the hotfix
+ number, respectively. The hotfix number defaults to 0 if not specified in the input string.
- Example
- -------
- Parsing a standard version string:
- >>> parse_version("3.5.8")
- (3, 5, 8, 0)
+ Examples
+ --------
+ Parsing a version string without a hotfix:
+ >>> parse_version("10.0.1")
+ (10, 0, 1, 0)
Parsing a version string with a hotfix component:
- >>> parse_version("3.5.8-h1")
- (3, 5, 8, 1)
+ >>> parse_version("10.0.1-h2")
+ (10, 0, 1, 2)
Notes
-----
- - This function is crucial for operations that require version comparison, ensuring proper version
- management and compliance with version-dependent features or requirements.
- - It is assumed that the input string follows the specified format. Deviations may result in incorrect
- parsing or conversion errors.
+ - This function is essential for operations that involve version comparison and management, ensuring that
+ version strings can be reliably interpreted and ordered.
+ - It assumes that the provided version string conforms to the specified format. Variations from this format
+ may lead to incorrect parsing or errors.
+
+ Raises
+ ------
+ ValueError
+ If the version string is malformatted or contains non-numeric components where integers are expected,
+ a ValueError is raised to indicate the parsing failure.
"""
parts = version.split(".")
@@ -2623,36 +2795,35 @@ def parse_version(version: str) -> Tuple[int, int, int, int]:
def resolve_hostname(hostname: str) -> bool:
"""
- Attempts to resolve a hostname to an IP address using DNS lookup.
+ Verifies if a given hostname can be resolved to an IP address using DNS lookup.
- Performs a DNS lookup to check if the provided hostname can be resolved to an IP address, indicating
- network connectivity and DNS functionality. Successful resolution suggests that the hostname is valid
- and accessible, while failure might indicate problems with the hostname, DNS settings, or network issues.
+ This function is crucial for network-related operations, as it checks the resolvability of a hostname. It performs a DNS query to determine if the hostname can be translated into an IP address, thereby validating its presence on the network. A successful DNS resolution implies the hostname is active and reachable, while a failure might indicate an issue with the hostname itself, DNS configuration, or broader network problems.
Parameters
----------
hostname : str
- The hostname (e.g., 'example.com') to be resolved, to verify its validity and accessibility.
+ The hostname to be resolved, such as 'example.com', to verify network reachability and DNS configuration.
Returns
-------
bool
- True if the hostname resolves to an IP address, false otherwise, indicating potential issues with
- the hostname or network configuration.
+ Returns True if the DNS resolution is successful, indicating the hostname is valid and reachable. Returns False if the resolution fails, suggesting potential issues with the hostname, DNS setup, or network connectivity.
Example
-------
- Verifying the resolution of a hostname:
- >>> resolve_hostname('www.example.com')
- True # Indicates that 'www.example.com' is resolvable and accessible.
+ Validating hostname resolution:
+ >>> resolve_hostname('google.com')
+ True # This would indicate that 'google.com' is successfully resolved, suggesting it is reachable.
- >>> resolve_hostname('unknown.hostname')
- False # Indicates that 'unknown.hostname' cannot be resolved, suggesting it may be invalid or there are network/DNS issues.
+ >>> resolve_hostname('invalid.hostname')
+ False # This would indicate a failure in resolving 'invalid.hostname', pointing to potential DNS or network issues.
Notes
-----
- - Useful as an initial verification step before establishing network connections to a specified hostname.
- - Exceptions are managed internally, with errors logged for troubleshooting, allowing the function to return a simple boolean value.
+ - This function is intended as a preliminary network connectivity check before attempting further network operations.
+ - It encapsulates exception handling for DNS resolution errors, logging them for diagnostic purposes while providing a simple boolean outcome to the caller.
+
+ The function's behavior and return values are not affected by external configurations or settings, hence no mention of `settings.yaml` file override capability is included.
"""
try:
@@ -2670,50 +2841,77 @@ def resolve_hostname(hostname: str) -> bool:
app = typer.Typer(help="PAN-OS Upgrade script")
+# ----------------------------------------------------------------------------
+# Global variables
+# ----------------------------------------------------------------------------
+
+# Define the path to the settings file
+settings_file_path = Path.cwd() / "settings.yaml"
+
+# Initialize Dynaconf settings object conditionally based on the existence of settings.yaml
+if settings_file_path.exists():
+ settings_file = Dynaconf(settings_files=[str(settings_file_path)])
+else:
+ settings_file = Dynaconf()
+
+# Global list and lock for storing HA active firewalls and Panorama to revisit
+target_devices_to_revisit = []
+target_devices_to_revisit_lock = Lock()
+
+# Define logging levels
+LOGGING_LEVELS = {
+ "debug": logging.DEBUG,
+ "info": logging.INFO,
+ "warning": logging.WARNING,
+ "error": logging.ERROR,
+ "critical": logging.CRITICAL,
+}
+
+
# ----------------------------------------------------------------------------
# Common setup for all subcommands
# ----------------------------------------------------------------------------
def common_setup(
- hostname: str, username: str, password: str, log_level: str
+ hostname: str,
+ username: str,
+ password: str,
) -> PanDevice:
"""
- Performs initial setup tasks for a device, including directory creation, logging configuration, and device connection.
+ Initializes the environment for interacting with a Palo Alto Networks device, including directory setup, logging configuration, and establishing a device connection.
- This function encompasses the preliminary steps required to establish a managed environment for interacting with
- a Palo Alto Networks device. It ensures that necessary directories for logging and data storage are present,
- configures logging according to the specified level, and establishes a connection to the device using provided
- credentials. The function returns an instance of the connected device, ready for further operations.
+ This function consolidates essential preparatory steps required before performing operations on a Palo Alto Networks device. It ensures the creation of necessary directories for organized data storage and logs, sets up logging with a configurable verbosity level, and establishes a secure connection to the device using the provided API credentials. The function is designed to return a `PanDevice` object, which could be a `Firewall` or `Panorama` instance, ready for subsequent API interactions.
Parameters
----------
hostname : str
- The IP address or DNS hostname of the target device.
+ The network address or DNS name of the Palo Alto Networks device to connect to.
username : str
- The username for authentication with the target device.
+ The API username for authenticating with the device.
password : str
- The password for authentication with the target device.
- log_level : str
- The desired logging level (e.g., 'DEBUG', 'INFO') for the application.
+ The API password for authenticating with the device.
Returns
-------
PanDevice
- An instance of `PanDevice` representing the connected device, either a `Firewall` or `Panorama` object,
- depending on the target device type.
+ A connected `PanDevice` instance, representing the target Palo Alto Networks device, fully initialized and ready for further API operations.
Example
-------
- Setting up a common environment for a device:
- >>> device = common_setup('192.168.1.1', 'admin', 'adminpassword', 'INFO')
- # This will create necessary directories, configure logging, and return a connected device instance.
+ Initializing the environment for a device:
+ >>> device = common_setup('10.0.0.1', 'apiuser', 'apipassword')
+ # Ensures necessary directories exist, logging is configured, and returns a connected `PanDevice` instance.
Notes
-----
- - Directory creation is idempotent; it checks for existence before creation to avoid redundancy.
- - Logging configuration is applied globally for the application, influencing all subsequent logging calls.
- - The connection to the device is established using API credentials, and the function assumes network reachability.
+ - Directory setup is performed only once; existing directories are not modified.
+ - Logging configuration affects the entire application's logging behavior; the log level can be overridden by `settings.yaml` if `settings_file_path` is detected in the function.
+ - A successful device connection is critical for the function to return; otherwise, it may raise exceptions based on connection issues.
+
+ The ability to override default settings with `settings.yaml` is supported for the log level configuration in this function if `settings_file_path` is utilized within `configure_logging`.
"""
+ log_level = settings_file.get("logging.level", "INFO")
+
# Create necessary directories
directories = [
"logs",
@@ -2786,55 +2984,52 @@ def firewall(
prompt="Dry Run?",
),
] = False,
- log_level: Annotated[
- str,
- typer.Option(
- "--log-level",
- "-l",
- help="Set the logging output level",
- ),
- ] = "info",
):
"""
- Initiates the upgrade process for a specified firewall appliance.
+ Launches the upgrade process for a Palo Alto Networks firewall, facilitating a comprehensive and controlled upgrade workflow.
- This subcommand handles the upgrade of a firewall device by performing necessary preparatory steps,
- including connectivity checks, version validation, and upgrade execution. It supports a dry run mode
- for testing the upgrade procedure without making any changes to the device. The process involves setting
- up the environment, including logging configuration and directory structure, followed by the actual upgrade
- steps as defined in the `upgrade_firewall` function.
+ This command orchestrates the steps required for upgrading a firewall appliance, encompassing initial validations, environment setup, and execution of the upgrade process. It is capable of operating in a 'dry run' mode, which simulates the upgrade process without applying any changes, allowing for validation of upgrade prerequisites and assessment of potential issues. The command leverages a series of internal functions to prepare the environment, verify connectivity, ensure compatibility with the target version, and, if all checks pass, proceed with the upgrade.
Parameters
----------
hostname : str
- The IP address or resolvable DNS name of the firewall or Panorama appliance.
+ The IP address or DNS hostname of the firewall to be upgraded. Must be accessible from the execution environment.
username : str
- The username required for authentication with the target device.
+ The administrative username for the firewall, used for API or CLI authentication.
password : str
- The password corresponding to the provided username for device authentication.
+ The corresponding password for the provided administrative username.
target_version : str
- The software version to which the firewall will be upgraded.
+ The version of PAN-OS to which the firewall is to be upgraded. Must be a valid and supported version for the device.
dry_run : bool, optional
- If set to True, performs all preparatory checks without applying the upgrade, by default False.
- log_level : str, optional
- Specifies the verbosity of log messages, by default 'info'.
+ When set to True, the function performs all preparatory and validation steps without executing the actual upgrade, defaulting to False.
Examples
--------
- Upgrading a firewall to a specific version:
- $ python script.py firewall --hostname 192.168.1.1 --username admin --password adminpassword --version 9.1.3 --dry-run
+ Executing an upgrade to version 9.1.0:
+ $ python upgrade.py firewall --hostname 192.168.1.1 --username admin --password secure123 --version 9.1.0
- Performing a dry run to check for upgrade feasibility:
- $ python script.py firewall --hostname 192.168.1.1 --username admin --password adminpassword --version 9.1.3 --dry-run True
+ Performing a dry run for version 9.1.0:
+ $ python upgrade.py firewall --hostname 192.168.1.1 --username admin --password secure123 --version 9.1.0 --dry-run
Notes
-----
- - Ensure network connectivity and correct credentials before attempting the upgrade.
- - The dry run mode is recommended for verifying upgrade requirements and potential issues without impacting the device.
+ - Prior to executing the upgrade, ensure that the firewall is in a stable state and that there is a reliable network connection to the device.
+ - The 'dry run' mode is highly recommended for a preliminary assessment to identify any potential issues that might impede the upgrade process.
+ - Default settings for the upgrade process, such as log levels and file paths, can be overridden by providing a `settings.yaml` file, if supported by the implementation of `common_setup` and other called functions within this command.
"""
+ # Display the custom banner for firewall upgrade
+ if settings_file_path.exists():
+ console_welcome_banner(mode="firewall", config_path=settings_file_path)
+ else:
+ console_welcome_banner(mode="firewall")
+
# Perform common setup tasks, return a connected device
- device = common_setup(hostname, username, password, log_level)
+ device = common_setup(
+ hostname,
+ username,
+ password,
+ )
# Perform upgrade
upgrade_firewall(
@@ -2896,54 +3091,52 @@ def panorama(
prompt="Dry Run?",
),
] = False,
- log_level: Annotated[
- str,
- typer.Option(
- "--log-level",
- "-l",
- help="Set the logging output level",
- ),
- ] = "info",
):
"""
- Initiates the upgrade process for a specified Panorama appliance.
+ Manages the upgrade process for a Panorama management platform, orchestrating the sequence of actions required for a successful upgrade.
- This subcommand orchestrates the upgrade of a Panorama device, encompassing preparatory steps such as
- connectivity verification, version validation, and the execution of the upgrade. A dry run mode is available,
- enabling the simulation of the upgrade process without actual changes. The upgrade procedure involves setting up
- the environment, configuring logging, and performing the upgrade through the `upgrade_panorama` function.
+ This command facilitates the upgrade of a Panorama appliance by executing a series of preparatory actions, including validation of connectivity, compatibility checks with the target version, and the actual upgrade process. The dry run mode allows operators to simulate the upgrade steps without making any modifications to the Panorama appliance. The command leverages internal utility functions to ensure the environment is correctly configured, to establish a connection to the Panorama, and to conduct the upgrade according to the parameters specified.
Parameters
----------
hostname : str
- The IP address or resolvable DNS name of the Panorama appliance.
+ The network address of the Panorama appliance, either as an IP address or a DNS-resolvable hostname.
username : str
- Username for authentication with the Panorama appliance.
+ The administrative username required for authentication on the Panorama appliance.
password : str
- Password for authentication with the Panorama appliance.
+ The corresponding password for the specified administrative username.
target_version : str
- The Panorama version to which the upgrade is targeted.
+ The target version of PAN-OS to which the Panorama appliance is to be upgraded.
dry_run : bool, optional
- If True, performs all pre-upgrade checks without executing the actual upgrade, by default False.
- log_level : str, optional
- Determines the verbosity of logging output, by default 'info'.
+ A boolean flag indicating whether to simulate the upgrade process without applying changes, defaulting to False.
Examples
--------
- Upgrading a Panorama to a specific version:
- $ python script.py panorama --hostname panorama.example.com --username admin --password adminpassword --version 9.1.3
+ Directly upgrading a Panorama appliance:
+ $ python upgrade.py panorama --hostname panorama.example.com --username admin --password secure123 --version 10.0.0
- Executing a dry run to verify upgrade readiness:
- $ python script.py panorama --hostname panorama.example.com --username admin --password adminpassword --version 9.1.3 --dry-run
+ Conducting a dry run for the upgrade process:
+ $ python upgrade.py panorama --hostname panorama.example.com --username admin --password secure123 --version 10.0.0 --dry-run
Notes
-----
- - Verify network connectivity and credentials before commencing the upgrade.
- - The dry run option is recommended to check for potential issues without impacting the Panorama appliance.
+ - It is critical to ensure that the Panorama appliance is accessible and that the provided credentials are correct before initiating the upgrade process.
+ - Utilizing the dry run mode is strongly recommended for validating the upgrade path and identifying any potential obstacles without risking the operational state of the Panorama appliance.
+ - Settings for the upgrade process, such as logging levels and file paths, may be overridden by a `settings.yaml` file if present and detected by the implementation of `common_setup` and other invoked functions within this command.
"""
+ # Display the custom banner for panorama upgrade
+ if settings_file_path.exists():
+ console_welcome_banner(mode="panorama", config_path=settings_file_path)
+ else:
+ console_welcome_banner(mode="panorama")
+
# Perform common setup tasks, return a connected device
- device = common_setup(hostname, username, password, log_level)
+ device = common_setup(
+ hostname,
+ username,
+ password,
+ )
# Perform upgrade
upgrade_panorama(
@@ -3014,57 +3207,54 @@ def batch(
prompt="Dry Run?",
),
] = False,
- log_level: Annotated[
- str,
- typer.Option(
- "--log-level",
- "-l",
- help="Set the logging output level",
- ),
- ] = "info",
):
"""
- Executes a batch upgrade of firewalls managed by a Panorama appliance based on specified criteria.
+ Executes a coordinated upgrade of multiple firewalls managed by a Panorama appliance using specified criteria.
- This command facilitates the mass upgrade of firewalls under Panorama's management, applying a consistent
- target version across multiple devices. It leverages Panorama's centralized management capabilities to proxy
- connections to individual firewalls, allowing for coordinated upgrades. A filter option is available to
- target specific subsets of firewalls based on criteria like hostname patterns, models, or locations. The
- process supports a dry-run mode for validation purposes without applying changes.
+ This command streamlines the process of upgrading a batch of firewalls by leveraging Panorama's centralized management capabilities. It supports filtering to target specific devices for the upgrade and offers a dry run option for validation without making changes. The function initiates by preparing the environment, validating connectivity to Panorama, and then sequentially or concurrently upgrading each managed firewall that meets the filter criteria.
Parameters
----------
hostname : str
- The IP address or resolvable DNS name of the Panorama appliance managing the firewalls.
+ The network address of the Panorama appliance, either as an IP address or a DNS-resolvable hostname.
username : str
- Username for authentication with the Panorama appliance.
+ The administrative username required for authentication on the Panorama appliance.
password : str
- Password for authentication with the Panorama appliance.
+ The corresponding password for the specified administrative username.
target_version : str
- The version to which the firewalls should be upgraded.
+ The PAN-OS version to which the targeted firewalls will be upgraded.
filter : str, optional
- A filter string defining criteria to select specific firewalls for the upgrade, by default "".
+ A string used to define filtering criteria for selecting specific firewalls managed by Panorama, default is empty which implies no filtering.
dry_run : bool, optional
- If True, performs all upgrade checks without executing the actual upgrade, by default False.
- log_level : str, optional
- The verbosity level of logging output, by default "info".
+ A flag to indicate whether to simulate the upgrade process without making any actual changes, default is False.
Examples
--------
- Executing a batch upgrade for firewalls managed by Panorama:
- $ python script.py batch --hostname panorama.example.com --username admin --password adminpassword --version 9.1.3 --filter "model=PA-220"
+ Performing a batch upgrade of firewalls:
+ $ python upgrade.py batch --hostname panorama.example.com --username admin --password adminpassword --version 9.1.3 --filter "model=PA-220"
- Performing a dry run to validate the batch upgrade process:
- $ python script.py batch --hostname panorama.example.com --username admin --password adminpassword --version 9.1.3 --filter "location=DataCenter" --dry-run
+ Conducting a dry run of a batch upgrade:
+ $ python upgrade.py batch --hostname panorama.example.com --username admin --password adminpassword --version 9.1.3 --filter "location=DataCenter" --dry-run
Notes
-----
- - Ensure Panorama connectivity and correct credentials before initiating the batch upgrade.
- - The dry run mode is recommended to assess the upgrade's impact and readiness without affecting the operational state.
+ - Ensure connectivity to Panorama and validity of credentials before initiating the batch upgrade.
+ - The dry run option is highly recommended for assessing the upgrade's feasibility and identifying any preparatory actions required without impacting the operational state of the firewalls.
+ - Configuration settings, such as logging levels and paths, can be customized through a `settings.yaml` file if available. The presence of this file and its path may be indicated by the global variable `settings_file_path` if implemented in the script.
"""
+ # Display the custom banner for batch firewall upgrades
+ if settings_file_path.exists():
+ console_welcome_banner(mode="batch", config_path=settings_file_path)
+ else:
+ console_welcome_banner(mode="batch")
+
# Perform common setup tasks, return a connected device
- device = common_setup(hostname, username, password, log_level)
+ device = common_setup(
+ hostname,
+ username,
+ password,
+ )
# Perform batch upgrade
firewalls_to_upgrade = []
@@ -3098,7 +3288,9 @@ def batch(
)
# Using ThreadPoolExecutor to manage threads
- with ThreadPoolExecutor(max_workers=10) as executor:
+ threads = settings_file.get("concurrency.threads", 10)
+ logging.debug(f"{get_emoji('working')} {hostname}: Using {threads} threads.")
+ with ThreadPoolExecutor(max_workers=threads) as executor:
# Store future objects along with firewalls for reference
future_to_firewall = {
executor.submit(
@@ -3127,7 +3319,9 @@ def batch(
)
# Using ThreadPoolExecutor to manage threads for revisiting firewalls
- with ThreadPoolExecutor(max_workers=2) as executor:
+ threads = settings_file.get("concurrency.threads", 10)
+ logging.debug(f"{get_emoji('working')} {hostname}: Using {threads} threads.")
+ with ThreadPoolExecutor(max_workers=threads) as executor:
future_to_firewall = {
executor.submit(
upgrade_firewall, target_device, target_version, dry_run
@@ -3153,5 +3347,128 @@ def batch(
target_devices_to_revisit.clear()
+# ----------------------------------------------------------------------------
+# Subcommand for creating a settings.yaml file to override default settings
+# ----------------------------------------------------------------------------
+@app.command()
+def settings():
+ """
+ Generates a settings.yaml file allowing customization of script configurations.
+
+ This interactive command guides the user through a series of prompts to configure various aspects of the script's behavior, including concurrency, logging, reboot strategies, readiness checks, snapshots, and timeout settings. Each configuration section allows the user to specify preferences, such as the number of concurrent threads, logging levels, and file paths, among others. Customization of readiness checks and snapshots is also offered, enabling selective execution based on user requirements. The resulting configurations are saved to a 'settings.yaml' file in the current working directory, which the script can subsequently use to override default settings.
+
+ Configuration Sections
+ ----------------------
+ - Concurrency: Defines the number of concurrent operations, particularly useful for batch operations.
+ - Logging: Sets logging preferences including verbosity level, file path, maximum size, and log retention count.
+ - Reboot: Configures retry intervals and maximum attempts for device reboots during the upgrade process.
+ - Readiness Checks: Allows customization of pre-upgrade readiness checks to run.
+ - Snapshots: Enables configuration of pre and post-upgrade snapshots for comparison and rollback purposes.
+ - Timeout Settings: Determines timeout values for device connections and command executions.
+
+ Notes
+ -----
+ - This command is part of the setup process and is intended to be run prior to executing upgrade commands.
+ - The 'settings.yaml' file created by this command can be edited manually for further customization.
+ - Default values are provided for each configuration option, with the option to accept the default or provide a custom value.
+ """
+
+ # Display the custom banner for settings
+ console_welcome_banner(mode="settings")
+
+ config_file_path = Path.cwd() / "settings.yaml"
+
+ config_data = {
+ "concurrency": {
+ "threads": typer.prompt(
+ "Number of concurrent threads",
+ default=10,
+ type=int,
+ ),
+ },
+ "logging": {
+ "level": typer.prompt("Logging level", default="INFO"),
+ "file_path": typer.prompt("Path for log files", default="logs/upgrade.log"),
+ "max_size": typer.prompt(
+ "Maximum log file size (MB)",
+ default=10,
+ type=int,
+ ),
+ "upgrade_log_count": typer.prompt(
+ "Number of upgrade logs to retain",
+ default=10,
+ type=int,
+ ),
+ },
+ "reboot": {
+ "retry_interval": typer.prompt(
+ "Reboot retry interval (seconds)",
+ default=60,
+ type=int,
+ ),
+ "max_tries": typer.prompt(
+ "Maximum reboot tries",
+ default=30,
+ type=int,
+ ),
+ },
+ "readiness_checks": {
+ "customize": typer.confirm(
+ "Would you like to customize readiness checks?",
+ default=False,
+ ),
+ "checks": {},
+ "location": typer.prompt(
+ "Location to save readiness checks",
+ default="assurance/readiness_checks/",
+ ),
+ },
+ "snapshots": {
+ "customize": typer.confirm(
+ "Would you like to customize snapshots?", default=False
+ ),
+ "state": {},
+ "location": typer.prompt(
+ "Location to save snapshots",
+ default="assurance/snapshots/",
+ ),
+ },
+ "timeout_settings": {
+ "connection_timeout": typer.prompt(
+ "Connection timeout (seconds)",
+ default=30,
+ type=int,
+ ),
+ "command_timeout": typer.prompt(
+ "Command timeout (seconds)",
+ default=120,
+ type=int,
+ ),
+ },
+ }
+
+ if config_data["readiness_checks"]["customize"]:
+ for check, info in AssuranceOptions.READINESS_CHECKS.items():
+ config_data["readiness_checks"]["checks"][check] = typer.confirm(
+ f"Enable {info['description']}?", default=True
+ )
+
+ if config_data["snapshots"]["customize"]:
+ for snapshot in AssuranceOptions.STATE_SNAPSHOTS:
+ config_data["snapshots"]["state"][snapshot] = typer.confirm(
+ f"Enable {snapshot} snapshot?", default=True
+ )
+
+ with open(config_file_path, "w") as f:
+ yaml.dump(
+ config_data,
+ f,
+ default_flow_style=False,
+ sort_keys=True,
+ )
+
+ typer.echo(f"Configuration saved to {config_file_path}")
+
+
if __name__ == "__main__":
app()
diff --git a/poetry.lock b/poetry.lock
index 3f07737..1dc83ee 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,3 +1,5 @@
+# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand.
+
[[package]]
name = "annotated-types"
version = "0.6.0"
@@ -5,6 +7,10 @@ description = "Reusable constraint types to use with typing.Annotated"
category = "main"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
+ {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
+]
[[package]]
name = "asttokens"
@@ -13,6 +19,10 @@ description = "Annotate AST trees with source code positions"
category = "dev"
optional = false
python-versions = "*"
+files = [
+ {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
+ {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
+]
[package.dependencies]
six = ">=1.12.0"
@@ -28,6 +38,10 @@ description = "Internationalization utilities"
category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"},
+ {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"},
+]
[package.extras]
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
@@ -39,6 +53,30 @@ description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
+ {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
+ {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"},
+ {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"},
+ {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
+ {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
+ {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
+ {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
+ {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
+ {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"},
+ {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"},
+ {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"},
+ {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"},
+ {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"},
+ {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"},
+ {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"},
+ {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"},
+ {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"},
+ {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"},
+ {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"},
+ {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
+ {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
+]
[package.dependencies]
click = ">=8.0.0"
@@ -60,6 +98,10 @@ description = "Python package for providing Mozilla's CA Bundle."
category = "dev"
optional = false
python-versions = ">=3.6"
+files = [
+ {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"},
+ {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
+]
[[package]]
name = "cffi"
@@ -68,6 +110,60 @@ description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
+ {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
+ {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
+ {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
+ {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
+ {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
+ {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
+ {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
+ {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
+ {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
+ {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
+ {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
+ {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
+ {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
+ {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
+]
[package.dependencies]
pycparser = "*"
@@ -79,6 +175,98 @@ description = "The Real First Universal Charset Detector. Open, modern and activ
category = "dev"
optional = false
python-versions = ">=3.7.0"
+files = [
+ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
+ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
+]
[[package]]
name = "click"
@@ -87,6 +275,10 @@ description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+ {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
+]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
@@ -98,6 +290,10 @@ description = "Cross-platform colored terminal text."
category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
[[package]]
name = "cryptography"
@@ -106,6 +302,31 @@ description = "cryptography is a package which provides cryptographic recipes an
category = "main"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"},
+ {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"},
+ {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"},
+ {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"},
+ {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"},
+ {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"},
+ {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"},
+ {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"},
+ {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"},
+ {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"},
+ {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"},
+ {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"},
+ {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"},
+ {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"},
+ {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"},
+ {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"},
+ {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"},
+ {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"},
+ {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"},
+ {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"},
+ {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"},
+ {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"},
+ {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"},
+]
[package.dependencies]
cffi = ">=1.12"
@@ -127,6 +348,10 @@ description = "Decorators for Humans"
category = "dev"
optional = false
python-versions = ">=3.5"
+files = [
+ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
+ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
+]
[[package]]
name = "dnspython"
@@ -135,6 +360,10 @@ description = "DNS toolkit"
category = "main"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "dnspython-2.5.0-py3-none-any.whl", hash = "sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6"},
+ {file = "dnspython-2.5.0.tar.gz", hash = "sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15"},
+]
[package.extras]
dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=5.0.3)", "mypy (>=1.0.1)", "pylint (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "sphinx (>=7.0.0)", "twine (>=4.0.0)", "wheel (>=0.41.0)"]
@@ -145,6 +374,28 @@ idna = ["idna (>=2.1)"]
trio = ["trio (>=0.14)"]
wmi = ["wmi (>=1.5.1)"]
+[[package]]
+name = "dynaconf"
+version = "3.2.4"
+description = "The dynamic configurator for your Python Project"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "dynaconf-3.2.4-py2.py3-none-any.whl", hash = "sha256:858f9806fab2409c4f5442614c2605d4c4071d5e5153b0e7f24a225f27465aed"},
+ {file = "dynaconf-3.2.4.tar.gz", hash = "sha256:2e6adebaa587f4df9241a16a4bec3fda521154d26b15f3258fde753a592831b6"},
+]
+
+[package.extras]
+all = ["configobj", "hvac", "redis", "ruamel.yaml"]
+configobj = ["configobj"]
+ini = ["configobj"]
+redis = ["redis"]
+test = ["configobj", "django", "flake8", "flake8-debugger", "flake8-print", "flake8-todo", "flask (>=0.12)", "hvac (>=1.1.0)", "pep8-naming", "pytest", "pytest-cov", "pytest-mock", "pytest-xdist", "python-dotenv", "radon", "redis", "toml"]
+toml = ["toml"]
+vault = ["hvac"]
+yaml = ["ruamel.yaml"]
+
[[package]]
name = "executing"
version = "2.0.1"
@@ -152,6 +403,10 @@ description = "Get the currently executing AST node of a frame, and other inform
category = "dev"
optional = false
python-versions = ">=3.5"
+files = [
+ {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"},
+ {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"},
+]
[package.extras]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
@@ -163,6 +418,10 @@ description = "the modular source code checker: pep8 pyflakes and co"
category = "dev"
optional = false
python-versions = ">=3.8.1"
+files = [
+ {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"},
+ {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"},
+]
[package.dependencies]
mccabe = ">=0.7.0,<0.8.0"
@@ -176,6 +435,10 @@ description = "Copy your docs directly to the gh-pages branch."
category = "dev"
optional = false
python-versions = "*"
+files = [
+ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
+ {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
+]
[package.dependencies]
python-dateutil = ">=2.8.1"
@@ -190,6 +453,10 @@ description = "Internationalized Domain Names in Applications (IDNA)"
category = "dev"
optional = false
python-versions = ">=3.5"
+files = [
+ {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
+ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
+]
[[package]]
name = "iniconfig"
@@ -198,6 +465,10 @@ description = "brain-dead simple config-ini parsing"
category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+]
[[package]]
name = "ipdb"
@@ -206,6 +477,10 @@ description = "IPython-enabled pdb"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"},
+ {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"},
+]
[package.dependencies]
decorator = {version = "*", markers = "python_version >= \"3.11\""}
@@ -218,6 +493,10 @@ description = "IPython: Productive Interactive Computing"
category = "dev"
optional = false
python-versions = ">=3.10"
+files = [
+ {file = "ipython-8.20.0-py3-none-any.whl", hash = "sha256:bc9716aad6f29f36c449e30821c9dd0c1c1a7b59ddcc26931685b87b4c569619"},
+ {file = "ipython-8.20.0.tar.gz", hash = "sha256:2f21bd3fc1d51550c89ee3944ae04bbc7bc79e129ea0937da6e6c68bfdbf117a"},
+]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
@@ -250,6 +529,10 @@ description = "An autocompletion tool for Python that can be used for text edito
category = "dev"
optional = false
python-versions = ">=3.6"
+files = [
+ {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"},
+ {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"},
+]
[package.dependencies]
parso = ">=0.8.3,<0.9.0"
@@ -266,6 +549,10 @@ description = "A very fast and expressive template engine."
category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
+ {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
+]
[package.dependencies]
MarkupSafe = ">=2.0"
@@ -280,6 +567,10 @@ description = "Python implementation of John Gruber's Markdown."
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "Markdown-3.5.2-py3-none-any.whl", hash = "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd"},
+ {file = "Markdown-3.5.2.tar.gz", hash = "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8"},
+]
[package.extras]
docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
@@ -292,8 +583,12 @@ description = "Python port of markdown-it. Markdown parsing, done right!"
category = "main"
optional = false
python-versions = ">=3.8"
-
-[package.dependencies]
+files = [
+ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
+ {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
+]
+
+[package.dependencies]
mdurl = ">=0.1,<1.0"
[package.extras]
@@ -313,6 +608,68 @@ description = "Safely add untrusted strings to HTML/XML markup."
category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"},
+ {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"},
+ {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"},
+ {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"},
+ {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"},
+ {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"},
+ {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"},
+ {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"},
+ {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"},
+ {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"},
+ {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"},
+ {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"},
+ {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"},
+ {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"},
+ {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"},
+ {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"},
+ {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"},
+ {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"},
+ {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"},
+ {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"},
+ {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"},
+ {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"},
+ {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"},
+ {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"},
+ {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"},
+ {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"},
+ {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"},
+ {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"},
+ {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"},
+ {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"},
+ {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"},
+ {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"},
+ {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"},
+ {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"},
+ {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"},
+ {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"},
+ {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"},
+ {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"},
+ {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"},
+ {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"},
+ {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"},
+ {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"},
+ {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"},
+ {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"},
+ {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"},
+ {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"},
+ {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"},
+ {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"},
+ {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"},
+ {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"},
+ {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"},
+ {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"},
+ {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"},
+ {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"},
+ {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"},
+ {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"},
+ {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"},
+ {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"},
+ {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"},
+ {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"},
+]
[[package]]
name = "matplotlib-inline"
@@ -321,6 +678,10 @@ description = "Inline Matplotlib backend for Jupyter"
category = "dev"
optional = false
python-versions = ">=3.5"
+files = [
+ {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
+ {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
+]
[package.dependencies]
traitlets = "*"
@@ -332,6 +693,10 @@ description = "McCabe checker, plugin for flake8"
category = "dev"
optional = false
python-versions = ">=3.6"
+files = [
+ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
+ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
+]
[[package]]
name = "mdurl"
@@ -340,6 +705,10 @@ description = "Markdown URL utilities"
category = "main"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
+ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
+]
[[package]]
name = "mergedeep"
@@ -348,6 +717,10 @@ description = "A deep merge function for ๐."
category = "dev"
optional = false
python-versions = ">=3.6"
+files = [
+ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
+ {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
+]
[[package]]
name = "mkdocs"
@@ -356,6 +729,10 @@ description = "Project documentation with Markdown."
category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"},
+ {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"},
+]
[package.dependencies]
click = ">=7.0"
@@ -383,6 +760,10 @@ description = "Automatically link across pages in MkDocs."
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "mkdocs_autorefs-0.5.0-py3-none-any.whl", hash = "sha256:7930fcb8ac1249f10e683967aeaddc0af49d90702af111a5e390e8b20b3d97ff"},
+ {file = "mkdocs_autorefs-0.5.0.tar.gz", hash = "sha256:9a5054a94c08d28855cfab967ada10ed5be76e2bfad642302a610b252c3274c0"},
+]
[package.dependencies]
Markdown = ">=3.3"
@@ -390,11 +771,15 @@ mkdocs = ">=1.1"
[[package]]
name = "mkdocs-material"
-version = "9.5.4"
+version = "9.5.5"
description = "Documentation that simply works"
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "mkdocs_material-9.5.5-py3-none-any.whl", hash = "sha256:ac50b2431a79a3b160fdefbba37c9132485f1a69166aba115ad49fafdbbbc5df"},
+ {file = "mkdocs_material-9.5.5.tar.gz", hash = "sha256:4480d9580faf42fed0123d0465502bfc1c0c239ecc9c4d66159cf0459ea1b4ae"},
+]
[package.dependencies]
babel = ">=2.10,<3.0"
@@ -411,7 +796,7 @@ requests = ">=2.26,<3.0"
[package.extras]
git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2,<2.0)"]
-imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=9.4,<10.0)"]
+imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"]
recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"]
[[package]]
@@ -421,6 +806,10 @@ description = "Extension pack for Python Markdown and MkDocs Material."
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"},
+ {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"},
+]
[[package]]
name = "mkdocstrings"
@@ -429,6 +818,10 @@ description = "Automatic documentation from sources, for MkDocs."
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "mkdocstrings-0.24.0-py3-none-any.whl", hash = "sha256:f4908560c10f587326d8f5165d1908817b2e280bbf707607f601c996366a2264"},
+ {file = "mkdocstrings-0.24.0.tar.gz", hash = "sha256:222b1165be41257b494a9d29b14135d2b7ca43f38161d5b10caae03b87bd4f7e"},
+]
[package.dependencies]
click = ">=7.0"
@@ -452,14 +845,22 @@ description = "Type system extensions for programs checked with the mypy type ch
category = "dev"
optional = false
python-versions = ">=3.5"
+files = [
+ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
+ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
+]
[[package]]
name = "packaging"
version = "23.2"
description = "Core utilities for Python packages"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
+ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
+]
[[package]]
name = "paginate"
@@ -468,6 +869,9 @@ description = "Divides large result sets into pages for easier browsing"
category = "dev"
optional = false
python-versions = "*"
+files = [
+ {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"},
+]
[[package]]
name = "pan-os-python"
@@ -476,6 +880,10 @@ description = "Framework for interacting with Palo Alto Networks devices via API
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+files = [
+ {file = "pan_os_python-1.11.0-py2.py3-none-any.whl", hash = "sha256:4f0fb65e2706587cf721b895bbf6b48de55ffd55f5b5139ada9d6565ed7a1616"},
+ {file = "pan_os_python-1.11.0.tar.gz", hash = "sha256:4caac2f100536bdc27df3d4f9ffe9ea74137e372b49ad16d6cf320ce13f96b00"},
+]
[package.dependencies]
pan-python = ">=0.17.0,<0.18.0"
@@ -487,16 +895,25 @@ description = "Multi-tool set for Palo Alto Networks PAN-OS, Panorama, WildFire
category = "main"
optional = false
python-versions = "*"
+files = [
+ {file = "pan-python-0.17.0.tar.gz", hash = "sha256:9c074ea2f69a63996a6fefe8935d60dca61660e14715ac19d257ea9b1c41c6e2"},
+ {file = "pan_python-0.17.0-py2.py3-none-any.whl", hash = "sha256:f4674e40763c46d5933244b3059a57884e4e28205ef6d0f9ce2dc2013e3db010"},
+]
[[package]]
name = "panos-upgrade-assurance"
-version = "0.3.1"
+version = "0.3.2"
description = ""
category = "main"
optional = false
python-versions = ">=3.8,<4.0"
+files = [
+ {file = "panos_upgrade_assurance-0.3.2-py3-none-any.whl", hash = "sha256:74e05748802f96f642b62a0a7238c1c67bd48dfc844edc93e5738ac4fbb0bc4d"},
+ {file = "panos_upgrade_assurance-0.3.2.tar.gz", hash = "sha256:36bd3fc37d817c58ab1f5732274ec11953474a1234b801ab8acb78227ecbd02a"},
+]
[package.dependencies]
+packaging = ">=23.2,<24.0"
pan-os-python = ">=1.8,<2.0"
pan-python = ">=0.17,<0.18"
pyopenssl = ">=23.2,<24.0"
@@ -509,6 +926,10 @@ description = "A Python Parser"
category = "dev"
optional = false
python-versions = ">=3.6"
+files = [
+ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
+ {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
+]
[package.extras]
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
@@ -521,6 +942,10 @@ description = "Utility library for gitignore style pattern matching of file path
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
+ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
+]
[[package]]
name = "pexpect"
@@ -529,6 +954,10 @@ description = "Pexpect allows easy control of interactive console applications."
category = "dev"
optional = false
python-versions = "*"
+files = [
+ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
+ {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
+]
[package.dependencies]
ptyprocess = ">=0.5"
@@ -540,6 +969,10 @@ description = "A small Python package for determining appropriate platform-speci
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"},
+ {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"},
+]
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
@@ -547,11 +980,15 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
[[package]]
name = "pluggy"
-version = "1.3.0"
+version = "1.4.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
+ {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
+]
[package.extras]
dev = ["pre-commit", "tox"]
@@ -564,6 +1001,10 @@ description = "Library for building powerful interactive command lines in Python
category = "dev"
optional = false
python-versions = ">=3.7.0"
+files = [
+ {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"},
+ {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"},
+]
[package.dependencies]
wcwidth = "*"
@@ -575,6 +1016,10 @@ description = "Run a subprocess in a pseudo terminal"
category = "dev"
optional = false
python-versions = "*"
+files = [
+ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
+ {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
+]
[[package]]
name = "pure-eval"
@@ -583,6 +1028,10 @@ description = "Safely evaluate AST nodes without side effects"
category = "dev"
optional = false
python-versions = "*"
+files = [
+ {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
+ {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
+]
[package.extras]
tests = ["pytest"]
@@ -594,6 +1043,10 @@ description = "Python style guide checker"
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"},
+ {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"},
+]
[[package]]
name = "pycparser"
@@ -602,6 +1055,10 @@ description = "C parser in Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
+]
[[package]]
name = "pydantic"
@@ -610,6 +1067,10 @@ description = "Data validation using Python type hints"
category = "main"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"},
+ {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"},
+]
[package.dependencies]
annotated-types = ">=0.4.0"
@@ -626,6 +1087,113 @@ description = ""
category = "main"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"},
+ {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"},
+ {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"},
+ {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"},
+ {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"},
+ {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"},
+ {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"},
+ {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"},
+ {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"},
+ {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"},
+ {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"},
+ {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"},
+ {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"},
+ {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"},
+ {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"},
+ {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"},
+ {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"},
+ {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"},
+ {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"},
+ {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"},
+ {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"},
+ {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"},
+]
[package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
@@ -637,6 +1205,10 @@ description = "passive checker of Python programs"
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"},
+ {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
+]
[[package]]
name = "pygments"
@@ -645,6 +1217,10 @@ description = "Pygments is a syntax highlighting package written in Python."
category = "main"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
+ {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
+]
[package.extras]
plugins = ["importlib-metadata"]
@@ -657,6 +1233,10 @@ description = "Extension pack for Python Markdown."
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "pymdown_extensions-10.7-py3-none-any.whl", hash = "sha256:6ca215bc57bc12bf32b414887a68b810637d039124ed9b2e5bd3325cbb2c050c"},
+ {file = "pymdown_extensions-10.7.tar.gz", hash = "sha256:c0d64d5cf62566f59e6b2b690a4095c931107c250a8c8e1351c1de5f6b036deb"},
+]
[package.dependencies]
markdown = ">=3.5"
@@ -672,8 +1252,12 @@ description = "Python wrapper module around the OpenSSL library"
category = "main"
optional = false
python-versions = ">=3.7"
-
-[package.dependencies]
+files = [
+ {file = "pyOpenSSL-23.3.0-py3-none-any.whl", hash = "sha256:6756834481d9ed5470f4a9393455154bc92fe7a64b7bc6ee2c804e78c52099b2"},
+ {file = "pyOpenSSL-23.3.0.tar.gz", hash = "sha256:6b2cba5cc46e822750ec3e5a81ee12819850b11303630d575e98108a079c2b12"},
+]
+
+[package.dependencies]
cryptography = ">=41.0.5,<42"
[package.extras]
@@ -687,6 +1271,10 @@ description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
+ {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
+]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
@@ -704,6 +1292,10 @@ description = "Extensions to the standard Python datetime module"
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+files = [
+ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
+ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
+]
[package.dependencies]
six = ">=1.5"
@@ -712,9 +1304,51 @@ six = ">=1.5"
name = "pyyaml"
version = "6.0.1"
description = "YAML parser and emitter for Python"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.6"
+files = [
+ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
+ {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
+ {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
+ {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
+ {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
+ {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
+ {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
+ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
+ {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
+ {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
+ {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
+ {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
+ {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
+ {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
+ {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
+ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
+]
[[package]]
name = "pyyaml-env-tag"
@@ -723,6 +1357,10 @@ description = "A custom YAML tag for referencing environment variables in YAML f
category = "dev"
optional = false
python-versions = ">=3.6"
+files = [
+ {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
+ {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
+]
[package.dependencies]
pyyaml = "*"
@@ -734,6 +1372,101 @@ description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"},
+ {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"},
+ {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"},
+ {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"},
+ {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"},
+ {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"},
+ {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"},
+ {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"},
+ {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"},
+ {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"},
+ {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"},
+ {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"},
+ {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"},
+ {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"},
+ {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"},
+ {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"},
+ {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"},
+ {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"},
+ {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"},
+ {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"},
+ {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"},
+ {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"},
+ {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"},
+ {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"},
+ {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"},
+ {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"},
+ {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"},
+ {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"},
+ {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"},
+ {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"},
+ {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"},
+ {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"},
+ {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"},
+ {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"},
+ {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"},
+ {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"},
+ {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"},
+ {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"},
+ {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"},
+ {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"},
+ {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"},
+ {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"},
+ {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"},
+ {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"},
+ {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"},
+ {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"},
+ {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"},
+ {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"},
+ {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"},
+ {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"},
+ {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"},
+ {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"},
+ {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"},
+ {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"},
+ {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"},
+ {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"},
+ {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"},
+ {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"},
+ {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"},
+ {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"},
+ {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"},
+ {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"},
+ {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"},
+ {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"},
+ {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"},
+ {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"},
+ {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"},
+ {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"},
+ {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"},
+ {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"},
+ {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"},
+ {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"},
+ {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"},
+ {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"},
+ {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"},
+ {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"},
+ {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"},
+ {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"},
+ {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"},
+ {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"},
+ {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"},
+ {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"},
+ {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"},
+ {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"},
+ {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"},
+ {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"},
+ {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"},
+ {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"},
+ {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"},
+ {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"},
+ {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"},
+ {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"},
+ {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"},
+]
[[package]]
name = "requests"
@@ -742,6 +1475,10 @@ description = "Python HTTP for Humans."
category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
+ {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
+]
[package.dependencies]
certifi = ">=2017.4.17"
@@ -760,6 +1497,10 @@ description = "Render rich text, tables, progress bars, syntax highlighting, mar
category = "main"
optional = false
python-versions = ">=3.7.0"
+files = [
+ {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"},
+ {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"},
+]
[package.dependencies]
markdown-it-py = ">=2.2.0"
@@ -775,6 +1516,10 @@ description = "Easily download, build, install, upgrade, and uninstall Python pa
category = "main"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"},
+ {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"},
+]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
@@ -788,6 +1533,10 @@ description = "Tool to Detect Surrounding Shell"
category = "main"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
+ {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
+]
[[package]]
name = "six"
@@ -796,6 +1545,10 @@ description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
[[package]]
name = "stack-data"
@@ -804,6 +1557,10 @@ description = "Extract data from python stack frames and tracebacks for informat
category = "dev"
optional = false
python-versions = "*"
+files = [
+ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
+ {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
+]
[package.dependencies]
asttokens = ">=2.1.0"
@@ -820,6 +1577,10 @@ description = "Traitlets Python configuration system"
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"},
+ {file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"},
+]
[package.extras]
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
@@ -832,6 +1593,10 @@ description = "Typer, build great CLIs. Easy to code. Based on Python type hints
category = "main"
optional = false
python-versions = ">=3.6"
+files = [
+ {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"},
+ {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"},
+]
[package.dependencies]
click = ">=7.1.1,<9.0.0"
@@ -853,6 +1618,10 @@ description = "Backported and Experimental Type Hints for Python 3.8+"
category = "main"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
+ {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
+]
[[package]]
name = "urllib3"
@@ -861,6 +1630,10 @@ description = "HTTP library with thread-safe connection pooling, file post, and
category = "dev"
optional = false
python-versions = ">=3.8"
+files = [
+ {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"},
+ {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"},
+]
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
@@ -874,6 +1647,35 @@ description = "Filesystem events monitoring"
category = "dev"
optional = false
python-versions = ">=3.7"
+files = [
+ {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"},
+ {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"},
+ {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"},
+ {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"},
+ {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"},
+ {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"},
+ {file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"},
+ {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"},
+ {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"},
+ {file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"},
+ {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"},
+ {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"},
+ {file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"},
+ {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"},
+ {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"},
+ {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"},
+ {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"},
+ {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"},
+ {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"},
+ {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"},
+ {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"},
+]
[package.extras]
watchmedo = ["PyYAML (>=3.10)"]
@@ -885,6 +1687,10 @@ description = "Measures the displayed width of unicode strings in a terminal"
category = "dev"
optional = false
python-versions = "*"
+files = [
+ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
+ {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
+]
[[package]]
name = "xmltodict"
@@ -893,795 +1699,12 @@ description = "Makes working with XML feel like you are working with JSON"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "xmltodict-0.12.0-py2.py3-none-any.whl", hash = "sha256:8bbcb45cc982f48b2ca8fe7e7827c5d792f217ecf1792626f808bf41c3b86051"},
+ {file = "xmltodict-0.12.0.tar.gz", hash = "sha256:50d8c638ed7ecb88d90561beedbf720c9b4e851a9fa6c47ebd64e99d166d8a21"},
+]
[metadata]
-lock-version = "1.1"
+lock-version = "2.0"
python-versions = "^3.11"
-content-hash = "997a48e05bab3c4a8f8914ae9ac95e2b7788ff83479e73f239c765da30efc550"
-
-[metadata.files]
-annotated-types = [
- {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
- {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
-]
-asttokens = [
- {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
- {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
-]
-babel = [
- {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"},
- {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"},
-]
-black = [
- {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
- {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
- {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"},
- {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"},
- {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
- {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
- {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
- {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
- {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
- {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"},
- {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"},
- {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"},
- {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"},
- {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"},
- {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"},
- {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"},
- {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"},
- {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"},
- {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"},
- {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"},
- {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
- {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
-]
-certifi = [
- {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"},
- {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
-]
-cffi = [
- {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
- {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
- {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
- {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
- {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
- {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
- {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
- {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
- {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
- {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
- {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
- {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
- {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
- {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
- {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
- {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
- {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
- {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
- {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
- {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
- {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
- {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
- {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
- {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
- {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
- {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
- {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
- {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
- {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
- {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
- {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
- {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
- {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
- {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
- {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
- {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
- {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
- {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
- {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
- {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
- {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
- {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
- {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
- {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
- {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
- {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
- {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
- {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
- {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
- {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
- {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
- {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
-]
-charset-normalizer = [
- {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
- {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
- {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
- {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
- {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
- {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
- {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
- {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
-]
-click = [
- {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
- {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
-]
-colorama = [
- {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
- {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
-]
-cryptography = [
- {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"},
- {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"},
- {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"},
- {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"},
- {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"},
- {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"},
- {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"},
- {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"},
- {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"},
- {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"},
- {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"},
- {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"},
- {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"},
- {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"},
- {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"},
- {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"},
- {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"},
- {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"},
- {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"},
- {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"},
- {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"},
- {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"},
- {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"},
-]
-decorator = [
- {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
- {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
-]
-dnspython = [
- {file = "dnspython-2.5.0-py3-none-any.whl", hash = "sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6"},
- {file = "dnspython-2.5.0.tar.gz", hash = "sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15"},
-]
-executing = [
- {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"},
- {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"},
-]
-flake8 = [
- {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"},
- {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"},
-]
-ghp-import = [
- {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
- {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
-]
-idna = [
- {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
- {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
-]
-iniconfig = [
- {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
- {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
-]
-ipdb = [
- {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"},
- {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"},
-]
-ipython = [
- {file = "ipython-8.20.0-py3-none-any.whl", hash = "sha256:bc9716aad6f29f36c449e30821c9dd0c1c1a7b59ddcc26931685b87b4c569619"},
- {file = "ipython-8.20.0.tar.gz", hash = "sha256:2f21bd3fc1d51550c89ee3944ae04bbc7bc79e129ea0937da6e6c68bfdbf117a"},
-]
-jedi = [
- {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"},
- {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"},
-]
-jinja2 = [
- {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
- {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
-]
-markdown = [
- {file = "Markdown-3.5.2-py3-none-any.whl", hash = "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd"},
- {file = "Markdown-3.5.2.tar.gz", hash = "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8"},
-]
-markdown-it-py = [
- {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
- {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
-]
-markupsafe = [
- {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"},
- {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"},
- {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"},
- {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"},
- {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"},
- {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"},
- {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"},
- {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"},
- {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"},
- {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"},
- {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"},
- {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"},
- {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"},
- {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"},
- {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"},
- {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"},
- {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"},
- {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"},
- {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"},
- {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"},
- {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"},
- {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"},
- {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"},
- {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"},
- {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"},
- {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"},
- {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"},
- {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"},
- {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"},
- {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"},
- {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"},
-]
-matplotlib-inline = [
- {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
- {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
-]
-mccabe = [
- {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
- {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
-]
-mdurl = [
- {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
- {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
-]
-mergedeep = [
- {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
- {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
-]
-mkdocs = [
- {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"},
- {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"},
-]
-mkdocs-autorefs = [
- {file = "mkdocs_autorefs-0.5.0-py3-none-any.whl", hash = "sha256:7930fcb8ac1249f10e683967aeaddc0af49d90702af111a5e390e8b20b3d97ff"},
- {file = "mkdocs_autorefs-0.5.0.tar.gz", hash = "sha256:9a5054a94c08d28855cfab967ada10ed5be76e2bfad642302a610b252c3274c0"},
-]
-mkdocs-material = [
- {file = "mkdocs_material-9.5.4-py3-none-any.whl", hash = "sha256:efd7cc8ae03296d728da9bd38f4db8b07ab61f9738a0cbd0dfaf2a15a50e7343"},
- {file = "mkdocs_material-9.5.4.tar.gz", hash = "sha256:3d196ee67fad16b2df1a458d650a8ac1890294eaae368d26cee71bc24ad41c40"},
-]
-mkdocs-material-extensions = [
- {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"},
- {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"},
-]
-mkdocstrings = [
- {file = "mkdocstrings-0.24.0-py3-none-any.whl", hash = "sha256:f4908560c10f587326d8f5165d1908817b2e280bbf707607f601c996366a2264"},
- {file = "mkdocstrings-0.24.0.tar.gz", hash = "sha256:222b1165be41257b494a9d29b14135d2b7ca43f38161d5b10caae03b87bd4f7e"},
-]
-mypy-extensions = [
- {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
- {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
-]
-packaging = [
- {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
- {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
-]
-paginate = [
- {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"},
-]
-pan-os-python = [
- {file = "pan_os_python-1.11.0-py2.py3-none-any.whl", hash = "sha256:4f0fb65e2706587cf721b895bbf6b48de55ffd55f5b5139ada9d6565ed7a1616"},
- {file = "pan_os_python-1.11.0.tar.gz", hash = "sha256:4caac2f100536bdc27df3d4f9ffe9ea74137e372b49ad16d6cf320ce13f96b00"},
-]
-pan-python = [
- {file = "pan-python-0.17.0.tar.gz", hash = "sha256:9c074ea2f69a63996a6fefe8935d60dca61660e14715ac19d257ea9b1c41c6e2"},
- {file = "pan_python-0.17.0-py2.py3-none-any.whl", hash = "sha256:f4674e40763c46d5933244b3059a57884e4e28205ef6d0f9ce2dc2013e3db010"},
-]
-panos-upgrade-assurance = [
- {file = "panos_upgrade_assurance-0.3.1-py3-none-any.whl", hash = "sha256:b6093ba1ab70d4d01b5df5303eafcbdda9b67db90708f8d9ea7c19da97cbd108"},
- {file = "panos_upgrade_assurance-0.3.1.tar.gz", hash = "sha256:ea60770959f910622952d7ecfadc6a5e2d665c5cb974294c35affb2095c629e8"},
-]
-parso = [
- {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
- {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
-]
-pathspec = [
- {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
- {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
-]
-pexpect = [
- {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
- {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
-]
-platformdirs = [
- {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"},
- {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"},
-]
-pluggy = [
- {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
- {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
-]
-prompt-toolkit = [
- {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"},
- {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"},
-]
-ptyprocess = [
- {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
- {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
-]
-pure-eval = [
- {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
- {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
-]
-pycodestyle = [
- {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"},
- {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"},
-]
-pycparser = [
- {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
- {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
-]
-pydantic = [
- {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"},
- {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"},
-]
-pydantic-core = [
- {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"},
- {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"},
- {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"},
- {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"},
- {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"},
- {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"},
- {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"},
- {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"},
- {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"},
- {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"},
- {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"},
- {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"},
- {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"},
- {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"},
- {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"},
- {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"},
- {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"},
- {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"},
- {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"},
- {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"},
- {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"},
- {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"},
- {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"},
- {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"},
- {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"},
- {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"},
- {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"},
- {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"},
- {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"},
- {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"},
- {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"},
- {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"},
- {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"},
- {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"},
- {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"},
- {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"},
- {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"},
- {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"},
- {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"},
- {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"},
- {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"},
- {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"},
- {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"},
- {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"},
- {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"},
- {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"},
- {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"},
- {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"},
- {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"},
- {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"},
- {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"},
- {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"},
- {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"},
- {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"},
- {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"},
- {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"},
- {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"},
- {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"},
- {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"},
- {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"},
- {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"},
- {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"},
- {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"},
- {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"},
- {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"},
- {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"},
- {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"},
- {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"},
- {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"},
- {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"},
- {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"},
- {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"},
- {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"},
- {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"},
- {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"},
- {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"},
- {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"},
- {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"},
- {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"},
- {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"},
- {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"},
- {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"},
- {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"},
- {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"},
- {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"},
- {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"},
- {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"},
- {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"},
- {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"},
- {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"},
- {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"},
- {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"},
- {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"},
- {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"},
- {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"},
- {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"},
- {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"},
- {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"},
- {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"},
- {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"},
- {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"},
- {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"},
- {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"},
- {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"},
- {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"},
-]
-pyflakes = [
- {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"},
- {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
-]
-pygments = [
- {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
- {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
-]
-pymdown-extensions = [
- {file = "pymdown_extensions-10.7-py3-none-any.whl", hash = "sha256:6ca215bc57bc12bf32b414887a68b810637d039124ed9b2e5bd3325cbb2c050c"},
- {file = "pymdown_extensions-10.7.tar.gz", hash = "sha256:c0d64d5cf62566f59e6b2b690a4095c931107c250a8c8e1351c1de5f6b036deb"},
-]
-pyopenssl = [
- {file = "pyOpenSSL-23.3.0-py3-none-any.whl", hash = "sha256:6756834481d9ed5470f4a9393455154bc92fe7a64b7bc6ee2c804e78c52099b2"},
- {file = "pyOpenSSL-23.3.0.tar.gz", hash = "sha256:6b2cba5cc46e822750ec3e5a81ee12819850b11303630d575e98108a079c2b12"},
-]
-pytest = [
- {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
- {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
-]
-python-dateutil = [
- {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
- {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
-]
-pyyaml = [
- {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
- {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
- {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
- {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
- {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
- {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
- {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
- {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
- {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
- {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
- {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
- {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
- {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
- {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
- {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
- {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
- {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
- {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
- {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
- {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
- {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
- {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
- {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
- {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
- {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
- {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
- {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
- {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
- {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
- {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
- {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
- {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
- {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
- {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
- {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
- {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
- {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
- {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
- {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
- {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
-]
-pyyaml-env-tag = [
- {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
- {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
-]
-regex = [
- {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"},
- {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"},
- {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"},
- {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"},
- {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"},
- {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"},
- {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"},
- {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"},
- {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"},
- {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"},
- {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"},
- {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"},
- {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"},
- {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"},
- {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"},
- {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"},
- {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"},
- {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"},
- {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"},
- {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"},
- {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"},
- {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"},
- {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"},
- {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"},
- {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"},
- {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"},
- {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"},
- {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"},
- {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"},
- {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"},
- {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"},
- {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"},
- {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"},
- {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"},
- {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"},
- {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"},
- {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"},
- {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"},
- {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"},
- {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"},
- {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"},
- {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"},
- {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"},
- {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"},
- {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"},
- {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"},
- {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"},
- {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"},
- {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"},
- {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"},
- {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"},
- {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"},
- {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"},
- {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"},
- {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"},
- {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"},
- {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"},
- {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"},
- {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"},
- {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"},
- {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"},
- {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"},
- {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"},
- {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"},
- {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"},
- {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"},
- {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"},
- {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"},
- {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"},
- {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"},
- {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"},
- {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"},
- {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"},
- {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"},
- {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"},
- {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"},
- {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"},
- {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"},
- {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"},
- {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"},
- {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"},
- {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"},
- {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"},
- {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"},
- {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"},
- {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"},
- {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"},
- {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"},
- {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"},
- {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"},
- {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"},
- {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"},
- {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"},
-]
-requests = [
- {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
- {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
-]
-rich = [
- {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"},
- {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"},
-]
-setuptools = [
- {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"},
- {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"},
-]
-shellingham = [
- {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
- {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
-]
-six = [
- {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
- {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
-]
-stack-data = [
- {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
- {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
-]
-traitlets = [
- {file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"},
- {file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"},
-]
-typer = [
- {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"},
- {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"},
-]
-typing-extensions = [
- {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
- {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
-]
-urllib3 = [
- {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"},
- {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"},
-]
-watchdog = [
- {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"},
- {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"},
- {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"},
- {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"},
- {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"},
- {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"},
- {file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"},
- {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"},
- {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"},
- {file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"},
- {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"},
- {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"},
- {file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"},
- {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"},
- {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"},
- {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"},
- {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"},
- {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"},
- {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"},
- {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"},
- {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"},
- {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"},
- {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"},
- {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"},
- {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"},
- {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"},
- {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"},
-]
-wcwidth = [
- {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
- {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
-]
-xmltodict = [
- {file = "xmltodict-0.12.0-py2.py3-none-any.whl", hash = "sha256:8bbcb45cc982f48b2ca8fe7e7827c5d792f217ecf1792626f808bf41c3b86051"},
- {file = "xmltodict-0.12.0.tar.gz", hash = "sha256:50d8c638ed7ecb88d90561beedbf720c9b4e851a9fa6c47ebd64e99d166d8a21"},
-]
+content-hash = "a9ea781179eecd4542fa8e16d3d3f559e9126ba2ec241101d7f6452e81ab8a46"
diff --git a/pyproject.toml b/pyproject.toml
index 3401ab5..8e7d0d3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pan-os-upgrade"
-version = "0.4.1"
+version = "0.4.2"
description = "Python script to automate the upgrade process of PAN-OS firewalls."
authors = ["Calvin Remsburg "]
license = "Apache 2.0"
@@ -15,6 +15,8 @@ pydantic = "^2.5.3"
typer = { extras = ["all"], version = "^0.9.0" }
setuptools = "^69.0.3"
dnspython = "^2.5.0"
+dynaconf = "^3.2.4"
+pyyaml = "^6.0.1"
[tool.poetry.group.dev.dependencies]
black = "^23.12.1"