├── .gitignore ├── riseup_vpn_configurator ├── riseup-vpn.yaml └── __init__.py ├── release.sh ├── .github └── workflows │ └── tests.yaml ├── pyproject.toml ├── monitoring └── monitor_riseupvpn.py ├── README.md ├── tests └── test_riseupvpn.py ├── LICENSE └── poetry.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .mypy_cache 2 | __pycache__ 3 | *.bak 4 | .coverage 5 | dist 6 | venv 7 | -------------------------------------------------------------------------------- /riseup_vpn_configurator/riseup-vpn.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # /etc/riseup-vpn.yaml 3 | 4 | server: vpn07-par.riseup.net 5 | protocol: udp 6 | port: 53 7 | 8 | # excluded_routes: list servcies that should not be routed over VPN 9 | # can be an ipaddress, network or hostname 10 | # your local subnet is excluded by default 11 | excluded_routes: 12 | - 8.8.8.8 13 | - 192.168.123.0/24 14 | - us02web.zoom.us 15 | 16 | # os user/group 17 | user: openvpn 18 | group: openvpn 19 | 20 | # add custom config 21 | extra_config: | 22 | # emtpy extra_config 23 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | if [ -n "$(git status --untracked-files=no --porcelain)" ]; then 5 | echo "Working directory is not clean. Please commit all changes first" 6 | exit 1 7 | fi 8 | 9 | # install dev environment 10 | poetry install 11 | 12 | # run tests 13 | # run by git hooks 14 | poetry run flake8 --ignore=E501 --show-source --statistics 15 | poetry run mypy riseup_vpn_configurator 16 | sudo poetry run pytest -v -s tests 17 | 18 | # update version 19 | poetry version minor 20 | 21 | # update git version 22 | git add pyproject.toml 23 | git commit -m "Bump to v$(poetry version --short)" --no-verify 24 | git push 25 | git tag -m "Bump to v$(poetry version --short)" "v$(poetry version --short)" 26 | git push --tags 27 | 28 | # build package 29 | poetry build 30 | poetry publish 31 | 32 | rm -rf dist/ 33 | 34 | echo "done" 35 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: run tests for riseup-vpn-configurator 2 | 3 | on: 4 | push: 5 | schedule: 6 | - cron: '0 7 * * *' 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Install dependencies 19 | # we need to run everything as root because we make chown and need root for it ... 20 | run: | 21 | sudo useradd openvpn 22 | # poetry 1.1.12 is too old (with --group dev instead of --dev), so wie use pip to install it 23 | sudo pip install poetry 24 | sudo poetry install 25 | - name: Lint with flake8 26 | run: | 27 | sudo poetry run flake8 --ignore=E501 --show-source --statistics 28 | - name: Type check with mypy 29 | run: | 30 | sudo poetry run mypy riseup_vpn_configurator 31 | - name: Test with pytest 32 | run: | 33 | sudo poetry run pytest -v -s tests 34 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "riseup-vpn-configurator" 3 | version = "1.3.0" 4 | description = "a simple command line tool to get RiseupVPN up and running" 5 | authors = ["kmille "] 6 | readme = "README.md" 7 | packages = [{include = "riseup_vpn_configurator"}] 8 | license = "GPL-3.0-only" 9 | repository = "https://github.com/kmille/riseup-vpn-configurator" 10 | homepage = "https://github.com/kmille/riseup-vpn-configurator" 11 | 12 | 13 | [tool.poetry.dependencies] 14 | python = "^3.10" 15 | jinja2 = "^3.1.2" 16 | requests = "^2.28.2" 17 | pyyaml = "^6.0" 18 | pyasn1 = "^0.6.1" 19 | pyasn1-modules = "^0.4.1" 20 | psutil = "^6.0.0" 21 | icmplib = "^3.0.4" 22 | 23 | 24 | [tool.poetry.group.dev.dependencies] 25 | pdbpp = "^0.10.3" 26 | pytest = "^8.3.2" 27 | coverage = "^7.1.0" 28 | flake8 = "^7.1.1" 29 | 30 | 31 | [tool.poetry.group.mypy.dependencies] 32 | mypy = "^1.0.0" 33 | types-pyyaml = "^6.0.12.6" 34 | types-requests = "^2.28.11.12" 35 | 36 | [build-system] 37 | requires = ["poetry-core"] 38 | build-backend = "poetry.core.masonry.api" 39 | 40 | [tool.poetry.scripts] 41 | riseup-vpn-configurator = 'riseup_vpn_configurator.__init__:main' 42 | 43 | [tool.setuptools.package-data] 44 | riseup_vpn_configurator = ["riseup_vpn_configurator/riseup-vpn.yaml"] 45 | 46 | [tool.mypy] 47 | ignore_missing_imports = true 48 | -------------------------------------------------------------------------------- /monitoring/monitor_riseupvpn.py: -------------------------------------------------------------------------------- 1 | import netifaces 2 | from icmplib import ping, ICMPLibError 3 | 4 | """ 5 | checks: 6 | - is there a VPN interface tun0? 7 | - is the default gateway on tun0? 8 | - can I ping the gateway of tun0? 9 | 10 | requires: 11 | sudo pacman -S python-netifaces python-icmplib 12 | sudo apt-get install python3-netifaces python3-icmplib 13 | """ 14 | 15 | 16 | class Py3status: 17 | VPN_INTERFACE = "tun0" 18 | 19 | def monitor_host(self): 20 | state_fail = { 21 | 'full_text': "VPN: fail", 22 | 'color': self.py3.COLOR_BAD, 23 | 'cached_until': self.py3.time_in(seconds=10) 24 | } 25 | state_succeed = { 26 | 'full_text': "VPN: OK", 27 | 'color': self.py3.COLOR_GOOD, 28 | 'cached_until': self.py3.time_in(seconds=2 * 60) 29 | } 30 | 31 | interfaces = netifaces.interfaces() 32 | if self.VPN_INTERFACE not in interfaces: 33 | self.py3.log(f"VPN interface does not exist ({interfaces})") 34 | return state_fail 35 | 36 | gw_ip, interface = netifaces.gateways()['default'][netifaces.AF_INET] 37 | # self.py3.log(gw_ip, interface) 38 | if interface != self.VPN_INTERFACE: 39 | self.py3.log(f"Default gateway interface is not the VPN interface ({interface})") 40 | return state_fail 41 | 42 | try: 43 | resp = ping(gw_ip, timeout=2, # noqa: F841 44 | count=1, privileged=False) 45 | # self.py3.log(resp) 46 | return state_succeed 47 | except ICMPLibError as e: 48 | self.py3.log(f"Ping failed: {e}") 49 | return state_fail 50 | 51 | 52 | if __name__ == "__main__": 53 | """ 54 | Run module in test mode. 55 | """ 56 | from py3status.module_test import module_test 57 | module_test(Py3status) 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![tests](https://github.com/kmille/riseup-vpn-configurator/actions/workflows/tests.yaml/badge.svg?branch=main)](https://github.com/kmille/riseup-vpn-configurator/actions/workflows/tests.yaml) 2 | ![Python 3.6](https://img.shields.io/badge/python-%3E=3.5-blue.svg) 3 | ![license](https://img.shields.io/github/license/kmille/riseup-vpn-configurator?color=green) 4 | ![latest tag](https://img.shields.io/github/v/tag/kmille/riseup-vpn-configurator?sort=semver) 5 | ![pypi-version](https://img.shields.io/pypi/v/riseup-vpn-configurator) 6 | ![pypi-downloads](https://img.shields.io/pypi/dm/riseup-vpn-configurator) 7 | # riseup-vpn-configurator 8 | 9 | > Riseup offers Personal VPN service for censorship circumvention, location anonymization and traffic encryption. To make this possible, it [sends all your internet traffic through an encrypted connection](https://riseup.net/en/vpn/how-vpn-works) to riseup.net, where it then goes out onto the public internet. 10 | > 11 | > Unlike most other VPN providers, Riseup does not log your IP address. 12 | > 13 | > Riseup has a VPN client called **RiseupVPN**. This VPN client is super easy to use! You just install it and run it—no configuration, no account registration. 14 | 15 | There is already a [riseup-vpn](https://aur.archlinux.org/packages/riseup-vpn) package in AUR. But there a few things I don't like: 16 | 17 | - the software is pretty bloated (unnecessary GUI, I got `could not find polkit agent` error messages) 18 | - the autostart feature just put's a file into `~/.config/autostart` which doesn't work with i3 19 | - the VPN does not use the best available crypto/ciphers (TLS1.2 instead of TLS1.3) 20 | - it's not possible to exclude routes from the VPN 21 | 22 | The riseup-vpn-configurator is a simple command line tool that tries to solve these problems. It generates an OpenVPN configuration file that can be used by `systemctl`. 23 | 24 | # Installation 25 | 26 | Please don't install it as user and run it as root, as this makes it very easy for an attacker to escalate privileges. You can install the [Arch Linux AUR package](https://aur.archlinux.org/packages/riseup-vpn-configurator) or use it with `pip install --user riseup-vpn-configurator` as root. Check out the `How to use it` below to get the VPN up and running. You can start RiseupVPN with `systemctl start openvpn-client@riseup` and autostart it with `systemctl enable openvpn-client@riseup`. You can also start, stop and debug the VPN by using the `--start`, `--stop`, `--restart` and `--log` options. Please keep in mind that the client certificate is only valid for 90 and you have to update it manually. 27 | 28 | ```bash 29 | usage: riseup-vpn-configurator [-h] [-v] [--no-check-certificate] [-d] [-u] [--uninstall] [-l] [-b] [-c] [-g] [-s] [--version] 30 | 31 | options: 32 | -h, --help show this help message and exit 33 | -v, --verbose show verbose output 34 | --no-check-certificate 35 | skip ssl certificate check (used by --update to get the config/client private key from the API) 36 | -d, --default-config print default config file risup-vpn.yaml 37 | -u, --update update gateway list and client certificate/key 38 | --uninstall remove all files in /opt/riseup-vpn 39 | -l, --list-gateways show available VPN server 40 | -b, --benchmark use with --list - pings the gateway and shows the latency 41 | -c, --check-config check syntax of /etc/riseup-vpn.yaml. Generates default config 42 | -g, --generate-config 43 | Generate openvpn config (/etc/openvpn/client/riseup.conf) 44 | -s, --status show current state of riseup-vpn 45 | --start starts openvpn service 46 | --stop stops openvpn service 47 | --restart restarts openvpn service 48 | --log show systemd log 49 | --version show version 50 | ``` 51 | 52 | Default config file `/etc/riseup-vpn.yaml` 53 | ```yaml 54 | --- 55 | # /etc/riseup-vpn.yaml 56 | 57 | server: vpn07-par.riseup.net 58 | protocol: udp 59 | port: 53 60 | 61 | # excluded_routes: list servcies that should not be routed over VPN 62 | # can be an ipaddress, network or hostname 63 | # your local subnet is excluded by default 64 | excluded_routes: 65 | - 8.8.8.8 66 | - 192.168.123.0/24 67 | - us02web.zoom.us 68 | 69 | # os user/group 70 | user: openvpn 71 | group: openvpn 72 | 73 | # add custom config 74 | extra_config: | 75 | # emtpy extra_config 76 | ``` 77 | # How to use it 78 | [![asciicast](https://asciinema.org/a/559611.svg)](https://asciinema.org/a/559611) 79 | # Installation (as a dev) 80 | 81 | We need to run the tool with root permissions (for example to write to /etc). Running the tests also need root privileges (because we use chown). Therefore, I recommend running the dev environment also as root user (UPDATE: you can also use `sudo poetry install` and `sudo poetry run riseup-vpn-configurator`). 82 | 83 | ```bash 84 | root@linbox:tmp git clone https://github.com/kmille/riseup-vpn-configurator.git 85 | root@linbox:tmp cd riseup-vpn-configurator 86 | root@linbox:riseup-vpn-configurator poetry install 87 | poetry run python riseup_vpn_configurator/__init__.py --help 88 | root@linbox:riseup-vpn-configurator poetry run pytest -v -s -x --pdb 89 | root@linbox:riseup-vpn-configurator poetry run flake8 --ignore=E501 riseup_vpn_configurator/ 90 | root@linbox:riseup-vpn-configurator poetry run mypy riseup_vpn_configurator/ 91 | ``` 92 | 93 | # How it works 94 | The code for the RiseupVPN Linux client can be found [here](https://0xacab.org/leap/bitmask-vpn). It uses OpenVPN. An API gives you valid OpenVPN cient credentials (certificate + key) for authentication. The client certificate is only valid for 90 days, so you have to run `--update` once in a while. The VPN gateway list and client certificate can be fetched by a public API. 95 | 96 | # Allow for non-root user 97 | ```bash 98 | kmille ALL = NOPASSWD: /usr/bin/riseup-vpn-configurator 99 | ``` 100 | 101 | # Monitoring with py3status 102 | 103 | If you use [py3status](https://github.com/ultrabug/py3status) as i3bar implementation, you can use [monitor_riseupvpn.py](/monitoring/monitor_riseupvpn.py) for monitoring. 104 | 105 | # Known issues 106 | RiseupVPN does not support IPv6. It's routed over the tunnel but then gets blocked. Also, the VPN hangs after suspend ([see Arch Wiki](https://wiki.archlinux.org/title/OpenVPN#Client_daemon_not_reconnecting_after_suspend)). To solve this issue, the AUR package uses [openvpn-reconnect](https://aur.archlinux.org/packages/openvpn-reconnect) as a dependency. The official Linux clients add firewall rules. This client does not touch your firewall. 107 | 108 | # Changelog 109 | v1.0.4: You can specify user/group in the config file. For the tests, use VPN_USER/VPN_GROUP env variables to overwrite the default (openvpn). Fixes [#5](https://github.com/kmille/riseup-vpn-configurator/issues/5) 110 | -------------------------------------------------------------------------------- /tests/test_riseupvpn.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from pathlib import Path 4 | import tempfile 5 | import logging 6 | 7 | import riseup_vpn_configurator 8 | 9 | VPN_USER = os.environ["VPN_USER"] if "VPN_USER" in os.environ else "openvpn" 10 | VPN_GROUP = os.environ["VPN_GROUP"] if "VPN_GROUP" in os.environ else "openvpn" 11 | 12 | 13 | class TestRiseupVPN: 14 | 15 | def check_permissions_of_file(self, file: Path) -> bool: 16 | perm = os.stat(str(file)) 17 | assert perm.st_mode == 0o100600 18 | assert file.owner() == VPN_USER 19 | assert file.group() == VPN_GROUP 20 | return True 21 | 22 | def setup_class(self): 23 | if os.getuid() != 0: 24 | pytest.fail("Tests need to be run as root (for example to run chmod)") 25 | 26 | self.temp_dir = tempfile.TemporaryDirectory() 27 | working_dir = Path(self.temp_dir.name) 28 | 29 | riseup_vpn_configurator.working_dir = Path("/opt/riseup-vpn") 30 | riseup_vpn_configurator.api_ca_cert_file = working_dir / Path("api-ca.pem") 31 | riseup_vpn_configurator.gateway_json = working_dir / Path("gateways.json") 32 | riseup_vpn_configurator.ca_cert_file = working_dir / Path("vpn-ca.pem") 33 | riseup_vpn_configurator.cert_file = working_dir / Path("cert.pem") 34 | riseup_vpn_configurator.key_file = working_dir / Path("key.pem") 35 | riseup_vpn_configurator.ovpn_file = working_dir / Path("riseup.conf") 36 | riseup_vpn_configurator.config_file = working_dir / Path("riseup-vpn.yaml") 37 | # copy default config 38 | default_config = (Path(__file__).parent / "../riseup_vpn_configurator/riseup-vpn.yaml").read_text() 39 | riseup_vpn_configurator.config_file.write_text(default_config) 40 | 41 | def teardown_class(self): 42 | self.temp_dir.cleanup() 43 | 44 | # def test_cache_api_ca_cert(self, caplog): 45 | # from riseup_vpn_configurator import cache_api_ca_cert 46 | # caplog.set_level(logging.INFO) 47 | # 48 | # cache_api_ca_cert() 49 | # assert riseup_vpn_configurator.api_ca_cert_file.exists() 50 | # assert "Sucessfully" in caplog.text 51 | # assert self.check_permissions_of_file(riseup_vpn_configurator.api_ca_cert_file) 52 | # 53 | # api_ca_cert = riseup_vpn_configurator.api_ca_cert_file.read_text() 54 | # assert api_ca_cert.startswith("-----BEGIN CERTIFICATE-----") 55 | # assert api_ca_cert.strip().endswith("-----END CERTIFICATE-----") 56 | # 57 | def test_update_gateways(self, caplog): 58 | import json 59 | from riseup_vpn_configurator import update_gateways 60 | caplog.set_level(logging.INFO) 61 | 62 | update_gateways() 63 | 64 | assert riseup_vpn_configurator.gateway_json.exists() 65 | assert "Sucessfully saved VPN gateway list" in caplog.text 66 | assert self.check_permissions_of_file(riseup_vpn_configurator.gateway_json) 67 | 68 | with riseup_vpn_configurator.gateway_json.open() as f: 69 | j = json.load(f) 70 | assert list(j.keys()) == ['gateways', 'locations', 'openvpn_configuration', 'serial', 'version'] 71 | assert list(j['gateways'][0].keys()) == ['capabilities', 'host', 'ip_address', 'location'] 72 | 73 | def test_update_vpn_ca_certificate(self, caplog): 74 | from riseup_vpn_configurator import update_vpn_ca_certificate 75 | caplog.set_level(logging.INFO) 76 | 77 | update_vpn_ca_certificate() 78 | 79 | assert riseup_vpn_configurator.ca_cert_file.exists() 80 | assert "Sucessfully saved VPN CA" in caplog.text 81 | assert self.check_permissions_of_file(riseup_vpn_configurator.ca_cert_file) 82 | 83 | ca_cert_file = riseup_vpn_configurator.ca_cert_file.read_text() 84 | assert ca_cert_file.startswith("-----BEGIN CERTIFICATE-----") 85 | assert ca_cert_file.strip().endswith("-----END CERTIFICATE-----") 86 | 87 | def test_update_vpn_client_credentials(self, caplog): 88 | from riseup_vpn_configurator import update_vpn_client_credentials # , cache_api_ca_cert 89 | caplog.set_level(logging.INFO) 90 | 91 | # cache_api_ca_cert() 92 | update_vpn_client_credentials() 93 | 94 | # BEGIN CHECK CERT 95 | assert riseup_vpn_configurator.cert_file.exists() 96 | assert "Sucessfully saved VPN client certificate" in caplog.text 97 | assert self.check_permissions_of_file(riseup_vpn_configurator.cert_file) 98 | 99 | ca_cert_file = riseup_vpn_configurator.cert_file.read_text() 100 | assert ca_cert_file.startswith("-----BEGIN CERTIFICATE-----") 101 | assert ca_cert_file.strip().endswith("-----END CERTIFICATE-----") 102 | # END CHECK CERT 103 | 104 | # BEGIN CHECK KEY 105 | assert riseup_vpn_configurator.key_file.exists() 106 | assert "Sucessfully saved VPN client key" in caplog.text 107 | assert self.check_permissions_of_file(riseup_vpn_configurator.key_file) 108 | 109 | key_file = riseup_vpn_configurator.key_file.read_text() 110 | assert key_file.startswith("-----BEGIN RSA PRIVATE KEY-----") 111 | assert key_file.strip().endswith("-----END RSA PRIVATE KEY-----") 112 | # END CHECK KEY 113 | 114 | def test_get_rtt_valid(self): 115 | from riseup_vpn_configurator import get_rtt 116 | rtt = get_rtt("1.1.1.1") 117 | assert type(rtt) is float 118 | 119 | def test_get_rtt_invalid(self): 120 | from riseup_vpn_configurator import get_rtt 121 | rtt = get_rtt("1.1.1.1brokenip") 122 | assert type(rtt) is float 123 | assert rtt == 9000.0 124 | 125 | def test_list_gateways(self, capsys): 126 | from riseup_vpn_configurator import list_gateways, update_gateways 127 | 128 | update_gateways() 129 | list_gateways(False) 130 | captured = capsys.readouterr() 131 | assert "vpn01-sea.riseup.net location=Seattle ip=204.13.164.252 protocols=tcp,udp ports=53,80,1194" in captured.out 132 | 133 | def test_list_gateways_with_benchmark(self, capsys): 134 | from riseup_vpn_configurator import list_gateways, update_gateways 135 | 136 | update_gateways() 137 | list_gateways(True) 138 | captured = capsys.readouterr() 139 | assert "vpn01-sea.riseup.net location=Seattle ip=204.13.164.252 rtt=" in captured.out 140 | assert "protocols=tcp,udp ports=53,80,1194" in captured.out 141 | 142 | def test_generate_configurator_config(self, capsys): 143 | from riseup_vpn_configurator import print_default_config 144 | import yaml 145 | with pytest.raises(SystemExit) as se: 146 | print_default_config(0) 147 | assert se.value.code == 0 148 | captured = capsys.readouterr() 149 | config = yaml.safe_load(captured.out) 150 | assert list(config.keys()) == ['server', 'protocol', 'port', 'excluded_routes', 'user', 'group'] 151 | 152 | def test_generate_vpn_configuration(self, capsys, caplog): 153 | import yaml 154 | from riseup_vpn_configurator import generate_configuration, update_gateways, update_vpn_client_credentials, update_vpn_ca_certificate, print_default_config 155 | 156 | # BEGIN GENERATE CONFIG 157 | with pytest.raises(SystemExit) as se: 158 | print_default_config(0) 159 | assert se.value.code == 0 160 | captured = capsys.readouterr() 161 | config = yaml.safe_load(captured.out) 162 | config['excluded_routes'].append("one.one.one.one") 163 | with riseup_vpn_configurator.config_file.open("w") as f: 164 | yaml.safe_dump(config, f) 165 | # END GENERATE CONFIG 166 | 167 | update_gateways() 168 | update_vpn_client_credentials() 169 | update_vpn_ca_certificate() 170 | 171 | caplog.set_level(logging.DEBUG) 172 | generate_configuration() 173 | assert "Added '8.8.8.8' as an exception" in caplog.text 174 | assert "Resolved 'one.one.one.one' to '1.1.1.1'." in caplog.text 175 | assert "Resolved 'one.one.one.one' to '1.0.0.1'." in caplog.text 176 | assert "Sucessfully saved RiseupVPN configuration" in caplog.text 177 | 178 | vpn_config = riseup_vpn_configurator.ovpn_file.read_text() 179 | assert "route 8.8.8.8 255.255.255.255 net_gateway" in vpn_config 180 | assert "route 1.0.0.1 255.255.255.255 net_gateway" in vpn_config 181 | assert "route 1.1.1.1 255.255.255.255 net_gateway" in vpn_config 182 | assert "route 192.168.123.0 255.255.255.0 net_gateway" in vpn_config 183 | assert "proto udp" in vpn_config 184 | -------------------------------------------------------------------------------- /riseup_vpn_configurator/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import os 4 | import logging 5 | import argparse 6 | import json 7 | import yaml 8 | import subprocess 9 | import pwd 10 | import grp 11 | from jinja2 import Template 12 | from pathlib import Path 13 | import requests 14 | from ipaddress import ip_network 15 | from pyasn1_modules import pem, rfc2459 16 | from pyasn1.codec.der import decoder 17 | import psutil 18 | from icmplib import ping, ICMPLibError 19 | import shutil 20 | import socket 21 | from typing import Optional, NoReturn 22 | 23 | FORMAT = "%(levelname)s: %(message)s" 24 | logging.basicConfig(format=FORMAT, level=logging.INFO) 25 | # logging.getLogger("urllib3").setLevel(logging.WARNING) 26 | 27 | working_dir = Path("/opt/riseup-vpn") 28 | gateway_json = working_dir / Path("gateways.json") 29 | 30 | ca_cert_file = working_dir / Path("vpn-ca.pem") 31 | cert_file = working_dir / Path("cert.pem") 32 | key_file = working_dir / Path("key.pem") 33 | 34 | config_file = Path("/etc/riseup-vpn.yaml") 35 | ovpn_file = Path("/etc/openvpn/client/riseup.conf") 36 | 37 | GATEWAYS_API_URL = "https://api.black.riseup.net/3/config/eip-service.json" 38 | PROVIDER_API_URL = "https://riseup.net/provider.json" 39 | VPN_CA_CERT_URL = "https://black.riseup.net/ca.crt" 40 | VPN_CLIENT_CREDENTIALS_URL = "https://api.black.riseup.net/3/cert" 41 | 42 | VERIFY_SSL_CERTIFICATE = True 43 | 44 | 45 | def get_rtt(ip: str) -> float: 46 | try: 47 | resp = ping(ip, timeout=2, count=5, interval=0.5) 48 | logging.debug(f"Getting rtt for{resp}") 49 | # ping 1.1.1.1brokenip returns 0.0 and does not raise an exception ...? 50 | if resp.avg_rtt == 0.0: 51 | return 9000.0 52 | return resp.avg_rtt 53 | except ICMPLibError as e: 54 | logging.warning(f"Error getting rtt for {ip}: {e}") 55 | return 9000.0 56 | 57 | 58 | # def cache_api_ca_cert() -> None: 59 | # logging.debug("Updating riseup.net API API CA certificate") 60 | # logging.debug(f"Fetching riseup.net VPN metadata from {PROVIDER_API_URL}") 61 | # try: 62 | # resp = requests.get(PROVIDER_API_URL, verify=VERIFY_SSL_CERTIFICATE) 63 | # resp.raise_for_status() 64 | # j = resp.json() 65 | # logging.debug(f"Fetching API CA certificate from {j['ca_cert_uri']}") 66 | # resp = requests.get(j['ca_cert_uri'], verify=VERIFY_SSL_CERTIFICATE) 67 | # resp.raise_for_status() 68 | # api_ca_cert_file.write_text(resp.text) 69 | # except (requests.RequestException, KeyError) as e: 70 | # logging.error(e) 71 | # sys.exit(1) 72 | # fix_file_permissions(api_ca_cert_file) 73 | # logging.info(f"Sucessfully cached API CA certificate to {api_ca_cert_file}") 74 | 75 | 76 | def update_gateways() -> None: 77 | """ 78 | curl https://api.black.riseup.net/1/configs/eip-service.json 79 | """ 80 | logging.info("Updating VPN gateway list") 81 | logging.debug(f"Fetching gateways from {GATEWAYS_API_URL}") 82 | try: 83 | resp = requests.get(GATEWAYS_API_URL) 84 | resp.raise_for_status() 85 | gateway_json.write_text(resp.text) 86 | except requests.RequestException as e: 87 | logging.error(e) 88 | sys.exit(1) 89 | fix_file_permissions(gateway_json) 90 | logging.info(f"Sucessfully saved VPN gateway list to {gateway_json}") 91 | 92 | 93 | def update_vpn_ca_certificate() -> None: 94 | """ 95 | curl https://black.riseup.net/ca.crt 96 | """ 97 | logging.info("Updating VPN CA certificate") 98 | try: 99 | resp = requests.get(VPN_CA_CERT_URL) 100 | resp.raise_for_status() 101 | if "-----BEGIN CERTIFICATE-----" not in resp.text or \ 102 | "-----END CERTIFICATE-----" not in resp.text: 103 | raise ValueError(f"Response is invalid\nURL: {VPN_CA_CERT_URL}\nResponse:\n{resp.text}") 104 | ca_cert_file.write_text(resp.text) 105 | except (requests.RequestException, ValueError) as e: 106 | logging.error(e) 107 | sys.exit(1) 108 | fix_file_permissions(ca_cert_file) 109 | logging.info(f"Sucessfully saved VPN CA certificate to {ca_cert_file}") 110 | 111 | 112 | def update_vpn_client_credentials() -> None: 113 | """ 114 | curl https://black.riseup.net/ca.crt > ca.crt 115 | curl https://api.black.riseup.net/1/cert --cacert ca.crt 116 | """ 117 | logging.info("Updating client certificate/key") 118 | try: 119 | resp = requests.get(VPN_CLIENT_CREDENTIALS_URL) 120 | resp.raise_for_status() 121 | SEPERATOR = "-----BEGIN CERTIFICATE-----" 122 | parts = resp.text.split(SEPERATOR) 123 | key = parts[0].strip() 124 | if "-----BEGIN RSA PRIVATE KEY-----" not in key or \ 125 | "-----END RSA PRIVATE KEY-----" not in key: 126 | raise ValueError(f"Private key could not be found:\n{resp.text}") 127 | 128 | key_file.write_text(key) 129 | fix_file_permissions(key_file) 130 | logging.info(f"Sucessfully saved VPN client key to {key_file}") 131 | 132 | cert = f"{SEPERATOR}{parts[1]}".strip() 133 | if "-----BEGIN CERTIFICATE-----" not in cert or \ 134 | "-----END CERTIFICATE-----" not in cert: 135 | raise ValueError(f"Certificate could not be found:\n{resp.text}") 136 | 137 | cert_file.write_text(cert) 138 | fix_file_permissions(cert_file) 139 | logging.info(f"Sucessfully saved VPN client certificate to {cert_file}") 140 | except (requests.RequestException, ValueError) as e: 141 | logging.error(e) 142 | sys.exit(1) 143 | 144 | 145 | def list_gateways(bench: bool) -> None: 146 | if not gateway_json.exists(): 147 | logging.error(f"Could not find gateway list ({gateway_json}). You can get it with --update") 148 | sys.exit(1) 149 | 150 | with open(gateway_json) as f: 151 | j = json.load(f) 152 | if bench: 153 | logging.info("Listing VPN gateways with rtt (round-trip-time). Plase turn off the VPN before to get proper results.") 154 | for gw in j['gateways']: 155 | gw['rtt'] = get_rtt(gw['ip_address']) 156 | gateways = sorted(j['gateways'], key=lambda gw: gw['rtt']) 157 | else: 158 | gateways = sorted(j['gateways'], key=lambda gw: gw['location']) 159 | 160 | out = "" 161 | for gw in gateways: 162 | out += f"{gw['host']} location={gw['location']:<13} ip={gw['ip_address']:<15} " 163 | if bench: 164 | rtt_formatted = f"{gw['rtt']} ms " 165 | out += f"rtt={rtt_formatted:<11}" 166 | for transport in gw['capabilities']['transport']: 167 | if transport['type'] == "openvpn": 168 | protocols = ",".join(transport['protocols']) 169 | ports = ",".join(transport['ports']) 170 | out += f"protocols={protocols:<7} ports={ports}\n" 171 | print(out.strip()) 172 | 173 | 174 | def get_excluded_routes() -> str: 175 | with open(config_file) as f: 176 | y = yaml.safe_load(f) 177 | out = "" 178 | for host in y['excluded_routes']: 179 | try: 180 | net = ip_network(host, strict=False) 181 | out += f"route {net.network_address} {net.netmask} net_gateway\n" 182 | logging.debug(f"Added '{net.network_address}' as an exception") 183 | except ValueError: 184 | try: 185 | _, _, ip_addresses = socket.gethostbyname_ex(host) 186 | for ip_address in ip_addresses: 187 | logging.debug(f"Resolved '{host}' to '{ip_address}'. Added as an exception") 188 | out += f"route {ip_address} 255.255.255.255 net_gateway\n" 189 | except socket.gaierror as e: 190 | logging.error(f"Error parsing {host} in excluded_routes (not a ipaddress/network or hostname): {e}") 191 | sys.exit(1) 192 | return out.strip() 193 | 194 | 195 | def get_openvpn_user_group() -> tuple[str, str]: 196 | vpn_user = vpn_group = "openvpn" 197 | 198 | with open(config_file) as f: 199 | try: 200 | y = yaml.safe_load(f) 201 | except yaml.scanner.ScannerError as e: 202 | logging.error(f"Could not parse yaml file: {e}") 203 | sys.exit(1) 204 | if "user" in y: 205 | vpn_user = y["user"] 206 | if "group" in y: 207 | vpn_group = y["group"] 208 | return vpn_user, vpn_group 209 | 210 | 211 | def check_config_file() -> None: 212 | logging.debug(f"Checking configuration file {config_file}") 213 | 214 | with open(config_file) as f: 215 | try: 216 | y = yaml.safe_load(f) 217 | except yaml.scanner.ScannerError as e: 218 | logging.error(f"Could not parse yaml file: {e}") 219 | sys.exit(1) 220 | if not y or type(y) is not dict: 221 | logging.error(f"Could not parse config file {config_file}") 222 | print_default_config(1) 223 | 224 | for c in ("server", "protocol", "port", "excluded_routes"): 225 | if c not in y.keys(): 226 | logging.error(f"Error checking configuration file ({config_file}): '{c}' not specified") 227 | sys.exit(1) 228 | 229 | if y["protocol"] not in ("tcp", "udp"): 230 | logging.error(f"Error checking configuration file ({config_file}): 'protocol' must be one of the values tcp|udp (specified was '{y['protocol']}')") 231 | sys.exit(1) 232 | if not str(y["port"]).isnumeric(): 233 | logging.error(f"Error checking configuration file ({config_file}): 'port' must be numeric (specified was '{y['port']}')") 234 | sys.exit(1) 235 | 236 | for host in y['excluded_routes']: 237 | try: 238 | _ = ip_network(host, strict=False) 239 | except ValueError: 240 | try: 241 | socket.gethostbyname(host) 242 | except socket.gaierror as e: 243 | logging.error(f"Error checking configuration file ({config_file}): exclude route '{host}' is not an ip address/network or a valid hostname:: {e}") 244 | sys.exit(1) 245 | logging.info("Configuration file: OK") 246 | 247 | 248 | def get_server_info() -> Optional[dict]: 249 | with open(config_file) as f: 250 | config = yaml.safe_load(f) 251 | with open(gateway_json) as f: 252 | j = json.load(f) 253 | gateways = j['gateways'] 254 | for gw in gateways: 255 | if gw['host'] == config['server']: 256 | return { 257 | 'hostname': gw['host'], 258 | 'ip_address': gw['ip_address'], 259 | 'proto': config['protocol'], 260 | 'port': config['port'], 261 | 'location': gw['location'], 262 | 'extra_config': config.get('extra_config', "") 263 | } 264 | logging.error(f"Gateway '{config['server']}' not found in gateway list. Please check with --list") 265 | sys.exit(1) 266 | 267 | 268 | def generate_configuration() -> None: 269 | def check_file_exists(file: Path) -> None: 270 | if not file.exists(): 271 | logging.error(f"File ({file}) not found. You can get it by using --update") 272 | sys.exit(1) 273 | 274 | check_file_exists(ca_cert_file) 275 | check_file_exists(cert_file) 276 | check_file_exists(key_file) 277 | 278 | ovpn_template = """# reference manual: https://openvpn.net/community-resources/reference-manual-for-openvpn-2-6/ 279 | client 280 | dev tun 281 | 282 | remote {{ server_info['ip_address'] }} {{ server_info['port'] }} # {{ server_info['hostname'] }} in {{ server_info['location'] }} 283 | proto {{ server_info['proto'] }} 284 | verify-x509-name {{ server_info['hostname'].split(".")[0] }} name 285 | 286 | cipher AES-256-GCM 287 | tls-version-min 1.3 288 | 289 | resolv-retry infinite 290 | keepalive 10 60 291 | nobind 292 | verb 3 293 | 294 | #script-security 2 295 | #up /etc/openvpn/update-resolv-conf 296 | #down /etc/openvpn/update-resolv-conf 297 | 298 | remote-cert-tls server 299 | remote-cert-eku "TLS Web Server Authentication" 300 | 301 | # BEGIN EXCLUDE ROUTES 302 | {{ excluded_routes }} 303 | # END EXCLUDE ROUTES 304 | 305 | ca {{ ca_cert_file }} 306 | cert {{ cert_file }} 307 | key {{ key_file }} 308 | 309 | {{ server_info['extra_config'] }}""" 310 | 311 | server_info = get_server_info() 312 | excluded_routes = get_excluded_routes() 313 | t = Template(ovpn_template) 314 | config = t.render(server_info=server_info, 315 | excluded_routes=excluded_routes, 316 | ca_cert_file=ca_cert_file, 317 | cert_file=cert_file, 318 | key_file=key_file) 319 | ovpn_file.write_text(config) 320 | fix_file_permissions(ovpn_file) 321 | logging.info(f"Sucessfully saved RiseupVPN configuration file to {ovpn_file}") 322 | 323 | 324 | def show_status() -> None: 325 | if ca_cert_file.exists(): 326 | logging.info("CA certificate: OK") 327 | else: 328 | logging.warning("CA certificate not found. You can get it with --update") 329 | 330 | if key_file.exists(): 331 | logging.info("Client key: OK") 332 | else: 333 | logging.warning("Client key not found. You can get it with --update") 334 | 335 | if not cert_file.exists(): 336 | logging.warning("Client certificate not found. You can get it with --update") 337 | else: 338 | with open(cert_file) as f: 339 | substrate = pem.readPemFromFile(f) 340 | cert = decoder.decode(substrate, asn1Spec=rfc2459.Certificate())[0] 341 | notBefore = next(cert['tbsCertificate']['validity']['notBefore'].values()).asDateTime 342 | notAfter = next(cert['tbsCertificate']['validity']['notAfter'].values()).asDateTime 343 | logging.info(f"Client certificate is valid from {notBefore.strftime('%d.%m.%Y')} to {notAfter.strftime('%d.%m.%Y')}") 344 | 345 | if gateway_json.exists(): 346 | logging.info("VPN gateway list: OK") 347 | else: 348 | logging.warning("VPN gateway not found. You can get it with --update") 349 | 350 | if ovpn_file.exists(): 351 | logging.info(f"VPN configuration ({ovpn_file}): OK") 352 | else: 353 | logging.warning(f"VPN configuration ({ovpn_file}) not found. You can get it with --generate-config") 354 | 355 | openvpn_found = False 356 | for proc in psutil.process_iter(): 357 | if "openvpn" in proc.name(): 358 | openvpn_found = True 359 | logging.info(f"Found a running openvpn process: '{' '.join(proc.cmdline())}' with pid {proc.pid}") 360 | if not openvpn_found: 361 | logging.warning("No running openvpn process found") 362 | 363 | try: 364 | resp = requests.get("https://api4.ipify.org?format=json", timeout=5) 365 | resp.raise_for_status() 366 | logging.info(f"Your IPv4 address: {resp.json()['ip']}") 367 | except requests.RequestException as e: 368 | logging.warning(f"Error finding your public IPv4 address: {e}") 369 | 370 | logging.debug("Start/Stop Riseup-VPN") 371 | logging.debug("systemctl start openvpn-client@riseup") 372 | logging.debug("systemctl stop openvpn-client@riseup") 373 | logging.debug("Autostart Riseup-VPN") 374 | logging.debug("systemctl enable openvpn-client@riseup") 375 | logging.debug("systemctl disable openvpn-client@riseup") 376 | 377 | 378 | def check_root_permissions() -> None: 379 | if os.getuid() != 0: 380 | logging.error("This scripts needs to be executed with root permission.") 381 | sys.exit(1) 382 | 383 | 384 | def fix_file_permissions(file: Path) -> None: 385 | vpn_user, vpn_group = get_openvpn_user_group() 386 | try: 387 | uid = pwd.getpwnam(vpn_user).pw_uid 388 | gid = grp.getgrnam(vpn_group).gr_gid 389 | except KeyError as e: 390 | logging.error(f"Could not find user/group: {e}. You can adjust user/group in {config_file}") 391 | sys.exit(1) 392 | os.chown(file, uid, gid) 393 | file.chmod(0o600) 394 | 395 | 396 | def print_default_config(return_code: int) -> NoReturn: 397 | config_template = Path(__file__).parent / config_file.name 398 | print(config_template.read_text()) 399 | sys.exit(return_code) 400 | 401 | 402 | def check_working_directory() -> None: 403 | if not working_dir.exists(): 404 | vpn_user, vpn_group = get_openvpn_user_group() 405 | try: 406 | uid = pwd.getpwnam(vpn_user).pw_uid 407 | gid = grp.getgrnam(vpn_group).gr_gid 408 | except KeyError as e: 409 | logging.error(f"Could not find user/group: {e}. You can adjust user/group in {config_file}") 410 | sys.exit(1) 411 | working_dir.mkdir(0o700) 412 | os.chown(working_dir, uid, gid) 413 | 414 | if not config_file.exists(): 415 | logging.error(f"Could not find config file {config_file}. Use --default-config for the default config file") 416 | sys.exit(1) 417 | 418 | 419 | def uninstall() -> NoReturn: 420 | def delete(file: Path) -> None: 421 | try: 422 | if file.resolve().is_file(): 423 | file.unlink() 424 | logging.info(f"Deleted file {file}") 425 | else: 426 | shutil.rmtree(file) 427 | logging.info(f"Deleted directory {file}") 428 | except FileNotFoundError: 429 | pass 430 | 431 | delete(working_dir) 432 | delete(config_file) 433 | delete(ovpn_file) 434 | sys.exit(0) 435 | 436 | 437 | def print_error_log(): 438 | logging.info("Printing debug log") 439 | try: 440 | p = subprocess.run(["journalctl", "-u", "openvpn-client@riseup", "-n", "50"], capture_output=True) 441 | logging.info(p.stdout.decode()) 442 | except subprocess.CalledProcessError as e: 443 | logging.error(f"Could not get logs for riseup vpn: {e}") 444 | 445 | 446 | def start_openvpn(): 447 | try: 448 | subprocess.run(["systemctl", "start", "openvpn-client@riseup"], check=True, capture_output=True) 449 | except subprocess.CalledProcessError as e: 450 | logging.error(f"Could not start riseup vpn: {e}") 451 | print_error_log() 452 | else: 453 | logging.info("riseupvpn sucessfully started") 454 | 455 | 456 | def stop_openvpn(): 457 | try: 458 | subprocess.run(["systemctl", "stop", "openvpn-client@riseup"], check=True, capture_output=True) 459 | except subprocess.CalledProcessError as e: 460 | logging.error(f"Could not stop riseup vpn: {e}") 461 | print_error_log() 462 | else: 463 | logging.info("riseupvpn sucessfully stopped") 464 | 465 | 466 | def show_version(): 467 | from importlib.metadata import version 468 | app_name = "riseup-vpn-configurator" 469 | logging.info(f"Running {app_name} v{version(app_name)}") 470 | sys.exit() 471 | 472 | 473 | def main() -> None: 474 | 475 | parser = argparse.ArgumentParser() 476 | parser.add_argument("-v", "--verbose", action="store_true", help="show verbose output") 477 | parser.add_argument("--no-check-certificate", action="store_true", help="skip ssl certificate check (used by --update to get the config/client private key from the API)") 478 | parser.add_argument("-d", "--default-config", action="store_true", help="print default config file risup-vpn.yaml") 479 | parser.add_argument("-u", "--update", action="store_true", help="update gateway list and client certificate/key") 480 | parser.add_argument("--uninstall", action="store_true", help=f"remove all files in {working_dir}") 481 | parser.add_argument("-l", "--list-gateways", action="store_true", help="show available VPN server") 482 | parser.add_argument("-b", "--benchmark", action="store_true", help="use with --list - pings the gateway and shows the latency") 483 | parser.add_argument("-c", "--check-config", action="store_true", help=f"check syntax of {config_file}. Generates default config") 484 | parser.add_argument("-g", "--generate-config", action="store_true", help=f"Generate openvpn config ({ovpn_file})") 485 | parser.add_argument("-s", "--status", action="store_true", help="show current state of riseup-vpn") 486 | parser.add_argument("--start", action="store_true", help="starts openvpn service") 487 | parser.add_argument("--stop", action="store_true", help="stops openvpn service") 488 | parser.add_argument("--restart", action="store_true", help="restarts openvpn service") 489 | parser.add_argument("--log", action="store_true", help="show systemd log") 490 | parser.add_argument("--version", action="store_true", help="show version") 491 | 492 | args = parser.parse_args() 493 | if len(sys.argv) == 1: 494 | parser.print_help() 495 | sys.exit(1) 496 | 497 | if args.verbose: 498 | logging.getLogger().setLevel(logging.DEBUG) 499 | if args.no_check_certificate: 500 | # deprecated 501 | pass 502 | # global VERIFY_SSL_CERTIFICATE 503 | # VERIFY_SSL_CERTIFICATE = False 504 | 505 | elif args.version: 506 | show_version() 507 | elif args.default_config: 508 | print_default_config(0) 509 | 510 | check_root_permissions() 511 | 512 | if args.uninstall: 513 | uninstall() 514 | 515 | check_working_directory() 516 | 517 | if args.update: 518 | update_gateways() 519 | update_vpn_ca_certificate() 520 | update_vpn_client_credentials() 521 | elif args.check_config: 522 | check_config_file() 523 | elif args.list_gateways: 524 | list_gateways(args.benchmark) 525 | elif args.generate_config: 526 | check_config_file() 527 | generate_configuration() 528 | elif args.status: 529 | check_config_file() 530 | show_status() 531 | elif args.start: 532 | start_openvpn() 533 | elif args.stop: 534 | stop_openvpn() 535 | elif args.restart: 536 | stop_openvpn() 537 | start_openvpn() 538 | elif args.log: 539 | print_error_log() 540 | 541 | 542 | if __name__ == '__main__': 543 | main() 544 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "attrs" 5 | version = "24.2.0" 6 | description = "Classes Without Boilerplate" 7 | optional = false 8 | python-versions = ">=3.7" 9 | files = [ 10 | {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, 11 | {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, 12 | ] 13 | 14 | [package.extras] 15 | benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] 16 | cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] 17 | dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] 18 | docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] 19 | tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] 20 | tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] 21 | 22 | [[package]] 23 | name = "certifi" 24 | version = "2024.8.30" 25 | description = "Python package for providing Mozilla's CA Bundle." 26 | optional = false 27 | python-versions = ">=3.6" 28 | files = [ 29 | {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, 30 | {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, 31 | ] 32 | 33 | [[package]] 34 | name = "charset-normalizer" 35 | version = "3.4.0" 36 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 37 | optional = false 38 | python-versions = ">=3.7.0" 39 | files = [ 40 | {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, 41 | {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, 42 | {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, 43 | {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, 44 | {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, 45 | {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, 46 | {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, 47 | {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, 48 | {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, 49 | {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, 50 | {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, 51 | {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, 52 | {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, 53 | {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, 54 | {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, 55 | {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, 56 | {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, 57 | {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, 58 | {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, 59 | {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, 60 | {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, 61 | {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, 62 | {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, 63 | {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, 64 | {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, 65 | {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, 66 | {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, 67 | {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, 68 | {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, 69 | {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, 70 | {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, 71 | {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, 72 | {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, 73 | {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, 74 | {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, 75 | {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, 76 | {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, 77 | {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, 78 | {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, 79 | {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, 80 | {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, 81 | {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, 82 | {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, 83 | {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, 84 | {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, 85 | {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, 86 | {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, 87 | {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, 88 | {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, 89 | {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, 90 | {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, 91 | {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, 92 | {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, 93 | {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, 94 | {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, 95 | {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, 96 | {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, 97 | {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, 98 | {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, 99 | {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, 100 | {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, 101 | {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, 102 | {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, 103 | {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, 104 | {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, 105 | {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, 106 | {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, 107 | {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, 108 | {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, 109 | {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, 110 | {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, 111 | {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, 112 | {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, 113 | {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, 114 | {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, 115 | {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, 116 | {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, 117 | {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, 118 | {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, 119 | {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, 120 | {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, 121 | {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, 122 | {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, 123 | {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, 124 | {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, 125 | {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, 126 | {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, 127 | {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, 128 | {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, 129 | {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, 130 | {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, 131 | {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, 132 | {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, 133 | {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, 134 | {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, 135 | {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, 136 | {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, 137 | {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, 138 | {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, 139 | {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, 140 | {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, 141 | {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, 142 | {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, 143 | {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, 144 | {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, 145 | ] 146 | 147 | [[package]] 148 | name = "colorama" 149 | version = "0.4.6" 150 | description = "Cross-platform colored terminal text." 151 | optional = false 152 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 153 | files = [ 154 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 155 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 156 | ] 157 | 158 | [[package]] 159 | name = "coverage" 160 | version = "7.6.7" 161 | description = "Code coverage measurement for Python" 162 | optional = false 163 | python-versions = ">=3.9" 164 | files = [ 165 | {file = "coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e"}, 166 | {file = "coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45"}, 167 | {file = "coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1"}, 168 | {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c"}, 169 | {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2"}, 170 | {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06"}, 171 | {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777"}, 172 | {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314"}, 173 | {file = "coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a"}, 174 | {file = "coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163"}, 175 | {file = "coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469"}, 176 | {file = "coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99"}, 177 | {file = "coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec"}, 178 | {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b"}, 179 | {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a"}, 180 | {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b"}, 181 | {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d"}, 182 | {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4"}, 183 | {file = "coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2"}, 184 | {file = "coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f"}, 185 | {file = "coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9"}, 186 | {file = "coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b"}, 187 | {file = "coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c"}, 188 | {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1"}, 189 | {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354"}, 190 | {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433"}, 191 | {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f"}, 192 | {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb"}, 193 | {file = "coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76"}, 194 | {file = "coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c"}, 195 | {file = "coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3"}, 196 | {file = "coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab"}, 197 | {file = "coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808"}, 198 | {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc"}, 199 | {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8"}, 200 | {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a"}, 201 | {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55"}, 202 | {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384"}, 203 | {file = "coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30"}, 204 | {file = "coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42"}, 205 | {file = "coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413"}, 206 | {file = "coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd"}, 207 | {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37"}, 208 | {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b"}, 209 | {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d"}, 210 | {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529"}, 211 | {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b"}, 212 | {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3"}, 213 | {file = "coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8"}, 214 | {file = "coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56"}, 215 | {file = "coverage-7.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874"}, 216 | {file = "coverage-7.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0"}, 217 | {file = "coverage-7.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c"}, 218 | {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cf96ceaa275f071f1bea3067f8fd43bec184a25a962c754024c973af871e1b7"}, 219 | {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee9cf6b0134d6f932d219ce253ef0e624f4fa588ee64830fcba193269e4daa3"}, 220 | {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327"}, 221 | {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c"}, 222 | {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289"}, 223 | {file = "coverage-7.6.7-cp39-cp39-win32.whl", hash = "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c"}, 224 | {file = "coverage-7.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13"}, 225 | {file = "coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671"}, 226 | {file = "coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24"}, 227 | ] 228 | 229 | [package.extras] 230 | toml = ["tomli"] 231 | 232 | [[package]] 233 | name = "exceptiongroup" 234 | version = "1.2.2" 235 | description = "Backport of PEP 654 (exception groups)" 236 | optional = false 237 | python-versions = ">=3.7" 238 | files = [ 239 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 240 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 241 | ] 242 | 243 | [package.extras] 244 | test = ["pytest (>=6)"] 245 | 246 | [[package]] 247 | name = "fancycompleter" 248 | version = "0.9.1" 249 | description = "colorful TAB completion for Python prompt" 250 | optional = false 251 | python-versions = "*" 252 | files = [ 253 | {file = "fancycompleter-0.9.1-py3-none-any.whl", hash = "sha256:dd076bca7d9d524cc7f25ec8f35ef95388ffef9ef46def4d3d25e9b044ad7080"}, 254 | {file = "fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272"}, 255 | ] 256 | 257 | [package.dependencies] 258 | pyreadline = {version = "*", markers = "platform_system == \"Windows\""} 259 | pyrepl = ">=0.8.2" 260 | 261 | [[package]] 262 | name = "flake8" 263 | version = "7.1.1" 264 | description = "the modular source code checker: pep8 pyflakes and co" 265 | optional = false 266 | python-versions = ">=3.8.1" 267 | files = [ 268 | {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, 269 | {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, 270 | ] 271 | 272 | [package.dependencies] 273 | mccabe = ">=0.7.0,<0.8.0" 274 | pycodestyle = ">=2.12.0,<2.13.0" 275 | pyflakes = ">=3.2.0,<3.3.0" 276 | 277 | [[package]] 278 | name = "icmplib" 279 | version = "3.0.4" 280 | description = "Easily forge ICMP packets and make your own ping and traceroute." 281 | optional = false 282 | python-versions = ">=3.7" 283 | files = [ 284 | {file = "icmplib-3.0.4-py3-none-any.whl", hash = "sha256:336b75c6c23c5ce99ddec33f718fab09661f6ad698e35b6f1fc7cc0ecf809398"}, 285 | {file = "icmplib-3.0.4.tar.gz", hash = "sha256:57868f2cdb011418c0e1d5586b16d1fabd206569fe9652654c27b6b2d6a316de"}, 286 | ] 287 | 288 | [[package]] 289 | name = "idna" 290 | version = "3.10" 291 | description = "Internationalized Domain Names in Applications (IDNA)" 292 | optional = false 293 | python-versions = ">=3.6" 294 | files = [ 295 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 296 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 297 | ] 298 | 299 | [package.extras] 300 | all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 301 | 302 | [[package]] 303 | name = "iniconfig" 304 | version = "2.0.0" 305 | description = "brain-dead simple config-ini parsing" 306 | optional = false 307 | python-versions = ">=3.7" 308 | files = [ 309 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 310 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 311 | ] 312 | 313 | [[package]] 314 | name = "jinja2" 315 | version = "3.1.4" 316 | description = "A very fast and expressive template engine." 317 | optional = false 318 | python-versions = ">=3.7" 319 | files = [ 320 | {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, 321 | {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, 322 | ] 323 | 324 | [package.dependencies] 325 | MarkupSafe = ">=2.0" 326 | 327 | [package.extras] 328 | i18n = ["Babel (>=2.7)"] 329 | 330 | [[package]] 331 | name = "markupsafe" 332 | version = "3.0.2" 333 | description = "Safely add untrusted strings to HTML/XML markup." 334 | optional = false 335 | python-versions = ">=3.9" 336 | files = [ 337 | {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, 338 | {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, 339 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, 340 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, 341 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, 342 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, 343 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, 344 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, 345 | {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, 346 | {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, 347 | {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, 348 | {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, 349 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, 350 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, 351 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, 352 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, 353 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, 354 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, 355 | {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, 356 | {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, 357 | {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, 358 | {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, 359 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, 360 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, 361 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, 362 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, 363 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, 364 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, 365 | {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, 366 | {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, 367 | {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, 368 | {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, 369 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, 370 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, 371 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, 372 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, 373 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, 374 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, 375 | {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, 376 | {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, 377 | {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, 378 | {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, 379 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, 380 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, 381 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, 382 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, 383 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, 384 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, 385 | {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, 386 | {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, 387 | {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, 388 | {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, 389 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, 390 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, 391 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, 392 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, 393 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, 394 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, 395 | {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, 396 | {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, 397 | {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, 398 | ] 399 | 400 | [[package]] 401 | name = "mccabe" 402 | version = "0.7.0" 403 | description = "McCabe checker, plugin for flake8" 404 | optional = false 405 | python-versions = ">=3.6" 406 | files = [ 407 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 408 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 409 | ] 410 | 411 | [[package]] 412 | name = "mypy" 413 | version = "1.13.0" 414 | description = "Optional static typing for Python" 415 | optional = false 416 | python-versions = ">=3.8" 417 | files = [ 418 | {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, 419 | {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, 420 | {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, 421 | {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, 422 | {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, 423 | {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, 424 | {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, 425 | {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, 426 | {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, 427 | {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, 428 | {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, 429 | {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, 430 | {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, 431 | {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, 432 | {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, 433 | {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, 434 | {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, 435 | {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, 436 | {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, 437 | {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, 438 | {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, 439 | {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, 440 | {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, 441 | {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, 442 | {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, 443 | {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, 444 | {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, 445 | {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, 446 | {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, 447 | {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, 448 | {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, 449 | {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, 450 | ] 451 | 452 | [package.dependencies] 453 | mypy-extensions = ">=1.0.0" 454 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 455 | typing-extensions = ">=4.6.0" 456 | 457 | [package.extras] 458 | dmypy = ["psutil (>=4.0)"] 459 | faster-cache = ["orjson"] 460 | install-types = ["pip"] 461 | mypyc = ["setuptools (>=50)"] 462 | reports = ["lxml"] 463 | 464 | [[package]] 465 | name = "mypy-extensions" 466 | version = "1.0.0" 467 | description = "Type system extensions for programs checked with the mypy type checker." 468 | optional = false 469 | python-versions = ">=3.5" 470 | files = [ 471 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 472 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 473 | ] 474 | 475 | [[package]] 476 | name = "packaging" 477 | version = "24.2" 478 | description = "Core utilities for Python packages" 479 | optional = false 480 | python-versions = ">=3.8" 481 | files = [ 482 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, 483 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, 484 | ] 485 | 486 | [[package]] 487 | name = "pdbpp" 488 | version = "0.10.3" 489 | description = "pdb++, a drop-in replacement for pdb" 490 | optional = false 491 | python-versions = "*" 492 | files = [ 493 | {file = "pdbpp-0.10.3-py2.py3-none-any.whl", hash = "sha256:79580568e33eb3d6f6b462b1187f53e10cd8e4538f7d31495c9181e2cf9665d1"}, 494 | {file = "pdbpp-0.10.3.tar.gz", hash = "sha256:d9e43f4fda388eeb365f2887f4e7b66ac09dce9b6236b76f63616530e2f669f5"}, 495 | ] 496 | 497 | [package.dependencies] 498 | fancycompleter = ">=0.8" 499 | pygments = "*" 500 | wmctrl = "*" 501 | 502 | [package.extras] 503 | funcsigs = ["funcsigs"] 504 | testing = ["funcsigs", "pytest"] 505 | 506 | [[package]] 507 | name = "pluggy" 508 | version = "1.5.0" 509 | description = "plugin and hook calling mechanisms for python" 510 | optional = false 511 | python-versions = ">=3.8" 512 | files = [ 513 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 514 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 515 | ] 516 | 517 | [package.extras] 518 | dev = ["pre-commit", "tox"] 519 | testing = ["pytest", "pytest-benchmark"] 520 | 521 | [[package]] 522 | name = "psutil" 523 | version = "6.1.0" 524 | description = "Cross-platform lib for process and system monitoring in Python." 525 | optional = false 526 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" 527 | files = [ 528 | {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, 529 | {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, 530 | {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, 531 | {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, 532 | {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, 533 | {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, 534 | {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, 535 | {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, 536 | {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, 537 | {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, 538 | {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, 539 | {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, 540 | {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, 541 | {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, 542 | {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, 543 | {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, 544 | {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, 545 | ] 546 | 547 | [package.extras] 548 | dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] 549 | test = ["pytest", "pytest-xdist", "setuptools"] 550 | 551 | [[package]] 552 | name = "pyasn1" 553 | version = "0.6.1" 554 | description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" 555 | optional = false 556 | python-versions = ">=3.8" 557 | files = [ 558 | {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, 559 | {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, 560 | ] 561 | 562 | [[package]] 563 | name = "pyasn1-modules" 564 | version = "0.4.1" 565 | description = "A collection of ASN.1-based protocols modules" 566 | optional = false 567 | python-versions = ">=3.8" 568 | files = [ 569 | {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, 570 | {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, 571 | ] 572 | 573 | [package.dependencies] 574 | pyasn1 = ">=0.4.6,<0.7.0" 575 | 576 | [[package]] 577 | name = "pycodestyle" 578 | version = "2.12.1" 579 | description = "Python style guide checker" 580 | optional = false 581 | python-versions = ">=3.8" 582 | files = [ 583 | {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, 584 | {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, 585 | ] 586 | 587 | [[package]] 588 | name = "pyflakes" 589 | version = "3.2.0" 590 | description = "passive checker of Python programs" 591 | optional = false 592 | python-versions = ">=3.8" 593 | files = [ 594 | {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, 595 | {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, 596 | ] 597 | 598 | [[package]] 599 | name = "pygments" 600 | version = "2.18.0" 601 | description = "Pygments is a syntax highlighting package written in Python." 602 | optional = false 603 | python-versions = ">=3.8" 604 | files = [ 605 | {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, 606 | {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, 607 | ] 608 | 609 | [package.extras] 610 | windows-terminal = ["colorama (>=0.4.6)"] 611 | 612 | [[package]] 613 | name = "pyreadline" 614 | version = "2.1" 615 | description = "A python implmementation of GNU readline." 616 | optional = false 617 | python-versions = "*" 618 | files = [ 619 | {file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"}, 620 | ] 621 | 622 | [[package]] 623 | name = "pyrepl" 624 | version = "0.9.0" 625 | description = "A library for building flexible command line interfaces" 626 | optional = false 627 | python-versions = "*" 628 | files = [ 629 | {file = "pyrepl-0.9.0.tar.gz", hash = "sha256:292570f34b5502e871bbb966d639474f2b57fbfcd3373c2d6a2f3d56e681a775"}, 630 | ] 631 | 632 | [[package]] 633 | name = "pytest" 634 | version = "8.3.3" 635 | description = "pytest: simple powerful testing with Python" 636 | optional = false 637 | python-versions = ">=3.8" 638 | files = [ 639 | {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, 640 | {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, 641 | ] 642 | 643 | [package.dependencies] 644 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 645 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 646 | iniconfig = "*" 647 | packaging = "*" 648 | pluggy = ">=1.5,<2" 649 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 650 | 651 | [package.extras] 652 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 653 | 654 | [[package]] 655 | name = "pyyaml" 656 | version = "6.0.2" 657 | description = "YAML parser and emitter for Python" 658 | optional = false 659 | python-versions = ">=3.8" 660 | files = [ 661 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, 662 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, 663 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, 664 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, 665 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, 666 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, 667 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, 668 | {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, 669 | {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, 670 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, 671 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, 672 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, 673 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, 674 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, 675 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, 676 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, 677 | {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, 678 | {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, 679 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, 680 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, 681 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, 682 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, 683 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, 684 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, 685 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, 686 | {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, 687 | {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, 688 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, 689 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, 690 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, 691 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, 692 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, 693 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, 694 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, 695 | {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, 696 | {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, 697 | {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, 698 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, 699 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, 700 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, 701 | {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, 702 | {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, 703 | {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, 704 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, 705 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, 706 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, 707 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, 708 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, 709 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, 710 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, 711 | {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, 712 | {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, 713 | {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, 714 | ] 715 | 716 | [[package]] 717 | name = "requests" 718 | version = "2.32.3" 719 | description = "Python HTTP for Humans." 720 | optional = false 721 | python-versions = ">=3.8" 722 | files = [ 723 | {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, 724 | {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, 725 | ] 726 | 727 | [package.dependencies] 728 | certifi = ">=2017.4.17" 729 | charset-normalizer = ">=2,<4" 730 | idna = ">=2.5,<4" 731 | urllib3 = ">=1.21.1,<3" 732 | 733 | [package.extras] 734 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 735 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 736 | 737 | [[package]] 738 | name = "tomli" 739 | version = "2.1.0" 740 | description = "A lil' TOML parser" 741 | optional = false 742 | python-versions = ">=3.8" 743 | files = [ 744 | {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, 745 | {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, 746 | ] 747 | 748 | [[package]] 749 | name = "types-pyyaml" 750 | version = "6.0.12.20240917" 751 | description = "Typing stubs for PyYAML" 752 | optional = false 753 | python-versions = ">=3.8" 754 | files = [ 755 | {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"}, 756 | {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"}, 757 | ] 758 | 759 | [[package]] 760 | name = "types-requests" 761 | version = "2.32.0.20241016" 762 | description = "Typing stubs for requests" 763 | optional = false 764 | python-versions = ">=3.8" 765 | files = [ 766 | {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"}, 767 | {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"}, 768 | ] 769 | 770 | [package.dependencies] 771 | urllib3 = ">=2" 772 | 773 | [[package]] 774 | name = "typing-extensions" 775 | version = "4.12.2" 776 | description = "Backported and Experimental Type Hints for Python 3.8+" 777 | optional = false 778 | python-versions = ">=3.8" 779 | files = [ 780 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 781 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 782 | ] 783 | 784 | [[package]] 785 | name = "urllib3" 786 | version = "2.2.3" 787 | description = "HTTP library with thread-safe connection pooling, file post, and more." 788 | optional = false 789 | python-versions = ">=3.8" 790 | files = [ 791 | {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, 792 | {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, 793 | ] 794 | 795 | [package.extras] 796 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 797 | h2 = ["h2 (>=4,<5)"] 798 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 799 | zstd = ["zstandard (>=0.18.0)"] 800 | 801 | [[package]] 802 | name = "wmctrl" 803 | version = "0.5" 804 | description = "A tool to programmatically control windows inside X" 805 | optional = false 806 | python-versions = ">=2.7" 807 | files = [ 808 | {file = "wmctrl-0.5-py2.py3-none-any.whl", hash = "sha256:ae695c1863a314c899e7cf113f07c0da02a394b968c4772e1936219d9234ddd7"}, 809 | {file = "wmctrl-0.5.tar.gz", hash = "sha256:7839a36b6fe9e2d6fd22304e5dc372dbced2116ba41283ea938b2da57f53e962"}, 810 | ] 811 | 812 | [package.dependencies] 813 | attrs = "*" 814 | 815 | [package.extras] 816 | test = ["pytest"] 817 | 818 | [metadata] 819 | lock-version = "2.0" 820 | python-versions = "^3.10" 821 | content-hash = "f233f40401e13063a591dc4e33cd6d029674477fa60c1e48fd6348fb4c06dca7" 822 | --------------------------------------------------------------------------------