96 Commits

Author SHA1 Message Date
fedor 8d27a4ba61 Change usage 2026-01-11 12:01:50 +03:00
fedor 4726cbf7ec Move project to src structure 2026-01-04 12:01:55 +03:00
fedor 50a298da20 Add usage examples 2026-01-03 13:06:33 +03:00
fedor f4f6856a00 Setup dev tools with uv 2026-01-03 10:54:26 +03:00
Fedor Lyanguzov 1c61d6b97d Merge pull request #5 from Fedor-Lyanguzov/CIDR4_merge_algorithm
Cidr4 merge algorithm
2025-01-27 22:05:38 +03:00
Pavel Patsey 7024503699 add cosmetic changes 2025-01-26 19:10:21 +03:00
Pavel Patsey 37ffec0558 move util tests to their own file 2025-01-26 19:09:09 +03:00
Pavel Patsey 98583a7101 make the algorithm a little faster 2025-01-26 19:03:00 +03:00
Pavel Patsey be9d603bf8 add individual exceptions 2025-01-26 18:58:17 +03:00
Pavel Patsey 14c8e66f60 remove unnecessary function 2025-01-26 11:07:41 +03:00
Pavel Patsey 3e6c8525c4 remove unnecessary annotations 2025-01-26 11:05:19 +03:00
Pavel Patsey 6f745c2d61 remove unnecessary annotations 2025-01-26 11:04:23 +03:00
Pavel Patsey 0b56caffac fix tests after removing functions 2025-01-26 10:52:45 +03:00
Pavel Patsey a4991518ba move internal functions to the function where they are used 2025-01-26 10:48:08 +03:00
Pavel Patsey e0ecafa9d0 changed the order of functions 2025-01-26 10:45:05 +03:00
Pavel Patsey 52c5fb679e rename merge_nodes function to solution 2025-01-26 10:39:31 +03:00
Pavel Patsey 04b4d14a30 shorten variable names in merge_nodes function 2025-01-26 10:30:58 +03:00
Pavel Patsey fee38af690 shorten variable names in merge_two_nodes function 2025-01-26 10:27:55 +03:00
Pavel Patsey 731d77f0e3 shorten variable names in calc_dip function 2025-01-26 10:24:34 +03:00
Pavel Patsey fa9b4073b7 move how to launch algorithms info to the folder with algorithms 2025-01-26 10:04:37 +03:00
Pavel Patsey bc8ddf1be4 add insurance functions, delete raise exception from find_parent function 2025-01-25 10:28:03 +03:00
Pavel Patsey eccc03f48d add cosmetic changes (more comfortable work with arguments) 2025-01-25 10:15:42 +03:00
Pavel Patsey 735a3c0216 take into account the case when neighbors could remain 2025-01-24 14:01:47 +03:00
Pavel Patsey f9f711ca0b add insurance functions example 2025-01-24 13:49:00 +03:00
Pavel Patsey 811432a2f5 add cosmetic changes in merge_two_nodes 2025-01-24 13:34:47 +03:00
Pavel Patsey 75d400f52e add find subnets example 2025-01-24 13:30:37 +03:00
Pavel Patsey 6c43344893 make calc_dip faster 2025-01-24 12:13:55 +03:00
Pavel Patsey 68327db96c fix tests (delete sort_nodes from tests) 2025-01-24 11:59:35 +03:00
Pavel Patsey a160ffd3a2 add cosmetic changes 2025-01-24 11:38:17 +03:00
Pavel Patsey 7c8ecea980 delete sort nodes function 2025-01-24 11:36:34 +03:00
Pavel Patsey 55c71c6eb2 use functions from util 2025-01-24 11:35:03 +03:00
Pavel Patsey f40d47989a make similar output 2025-01-24 11:31:12 +03:00
Pavel Patsey d987b39bbd add merge_nodes function 2025-01-24 10:05:11 +03:00
Pavel Patsey 91d3556eee fix calc_dip, add merge_two_nodes 2025-01-23 18:34:13 +03:00
Pavel Patsey adc569af21 fix calc_dip 2025-01-23 10:20:36 +03:00
Pavel Patsey 474b6779d8 add calc_dip function 2025-01-23 10:15:30 +03:00
Pavel Patsey 81f741ee4e fix plan 2025-01-23 09:42:30 +03:00
Pavel Patsey 5a8f98041f correct mask recalculation when reducing its length 2025-01-22 23:27:47 +03:00
Pavel Patsey 725b80544a add find_parent function 2025-01-22 23:12:21 +03:00
Pavel Patsey faa2849e6b fix sort nodes test 2025-01-22 22:25:09 +03:00
Pavel Patsey d591470a0d correct the algorithm plan 2025-01-22 22:04:47 +03:00
Pavel Patsey 05c78ad399 delete added_ips_number info from node 2025-01-22 21:56:19 +03:00
Pavel Patsey c659e96d3f add cidr4_merger algorithm to the others 2025-01-22 21:47:31 +03:00
Pavel Patsey 3ff3843b58 delete unnecessary test data 2025-01-22 18:58:18 +03:00
Pavel Patsey 8229e91fe6 Merge pull request #7 from Fedor-Lyanguzov/cma-two-versions
Fast and precise algorithms, with tests
2025-01-22 18:49:05 +03:00
Pavel Patsey 6634643155 init new algorithm 2025-01-22 18:23:02 +03:00
Fedor Lyanguzov dfd6e21f2b Fast and precise algorithms, with tests 2025-01-22 18:21:39 +03:00
Pavel Patsey 2cab2c31a6 add current solution to archive before changes 2025-01-22 17:49:20 +03:00
Pavel Patsey 303ddee08f remove unnecessary copying of node list 2025-01-19 01:45:25 +03:00
Pavel Patsey 7600814772 add test merge_nodes recursion vs cycle 2025-01-19 01:24:08 +03:00
Pavel Patsey 39e99d24b6 add merge_nodes_cycle func 2025-01-19 01:00:01 +03:00
Pavel Patsey 8ce8ac6e1a rename prev merge_nodes func to merge_nodes_deprecated 2025-01-19 00:46:00 +03:00
Pavel Patsey 1d34eb92bd add working recursive merge algorithm 2025-01-19 00:42:13 +03:00
Pavel Patsey c5e20446be add merge_nodes_recursion test 2025-01-19 00:29:54 +03:00
Pavel Patsey 724e8f8471 add lift_lonely_node test 2025-01-19 00:27:42 +03:00
Pavel Patsey 71b5525c32 fix merge_neighbors and add merge_neighbors tests 2025-01-19 00:19:04 +03:00
Pavel Patsey 58c2b5cbe0 add fixtures for nodes and groups 2025-01-19 00:05:21 +03:00
Pavel Patsey 95fbdff75f add find_neighbours_singles function test 2025-01-18 23:54:00 +03:00
Pavel Patsey c56d09cfd9 extended the make_groups function test 2025-01-18 23:52:28 +03:00
Pavel Patsey a42f459db4 add test make_groups 2025-01-18 23:19:04 +03:00
Pavel Patsey feda229c78 add non-working recursive merge algorithm 2025-01-18 23:17:47 +03:00
Pavel Patsey af91a066a1 rename ip_str to ip_address 2025-01-18 20:24:04 +03:00
Pavel Patsey b5a1498a51 add parent_ip info to Node tuple 2025-01-18 20:15:21 +03:00
Pavel Patsey edcce93d01 rename test ips 2025-01-16 15:18:08 +03:00
Pavel Patsey a1dba6affb move tests to a separate file 2025-01-16 15:12:30 +03:00
Pavel Patsey dd90ad624a use tuple opening, use only necessary arguments in functions, delete data_to_nodes function 2025-01-16 14:08:38 +03:00
Pavel Patsey d1afaace68 fix tests 2025-01-16 12:42:18 +03:00
Pavel Patsey 048d4941e4 format by black and isort 2025-01-16 12:41:52 +03:00
Fedor Lyanguzov 5f0353d574 2025-01-12 обсуждение кода 2025-01-12 18:53:24 +03:00
Павел 5ef04aefdf cleanup 2025-01-12 00:00:33 +03:00
Павел 9d5c6b384a add algorithm 2025-01-11 23:59:29 +03:00
Павел c4c5940f8d cosmetic changes 2025-01-11 22:40:23 +03:00
Павел aa75fd7ba6 cleanup 2025-01-11 22:39:23 +03:00
Павел 5a4166095b add reduce_nodes function 2025-01-11 22:30:08 +03:00
Павел 020836d33d add sort_nodes func 2025-01-11 14:40:21 +03:00
Павел 3a5a195d3d add have_same_parent func 2025-01-11 09:45:35 +03:00
Павел 9377d23134 add convert data to nodes 2025-01-11 08:08:08 +03:00
Павел dfd02e3da5 add convert from cidr4 to node 2025-01-11 07:24:48 +03:00
Павел 9148b9be32 init new algorithm 2025-01-11 07:00:22 +03:00
Павел 113b651167 speed up remove_ips_with_subnets function 2025-01-09 14:19:00 +03:00
Павел 99da7ea726 cleanup 2025-01-09 13:46:59 +03:00
Павел 722bd5a751 add remove_ips_with_subnets function and really fix merge functions 2025-01-09 13:00:37 +03:00
Павел 7d89eee92f fix rough_merge_binaries funс, previously the function returned networks and subnets 2025-01-09 01:09:29 +03:00
Павел d514d4f5e0 added smooth_merge_binaries function 2025-01-08 23:13:57 +03:00
Павел fab8786ff8 cleanup 2025-01-08 22:30:13 +03:00
Павел 604861aa80 refactoring: represent binary ip as a string of length 32 2025-01-08 22:29:17 +03:00
Павел ddcb043caa refactoring: represent binary as a string of vlsm length 2025-01-08 21:16:00 +03:00
Павел 44893da6a8 fix merge conflicts 2025-01-08 19:25:28 +03:00
Павел bb269f380a add rough_merge_ips function 2025-01-08 19:21:44 +03:00
Fedor Lyanguzov 8b2727ca38 Test intersecting cidrs will combine 2025-01-08 13:18:58 +03:00
Павел c9c53941ee add reduce_bin_ip function 2025-01-08 12:38:06 +03:00
Павел 76f2710f70 fix cidr4_to_binary function 2025-01-08 12:20:22 +03:00
Павел f7156fb61d added convert functions 2025-01-08 12:16:13 +03:00
Павел 2a4418fccc added cidr list merge algorithm 2025-01-07 17:35:23 +03:00
Павел e901b4e528 init cidr4_merger 2025-01-07 13:28:07 +03:00
Павел 1895a112b0 add .idea to gitignore 2025-01-07 12:55:49 +03:00
22 changed files with 2234 additions and 25 deletions
+7
View File
@@ -0,0 +1,7 @@
alias venv='source .venv/Scripts/activate'
alias check='flake8 vpn_manager'
alias format='black vpn_manager'
alias test='pytest'
venv
+1 -1
View File
@@ -162,4 +162,4 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/
+45 -10
View File
@@ -1,9 +1,45 @@
# vpn-manager
# WireGuard config manager (WCM)
Manage WireGuard VPN peers (both server and clients)
# Development
To start developing:
## Usage
```bash
# networks
wcm network add test
wcm network add with-ip --address=10.0.0.2/24
wcm network delete with-ip
wcm network add no-preshared --no-preshared-key
wcm network show
# peers
wcm peer add temp
wcm peer add server --to-network test --pass-thru=0.0.0.0/0 --port=59
wcm peer add alice --to-network test --address=10.0.0.150
wcm peer add bob --with-pk=wireguardprivatekey=
wcm connect bob test
wcm disconnect bob all
wcm peer remove alice from network test
wcm peer delete alice
wcm peer show
# config
wcm config make test
wcm config make all
wcm config make test --output-dir=~/vpn-configs
wcm config load ~/vpn-configs/old
# Delete database
wcm database delete
# development crud
```
## Development
To start developing:
```bash
python -m venv .venv
source .venv/Scripts/activate
pip install -e .[test,lint]
@@ -12,17 +48,16 @@ deactivate
```
Tools:
```
```bash
pytest
flake8 vpn_manager
black vpn_manager
flake8 wcm
black wcm
```
Test runs:
```
# package
$ python -m vpn_manager
run
```bash
# script declared in pyproject.toml
$ example
run
+486
View File
@@ -0,0 +1,486 @@
4.78.139.0/24
23.101.24.0/24
23.202.231.0/24
23.217.138.0/24
23.225.141.0/24
23.234.30.0/24
31.13.64.0/24
31.13.67.0/24
31.13.68.0/22
31.13.73.0/24
31.13.75.0/24
31.13.76.0/24
31.13.80.0/21
31.13.88.0/24
31.13.90.0/23
31.13.92.0/24
31.13.94.0/23
31.13.96.0/24
31.13.106.0/24
31.13.112.0/24
37.152.2.0/24
38.121.72.0/24
39.109.122.0/24
43.226.16.0/24
43.245.104.0/24
45.54.28.0/24
45.77.186.0/24
45.114.11.0/24
45.253.131.0/24
46.61.154.0/24
46.134.216.0/24
47.88.58.0/24
49.231.55.0/24
50.23.209.0/24
50.87.93.0/24
50.117.117.0/24
52.58.1.0/24
52.175.9.0/24
54.89.135.0/24
54.144.128.0/24
54.234.18.0/24
58.63.233.0/24
59.18.44.0/24
59.18.46.0/24
59.24.3.0/24
59.188.250.0/24
61.91.8.0/24
61.205.119.0/24
62.0.80.0/24
64.13.192.0/24
64.53.242.0/24
64.233.160.0/21
64.233.168.0/22
64.233.176.0/20
65.49.26.0/24
65.49.68.0/24
66.102.1.0/24
66.220.146.0/23
66.220.148.0/23
67.15.100.0/24
67.15.129.0/24
67.228.102.0/24
67.228.235.0/24
67.230.169.0/24
69.30.25.0/24
69.50.221.0/24
69.63.176.0/24
69.63.178.0/24
69.63.180.0/23
69.63.184.0/24
69.63.186.0/23
69.63.190.0/24
69.162.134.0/24
69.171.224.0/24
69.171.227.0/24
69.171.228.0/23
69.171.234.0/24
69.171.242.0/24
69.171.247.0/24
69.197.153.0/24
74.86.3.0/24
74.86.12.0/24
74.86.17.0/24
74.86.118.0/24
74.86.142.0/24
74.86.151.0/24
74.86.226.0/24
74.86.228.0/24
74.125.1.0/24
74.125.2.0/24
74.125.8.0/24
74.125.10.0/23
74.125.13.0/24
74.125.20.0/23
74.125.23.0/24
74.125.24.0/24
74.125.26.0/24
74.125.28.0/24
74.125.31.0/24
74.125.68.0/22
74.125.90.0/24
74.125.96.0/24
74.125.100.0/24
74.125.102.0/23
74.125.104.0/24
74.125.106.0/24
74.125.110.0/23
74.125.124.0/24
74.125.126.0/23
74.125.128.0/20
74.125.153.0/24
74.125.154.0/23
74.125.159.0/24
74.125.164.0/24
74.125.170.0/24
74.125.172.0/23
74.125.192.0/23
74.125.195.0/24
74.125.196.0/22
74.125.200.0/22
74.125.204.0/23
74.125.206.0/24
75.126.33.0/24
75.126.115.0/24
75.126.124.0/24
75.126.135.0/24
75.126.150.0/24
75.126.164.0/24
77.37.252.0/24
77.120.15.0/24
80.87.199.0/24
81.23.20.0/24
81.23.23.0/24
81.192.13.0/24
81.192.191.0/24
88.191.249.0/24
90.201.124.0/24
92.87.232.0/24
93.179.102.0/24
94.24.232.0/24
94.31.189.0/24
95.59.170.0/24
96.44.137.0/24
98.159.108.0/24
103.39.76.0/24
103.42.176.0/24
103.56.16.0/24
103.73.161.0/24
103.97.3.0/24
103.97.176.0/24
103.200.30.0/23
103.214.168.0/24
103.226.246.0/24
103.228.130.0/24
103.230.123.0/24
103.240.180.0/24
103.240.182.0/24
103.246.246.0/24
103.252.114.0/23
104.16.251.0/24
104.16.252.0/24
104.23.124.0/23
104.31.142.0/24
104.244.43.0/24
104.244.45.0/24
104.244.46.0/24
107.181.166.0/24
108.160.161.0/24
108.160.162.0/23
108.160.165.0/24
108.160.166.0/23
108.160.169.0/24
108.160.170.0/24
108.160.172.0/23
108.177.8.0/21
108.177.96.0/23
108.177.98.0/24
108.177.103.0/24
108.177.104.0/24
108.177.111.0/24
108.177.112.0/24
108.177.119.0/24
108.177.120.0/22
108.177.125.0/24
108.177.126.0/23
109.224.41.0/24
110.164.8.0/24
111.243.214.0/24
113.108.239.0/24
113.171.242.0/24
114.4.7.0/24
114.43.24.0/24
115.126.100.0/24
116.89.243.0/24
118.98.30.0/24
118.98.36.0/24
118.98.106.0/24
118.107.180.0/24
118.184.26.0/24
118.184.78.0/24
118.193.202.0/24
118.193.240.0/24
119.28.87.0/24
120.232.233.0/24
120.232.234.0/24
120.233.71.0/24
120.253.250.0/24
120.253.253.0/24
120.253.255.0/24
121.78.42.0/24
122.10.85.0/24
122.154.76.0/24
122.248.226.0/24
122.252.245.0/24
124.11.210.0/24
128.121.146.0/24
128.121.243.0/24
128.242.240.0/24
128.242.245.0/24
128.242.250.0/24
130.211.15.0/24
142.250.0.0/23
142.250.4.0/24
142.250.8.0/22
142.250.12.0/23
142.250.27.0/24
142.250.28.0/24
142.250.30.0/23
142.250.64.0/20
142.250.80.0/23
142.250.96.0/21
142.250.105.0/24
142.250.107.0/24
142.250.110.0/23
142.250.112.0/22
142.250.123.0/24
142.250.125.0/24
142.250.126.0/24
142.250.128.0/24
142.250.136.0/24
142.250.138.0/24
142.250.141.0/24
142.250.142.0/24
142.250.145.0/24
142.250.147.0/24
142.250.148.0/23
142.250.150.0/24
142.250.152.0/23
142.250.157.0/24
142.250.158.0/23
142.250.176.0/20
142.250.192.0/21
142.250.200.0/23
142.250.203.0/24
142.250.204.0/22
142.250.217.0/24
142.250.218.0/23
142.251.0.0/23
142.251.2.0/24
142.251.4.0/23
142.251.6.0/24
142.251.8.0/23
142.251.10.0/24
142.251.12.0/24
142.251.15.0/24
142.251.16.0/24
142.251.18.0/24
142.251.31.0/24
142.251.32.0/22
142.251.36.0/23
142.251.39.0/24
142.251.40.0/22
142.251.45.0/24
142.251.46.0/23
142.251.107.0/24
142.251.111.0/24
142.251.112.0/24
142.251.116.0/23
142.251.120.0/24
142.251.128.0/23
142.251.130.0/24
142.251.132.0/22
142.251.140.0/22
142.251.161.0/24
142.251.162.0/23
142.251.164.0/22
142.251.168.0/24
142.251.170.0/23
142.251.172.0/22
142.251.176.0/22
142.251.180.0/24
142.251.182.0/23
142.251.184.0/22
142.251.188.0/24
142.251.208.0/23
142.251.211.0/24
142.251.214.0/23
142.251.216.0/24
142.251.218.0/24
142.251.220.0/22
145.255.14.0/24
148.163.48.0/24
150.107.3.0/24
154.0.29.0/24
154.83.14.0/23
154.85.102.0/24
154.92.16.0/24
156.233.67.0/24
157.240.0.0/22
157.240.6.0/23
157.240.8.0/22
157.240.12.0/23
157.240.15.0/24
157.240.16.0/23
157.240.18.0/24
157.240.20.0/23
159.65.107.0/24
159.106.121.0/24
159.138.20.0/24
162.125.1.0/24
162.125.2.0/24
162.125.6.0/23
162.125.8.0/24
162.125.17.0/24
162.125.18.0/24
162.125.32.0/24
162.125.34.0/24
162.125.80.0/24
162.125.82.0/23
162.220.12.0/24
168.143.162.0/24
168.143.171.0/24
172.217.0.0/19
172.217.129.0/24
172.217.130.0/24
172.217.133.0/24
172.217.135.0/24
172.217.160.0/20
172.217.192.0/22
172.217.197.0/24
172.217.203.0/24
172.217.204.0/24
172.217.212.0/24
172.217.214.0/23
172.217.218.0/23
172.217.222.0/24
172.253.58.0/24
172.253.62.0/23
172.253.112.0/21
172.253.120.0/24
172.253.122.0/23
172.253.124.0/22
173.194.4.0/23
173.194.12.0/24
173.194.22.0/24
173.194.28.0/23
173.194.31.0/24
173.194.49.0/24
173.194.51.0/24
173.194.54.0/23
173.194.59.0/24
173.194.65.0/24
173.194.66.0/23
173.194.68.0/23
173.194.70.0/24
173.194.73.0/24
173.194.74.0/24
173.194.76.0/22
173.194.135.0/24
173.194.150.0/24
173.194.154.0/24
173.194.161.0/24
173.194.162.0/23
173.194.164.0/24
173.194.166.0/23
173.194.174.0/23
173.194.178.0/24
173.194.182.0/23
173.194.184.0/23
173.194.187.0/24
173.194.188.0/24
173.194.190.0/23
173.194.192.0/19
173.208.182.0/24
173.231.12.0/24
173.234.53.0/24
173.236.182.0/24
173.236.212.0/24
173.244.209.0/24
173.244.217.0/24
173.252.88.0/24
173.252.105.0/24
173.252.108.0/24
173.252.248.0/24
173.255.209.0/24
173.255.213.0/24
174.36.196.0/24
174.36.228.0/24
174.37.54.0/24
174.37.154.0/24
174.37.175.0/24
174.37.243.0/24
178.151.230.0/24
178.176.156.0/24
179.60.193.0/24
180.163.150.0/23
182.50.139.0/24
182.79.251.0/24
184.72.1.0/24
184.173.136.0/24
185.45.6.0/23
185.60.216.0/24
185.60.218.0/23
185.158.208.0/24
186.208.210.0/24
187.7.116.0/24
190.5.235.0/24
192.133.77.0/24
192.178.24.0/23
192.178.27.0/24
192.178.48.0/23
192.178.50.0/24
192.178.52.0/24
192.178.54.0/24
192.178.56.0/23
192.178.128.0/24
192.178.130.0/24
193.109.164.0/24
194.78.0.0/24
196.49.8.0/24
198.27.124.0/24
198.44.185.0/24
199.16.156.0/24
199.16.158.0/24
199.59.148.0/23
199.59.150.0/24
199.96.58.0/23
199.96.61.0/24
199.96.62.0/23
199.193.116.0/24
201.0.223.0/24
202.53.137.0/24
202.160.128.0/23
202.160.130.0/24
202.169.173.0/24
202.182.98.0/24
203.66.182.0/24
203.111.254.0/24
203.113.51.0/24
203.113.189.0/24
203.208.39.0/24
203.208.40.0/23
203.208.43.0/24
203.208.49.0/24
203.208.50.0/24
203.233.96.0/24
204.79.197.0/24
205.186.152.0/24
208.31.254.0/24
208.43.170.0/24
208.43.237.0/24
208.77.47.0/24
208.101.21.0/24
208.101.60.0/24
209.85.144.0/22
209.85.165.0/24
209.85.200.0/22
209.85.224.0/24
209.85.226.0/24
209.85.232.0/22
209.95.56.0/24
210.56.51.0/24
210.139.253.0/24
210.209.84.0/24
211.104.160.0/24
212.113.52.0/24
213.59.210.0/24
216.58.192.0/20
216.58.208.0/21
216.58.217.0/24
216.58.220.0/22
216.239.32.0/24
216.239.34.0/24
216.239.36.0/24
216.239.38.0/24
220.181.174.0/24
+16 -13
View File
@@ -1,28 +1,31 @@
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["vpn_manager"]
[project]
name = "vpn-manager"
name = "wcm"
description = "Manage WireGuard VPN peers (both server and clients)"
version = "2024.12.0a"
license = {file = "LICENSE"}
readme = "README.md"
requires-python = ">=3.8"
dependencies = ["click>=8.1"]
dependencies = [
"click>=8.1",
]
authors = [
{name = "Fedor Lyanguzov"},
]
[project.optional-dependencies]
test = ["pytest"]
lint = [
[dependency-groups]
dev = [
"pytest",
"black",
"flake8"
]
[project.scripts]
example = "vpn_manager.__main__:main"
example = "wcm.__main__:main"
[build-system]
requires = ["uv_build>=0.9.21,<0.10.0"]
build-backend = "uv_build"
[tool.setuptools]
packages = ["wcm"]
+395
View File
@@ -0,0 +1,395 @@
import pytest
from cidr4_merger import (
Cidr4MergerError,
cidr4_to_node,
find_neighbours_singles,
get_group_with_max_mask_len,
get_net_addr,
get_parent_ip,
have_same_parent,
lift_lonely_node,
make_cidr4,
make_groups,
make_parent,
merge_neighbors,
merge_nodes_cycle,
merge_nodes_deprecated,
merge_nodes_recursion,
reduce_nodes,
sort_nodes,
)
from .cidr4_data_for_tests import test_cidr4_data
def test_true():
assert True
bin_a = "10011000000000001000010000010000"
assert len(bin_a) == 32
ip_a = int(bin_a, 2)
bin_b = "10011100000000000000000000101011"
assert len(bin_b) == 32
ip_b = int(bin_b, 2)
bin_c = "10011000000000000000000000000000"
assert len(bin_c) == 32
ip_c = int(bin_c, 2)
bin_d = "11111100000000000000000000000000"
assert len(bin_c) == 32
ip_d = int(bin_d, 2)
def test_cidr4_to_node():
assert cidr4_to_node("4.78.139.0/24") == (72256256, 24, 0, 72256000)
assert cidr4_to_node("0.0.0.0/32") == (0, 32, 0, 0)
assert cidr4_to_node("23.234.30.0/24") == (401219072, 24, 0, 401219072)
assert cidr4_to_node("172.217.0.0/19") == (2899902464, 19, 0, 2899902464)
assert cidr4_to_node("23.225.141.0/24") == (400657664, 24, 0, 400657408)
assert cidr4_to_node("31.13.94.0/23") == (520969728, 23, 0, 520969216)
assert cidr4_to_node("0.0.0.0/2") == (0, 2, 0, 0)
assert cidr4_to_node("64.0.0.0/2") == (1073741824, 2, 0, 0)
assert cidr4_to_node("128.0.0.0/2") == (2147483648, 2, 0, 2147483648)
assert cidr4_to_node("192.0.0.0/2") == (3221225472, 2, 0, 2147483648)
def test_make_cidr4():
assert make_cidr4(72256256, 24) == "4.78.139.0/24"
assert make_cidr4(0, 32) == "0.0.0.0/32"
assert make_cidr4(401219072, 24) == "23.234.30.0/24"
assert make_cidr4(2899902464, 19) == "172.217.0.0/19"
assert make_cidr4(400657664, 24) == "23.225.141.0/24"
assert make_cidr4(520969728, 23) == "31.13.94.0/23"
def test_get_net_addr():
assert get_net_addr(ip_a, 5) == ip_c
assert get_net_addr(ip_b, 5) == ip_c
assert get_net_addr(0, 1) == 0
assert get_net_addr(0, 0) == 0
def test_get_parent_mask():
assert get_parent_ip(ip_a, 6) == ip_c
assert get_parent_ip(ip_b, 6) == ip_c
assert get_parent_ip(0, 1) == 0
with pytest.raises(Exception) as exc_info:
get_parent_ip(0, 0)
assert str(exc_info.value) == "The top of the tree has no parent!"
assert exc_info.type is Cidr4MergerError
def test_have_same_parent():
assert have_same_parent(ip_c, 6, ip_c, 6) is True
assert have_same_parent(ip_c, 6, ip_c, 5) is False
assert have_same_parent(ip_a, 6, ip_d, 6) is False
assert have_same_parent(ip_a, 6, 0, 1) is False
assert have_same_parent(ip_a, 6, 0, 0) is False
def test_sort_nodes():
assert sort_nodes(
[
(401219072, 24, 0, 401219072),
(2899902464, 19, 0, 2899902464),
(400657664, 24, 0, 400657408),
(520969728, 23, 0, 520969216),
]
) == [
(2899902464, 19, 0, 2899902464),
(520969728, 23, 0, 520969216),
(400657664, 24, 0, 400657408),
(401219072, 24, 0, 401219072),
]
def test_get_group_with_max_mask_len():
assert get_group_with_max_mask_len(
[
(2899902464, 19, 0, 2899902464),
(520969728, 23, 0, 520969216),
(400657664, 24, 0, 400657408),
(401219072, 24, 0, 401219072),
]
) == [(400657664, 24, 0, 400657408), (401219072, 24, 0, 401219072)]
assert get_group_with_max_mask_len(
[
(401219072, 24, 0, 401219072),
(2899902464, 19, 0, 2899902464),
(520969728, 23, 0, 520969216),
]
) == [(401219072, 24, 0, 401219072)]
def test_make_parent():
assert make_parent((0, 2, 12, 0), (1073741824, 2, 3, 0)) == (0, 1, 15, 0)
assert make_parent(
(2147483648, 2, 1, 2147483648), (3221225472, 2, 2, 2147483648)
) == (2147483648, 1, 3, 0)
with pytest.raises(Exception) as exc_info:
make_parent((0, 2, 12, 0), (3221225472, 2, 2, 2147483648))
assert str(exc_info.value) == "Nodes must be neighbors!"
assert exc_info.type is Cidr4MergerError
def test_reduce_nodes():
assert reduce_nodes(
[
(0, 2, 12, 0),
(1073741824, 2, 3, 0),
]
) == [
(0, 1, 15, 0),
]
assert reduce_nodes(
[
(0, 2, 12, 0),
(1073741824, 2, 3, 0),
(2147483648, 2, 1, 2147483648),
(3221225472, 2, 2, 2147483648),
]
) == [
(2147483648, 1, 3, 0),
(0, 2, 12, 0),
(1073741824, 2, 3, 0),
]
assert reduce_nodes(
[
(0, 2, 12, 0),
(2147483648, 1, 0, 0),
]
) == [
(0, 1, 12 + 2**30, 0),
(2147483648, 1, 0, 0),
]
with pytest.raises(Exception) as exc_info:
reduce_nodes(
[
(0, 1, 12 + 2**30, 0),
(2147483648, 1, 0, 0),
]
)
assert exc_info.type is Cidr4MergerError
assert str(exc_info.value) == "The top of the tree has no parent!"
def test_merge_nodes_deprecated():
assert merge_nodes_deprecated(
[
(0, 2, 12, 0),
(2147483648, 2, 1, 2147483648),
(3221225472, 2, 2, 2147483648),
],
2,
) == [
(2147483648, 1, 3, 0),
(0, 2, 12, 0),
]
with pytest.raises(Exception) as exc_info:
merge_nodes_deprecated(
[
(0, 2, 12, 0),
(2147483648, 2, 1, 2147483648),
(3221225472, 2, 2, 2147483648),
],
1,
)
assert exc_info.type is Cidr4MergerError
assert str(exc_info.value) == "The top of the tree has no parent!"
def test_make_groups():
nodes = [
(2398793728, 20, 0, 2398789632),
(2899943424, 20, 0, 2899943424),
(3627728896, 20, 0, 3627728896),
(520963072, 22, 0, 520962048),
(1089054720, 22, 0, 1089054720),
(2899902464, 19, 0, 2899902464),
(2915221504, 19, 0, 2915221504),
]
assert dict(make_groups(nodes)) == {
20: [
(2398793728, 20, 0, 2398789632),
(2899943424, 20, 0, 2899943424),
(3627728896, 20, 0, 3627728896),
],
22: [(520963072, 22, 0, 520962048), (1089054720, 22, 0, 1089054720)],
19: [(2899902464, 19, 0, 2899902464), (2915221504, 19, 0, 2915221504)],
}
@pytest.fixture
def nodes_only_neighbours():
return [
(0, 2, 12, 0),
(1073741824, 2, 3, 0),
(2147483648, 2, 1, 2147483648),
(3221225472, 2, 2, 2147483648),
]
@pytest.fixture
def nodes_only_singles():
return [
(0, 2, 12, 0),
(2147483648, 2, 1, 2147483648),
]
@pytest.fixture
def nodes_with_neighbours_n_singles():
return [
(0, 2, 12, 0),
(1073741824, 2, 3, 0),
(2147483648, 2, 1, 2147483648),
]
@pytest.fixture
def groups_only_neighbours(nodes_only_neighbours):
return make_groups(nodes_only_neighbours)
@pytest.fixture
def groups_only_singles(nodes_only_singles):
return make_groups(nodes_only_singles)
@pytest.fixture
def groups_with_neighbours_n_singles(nodes_with_neighbours_n_singles):
return make_groups(nodes_with_neighbours_n_singles)
def test_find_neighbours_singles(
groups_only_neighbours,
groups_only_singles,
groups_with_neighbours_n_singles,
):
assert find_neighbours_singles(groups_only_neighbours) == (
[
((0, 2, 12, 0), (1073741824, 2, 3, 0)),
((2147483648, 2, 1, 2147483648), (3221225472, 2, 2, 2147483648)),
],
[],
)
assert find_neighbours_singles(groups_only_singles) == (
[],
[(0, 2, 12, 0), (2147483648, 2, 1, 2147483648)],
)
assert find_neighbours_singles(groups_with_neighbours_n_singles) == (
[((0, 2, 12, 0), (1073741824, 2, 3, 0))],
[(2147483648, 2, 1, 2147483648)],
)
def test_merge_neighbors__only_neighbours(nodes_only_neighbours):
new_nodes = merge_neighbors(
nodes_only_neighbours,
[
((0, 2, 12, 0), (1073741824, 2, 3, 0)),
((2147483648, 2, 1, 2147483648), (3221225472, 2, 2, 2147483648)),
],
)
assert new_nodes == [
(0, 1, 15, 0),
(2147483648, 1, 3, 0),
]
def test_merge_neighbors__neighbours_n_singles(nodes_with_neighbours_n_singles):
new_nodes = merge_neighbors(
nodes_with_neighbours_n_singles,
[((0, 2, 12, 0), (1073741824, 2, 3, 0))],
)
assert new_nodes == [
(0, 1, 15, 0),
(2147483648, 2, 1, 2147483648),
]
def test_lift_lonely_node(nodes_only_singles):
singles = [(0, 2, 12, 0), (2147483648, 2, 1, 2147483648)]
new_nodes = lift_lonely_node(nodes_only_singles, singles)
assert new_nodes == [(2147483648, 1, 1073741825, 0), (0, 2, 12, 0)]
def test_merge_nodes_recursion():
assert merge_nodes_recursion(
[
(0, 2, 12, 0),
(2147483648, 2, 1, 2147483648),
(3221225472, 2, 2, 2147483648),
],
2,
) == [
(2147483648, 1, 3, 0),
(0, 2, 12, 0),
]
with pytest.raises(Exception) as exc_info:
merge_nodes_recursion(
[
(0, 2, 12, 0),
(2147483648, 2, 1, 2147483648),
(3221225472, 2, 2, 2147483648),
],
1,
)
assert exc_info.type is Cidr4MergerError
assert str(exc_info.value) == "The top of the tree has no parent!"
def test_merge_nodes_cycle():
assert merge_nodes_cycle(
[
(0, 2, 12, 0),
(2147483648, 2, 1, 2147483648),
(3221225472, 2, 2, 2147483648),
],
2,
) == [
(2147483648, 1, 3, 0),
(0, 2, 12, 0),
]
with pytest.raises(Exception) as exc_info:
merge_nodes_cycle(
[
(0, 2, 12, 0),
(2147483648, 2, 1, 2147483648),
(3221225472, 2, 2, 2147483648),
],
1,
)
assert exc_info.type is Cidr4MergerError
assert str(exc_info.value) == "The top of the tree has no parent!"
def test_merge_nodes_recursion_vs_cycle():
required_len = 20
test_data = test_cidr4_data.strip().splitlines()
test_nodes = list(map(cidr4_to_node, test_data))
test_nodes = sort_nodes(test_nodes)
merged_nodes_recursion = merge_nodes_recursion(test_nodes, required_len)
merged_nodes_cycle = merge_nodes_cycle(test_nodes, required_len)
assert merged_nodes_recursion == merged_nodes_cycle
+226
View File
@@ -0,0 +1,226 @@
import cProfile
import sys
from collections import defaultdict
sys.setrecursionlimit(10_000)
Node = tuple[int, int, int, int]
class Cidr4MergerError(Exception):
pass
def get_data(input_file):
with open(input_file, "r") as file:
data = file.read().splitlines()
return data
def cidr4_to_node(cidr4: str) -> Node:
ip_address, mask_len = cidr4.strip().split("/")
mask_len = int(mask_len)
a, b, c, d = list(map(int, ip_address.split(".")))
ip = a * 256**3 + b * 256**2 + c * 256**1 + d * 256**0
added_ips_number = 0
parent_ip = get_parent_ip(ip, mask_len)
return ip, mask_len, added_ips_number, parent_ip
def sort_nodes(nodes: list[Node]) -> list[Node]:
return sorted(nodes, key=lambda x: (x[1], x[0]))
def get_net_addr(ip: int, mask_len: int) -> int:
mask = ((1 << mask_len) - 1) << (32 - mask_len)
net_addr = ip & mask
return net_addr
def get_parent_ip(ip: int, mask_len: int) -> int:
if mask_len == 0:
raise Cidr4MergerError("The top of the tree has no parent!")
return get_net_addr(ip, mask_len - 1)
def have_same_parent(mask_len_a, parent_ip_a, mask_len_b, parent_ip_b) -> bool:
return mask_len_a == mask_len_b and parent_ip_a == parent_ip_b
def get_group_with_max_mask_len(nodes: list[Node]) -> list[Node]:
max_mask_len = max(nodes, key=lambda x: x[1])[1]
return list(filter(lambda x: x[1] == max_mask_len, nodes))
def make_parent(a: Node, b: Node | None = None) -> Node:
ip_a, mask_len_a, added_ips_number_a, parent_ip_a = a
if b:
ip_b, mask_len_b, added_ips_b, parent_ip_b = b
if not have_same_parent(mask_len_a, parent_ip_a, mask_len_b, parent_ip_b):
raise Cidr4MergerError("Nodes must be neighbors!")
added_ips_number = added_ips_number_a + added_ips_b
else:
added_ips_number = added_ips_number_a + 2 ** (32 - mask_len_a)
ip = parent_ip_a
mask_len = mask_len_a - 1
parent_ip = get_parent_ip(ip, mask_len)
return ip, mask_len, added_ips_number, parent_ip
def reduce_nodes(nodes: list[Node]) -> list[Node]:
group = get_group_with_max_mask_len(nodes)
neighbours = []
loners = []
i = 0
while i < len(group) - 1:
a, b = group[i], group[i + 1]
ip_a, mask_len_a, _, parent_ip_a = a
ip_b, mask_len_b, _, parent_ip_b = b
if have_same_parent(mask_len_a, parent_ip_a, mask_len_b, parent_ip_b):
neighbours.append((a, b))
i += 2
else:
loners.append(a)
i += 1
if i == len(group) - 1:
loners.append(group[i])
if neighbours:
zipped = zip(neighbours, map(lambda x: make_parent(x[0], x[1]), neighbours))
min_zipped = min(zipped, key=lambda x: x[1][2])
(a, b), parent = min_zipped
nodes.remove(a)
nodes.remove(b)
nodes.append(parent)
elif loners:
zipped = zip(loners, map(make_parent, loners))
min_zipped = min(zipped, key=lambda x: x[1][2])
a, parent = min_zipped
nodes.remove(a)
nodes.append(parent)
else:
assert False, "Error"
return sort_nodes(nodes)
def merge_nodes_deprecated(nodes: list[Node], required_len: int) -> list[Node]:
while len(nodes) > required_len:
nodes = reduce_nodes(nodes)
return nodes
def make_cidr4(ip, mask_len) -> str:
lst = [str(ip >> (i << 3) & 0xFF) for i in reversed(range(4))]
ip_address = ".".join(lst)
return f"{ip_address}/{mask_len}"
def lift_lonely_node(nodes: list[Node], singles: list[Node]) -> list[Node]:
# find single whose parent has the least added addresses
min_single, min_parent = singles[0], make_parent(singles[0])
for node in singles[1:]:
parent = make_parent(node)
if parent[2] < min_parent[2]:
min_single, min_parent = node, parent
nodes.remove(min_single)
nodes.append(min_parent)
nodes = sort_nodes(nodes)
return nodes
def merge_neighbors(
nodes: list[Node], neighbours: list[tuple[Node, Node]]
) -> list[Node]:
for a, b in neighbours:
parent = make_parent(a, b)
nodes.remove(a)
nodes.remove(b)
nodes.append(parent)
return sort_nodes(nodes)
def find_neighbours_singles(groups: defaultdict) -> tuple[list, list]:
neighbours = []
singles = []
for group in groups.values():
i = 0
while i < len(group) - 1:
a, b = group[i], group[i + 1]
ip_a, mask_len_a, _, parent_ip_a = a
ip_b, mask_len_b, _, parent_ip_b = b
if have_same_parent(mask_len_a, parent_ip_a, mask_len_b, parent_ip_b):
neighbours.append((a, b))
i += 2
else:
singles.append(a)
i += 1
if i == len(group) - 1:
singles.append(group[i])
return neighbours, singles
def make_groups(nodes: list[Node]) -> defaultdict:
groups = defaultdict(list)
for n in nodes:
groups[n[1]].append(n)
return groups
def merge_nodes_recursion(nodes: list[Node], required_len: int) -> list[Node]:
if len(nodes) <= required_len:
return nodes
groups = make_groups(nodes)
neighbours, singles = find_neighbours_singles(groups)
if neighbours:
new_nodes = merge_neighbors(nodes, neighbours)
return merge_nodes_recursion(new_nodes, required_len)
new_nodes = lift_lonely_node(nodes, singles)
return merge_nodes_recursion(new_nodes, required_len)
def merge_nodes_cycle(nodes_to_merge: list[Node], required_len: int) -> list[Node]:
nodes = [x for x in nodes_to_merge]
while not len(nodes) <= required_len:
groups = make_groups(nodes)
neighbours, singles = find_neighbours_singles(groups)
if neighbours:
nodes = merge_neighbors(nodes, neighbours)
elif singles:
nodes = lift_lonely_node(nodes, singles)
else:
raise Cidr4MergerError("Invalid case!")
return nodes
def main():
file = "cidr4.txt"
required_len = 20
data = get_data(file)
nodes = list(map(cidr4_to_node, data))
nodes = sort_nodes(nodes)
# merged_nodes = merge_nodes_deprecated(nodes, required_len)
# merged_nodes = merge_nodes_recursion(nodes, required_len)
merged_nodes = merge_nodes_cycle(nodes, required_len)
cidr4s = []
sum_added_ips = 0
for ip_value, mask_len, added_ips, _ in merged_nodes:
cidr4s.append(make_cidr4(ip_value, mask_len))
sum_added_ips += added_ips
cidr4s_str = "\n".join(cidr4s)
print(
f"Исходный список длины {len(nodes)} сокращен до {len(cidr4s)}\n"
f"Количество добавленных ip адресов: {sum_added_ips:_}\n"
f"Список объединенных cidr4:\n"
f"{cidr4s_str}"
)
if __name__ == "__main__":
cProfile.run("main()")
@@ -0,0 +1,30 @@
```python
def find_subnets(nodes: list[Node]) -> list[tuple[Node, Node]]:
subnets = []
for i, (a, b) in enumerate(zip(nodes, nodes[1:])):
parent_node, dip = merge_two_nodes(a, b)
if parent_node == a or parent_node == b:
subnets.append((a, b))
return subnets
def ensure_no_subnets(nodes: list[Node]):
if subnets := find_subnets(nodes):
raise Cidr4MergerError(f"There are subnets! {subnets=}")
```
```python
def find_neighbors(nodes: list[Node]) -> list[tuple[Node, Node]]:
neighbors = []
for i, (a, b) in enumerate(zip(nodes, nodes[1:])):
parent_node, dip = merge_two_nodes(a, b)
if parent_node[1] + 1 == a[1] == b[1]:
neighbors.append((a, b))
return neighbors
def ensure_no_neighbors(nodes: list[Node]):
if neighbors := find_neighbors(nodes):
raise Cidr4MergerError(f"There are neighbors! {neighbors=}")
```
+6
View File
@@ -0,0 +1,6 @@
Run merging algorithms:
```
python -m vpn_manager.cidr4_merge.cidr4_merger
python -m vpn_manager.cidr4_merge.fast
python -m vpn_manager.cidr4_merge.precise
```
+2
View File
@@ -0,0 +1,2 @@
class Cidr4MergerError(Exception):
pass
+104
View File
@@ -0,0 +1,104 @@
import cProfile
from vpn_manager.cidr4_merge import Cidr4MergerError
from .util import cidr4_to_node, get_data, make_cidr4
Node = tuple[int, int]
class EnsureNoSubnetError(Cidr4MergerError):
pass
class EnsureNoNeighborsError(Cidr4MergerError):
pass
def find_subnets(nodes: list[Node]) -> list[tuple[Node, Node]]:
subnets = []
for i, (a, b) in enumerate(zip(nodes, nodes[1:])):
parent_node, dip = merge_nodes(a, b)
if parent_node == a or parent_node == b:
subnets.append((a, b))
return subnets
def ensure_no_subnets(nodes: list[Node]):
if subnets := find_subnets(nodes):
raise EnsureNoSubnetError(f"There are subnets! {subnets=}")
def find_neighbors(nodes: list[Node]) -> list[tuple[int, Node, Node]]:
neighbors = []
for i, (a, b) in enumerate(zip(nodes, nodes[1:])):
parent_node, dip = merge_nodes(a, b)
if parent_node[1] + 1 == a[1] == b[1]:
neighbors.append((i, a, b))
return neighbors
def ensure_no_neighbors(nodes: list[Node]):
if neighbors := find_neighbors(nodes):
raise EnsureNoNeighborsError(f"There are neighbors! {neighbors=}")
def merge_nodes(a: Node, b: Node) -> tuple[Node, int]:
def find_parent(a, b):
ia, la = a
ib, lb = b
min_l = min(la, lb)
mask = ((1 << min_l) - 1) << (32 - min_l)
while ia & mask != ib & mask:
min_l -= 1
mask = (mask << 1) & ((1 << 32) - 1)
return ia & mask, min_l
def calc_dip(l1, lp):
m = lp + 1
res = 1 << (l1 - m)
res -= 1
res <<= 32 - l1
return res
p = find_parent(a, b)
dip = calc_dip(a[1], p[1]) + calc_dip(b[1], p[1])
return p, dip
def solution(nodes: list[Node], req_len: int) -> tuple[list[Node], int]:
sum_dip = 0
while len(nodes) > req_len:
min_t = None, None, float("inf")
for i, (a, b) in enumerate(zip(nodes, nodes[1:])):
p, dip = merge_nodes(a, b)
if dip < min_t[2]:
min_t = i, p, dip
if dip == 0:
break
i, p, dip = min_t
nodes = nodes[:i] + [p] + nodes[i + 2 :]
sum_dip += dip
while nbs := find_neighbors(nodes):
i, a, b = nbs[0]
p, dip = merge_nodes(a, b)
nodes = nodes[:i] + [p] + nodes[i + 2 :]
sum_dip += dip
return nodes, sum_dip
def main():
required_len = 20
data = get_data()
nodes = sorted(map(cidr4_to_node, data))
ensure_no_subnets(nodes)
ensure_no_neighbors(nodes)
merged_nodes, sum_dip = solution(nodes, required_len)
cidr4s = [make_cidr4(ip, mask_len) for ip, mask_len in merged_nodes]
print(cidr4s, sum_dip, sep="\n")
if __name__ == "__main__":
cProfile.run("main()")
+43
View File
@@ -0,0 +1,43 @@
from heapq import heapify, heappush, heappop
from .util import mask, get_data, cidr4_to_node, make_cidr4
def solution(cidrs, M):
h = []
d = {}
for ip, l in cidrs:
h.append((32-l, ip, l))
d[(ip, l)] = 0
heapify(h)
while len(h)>M:
x1, ip1, l1 = heappop(h)
x2, ip2, l2 = h[0]
if l1==l2 and ip1 & mask[l1-1] == ip2 & mask[l2-1]:
heappop(h)
if (ip1 & mask[l1-1], l1-1) not in d:
heappush(h, (x1+1, ip1 & mask[l1-1], l1-1))
d[(ip1 & mask[l1-1], l1-1)] = d[(ip1, l1)] + d[(ip2, l2)]
del d[(ip2, l2)]
else:
if (ip1 & mask[l1-1], l1-1) not in d:
heappush(h, (x1+1, ip1 & mask[l1-1], l1-1))
d[(ip1 & mask[l1-1], l1-1)] = d[(ip1, l1)] + 2**x1
del d[(ip1, l1)]
s = sum(d.values())
cidrs = list(d.keys())
return cidrs, s
def main():
M = 20
a = get_data()
b = list(map(cidr4_to_node, a))
cidrs, s = solution(b, M)
cidrs = sorted([make_cidr4(*x) for x in cidrs])
print(cidrs, s, sep='\n')
if __name__=='__main__':
import cProfile
main()
+59
View File
@@ -0,0 +1,59 @@
from .util import mask, get_data, cidr4_to_node, make_cidr4
def f(x, y):
t = x
b = y
if x[1]>y[1]:
t = y
b = x
ip1, l1, a1 = t
ip2, l2, a2 = b
if ip1 & mask[l1] == ip2 & mask[l1]:
return (0, t)
t1 = t2 = 0
while not l1==l2:
t2 += 2**(32-l2)
l2 -= 1
ip2 = ip2 & mask[l2]
while not ip1 & mask[l1-1] == ip2 & mask[l2-1]:
t1 += 2**(32-l1)
l1 -= 1
ip1 = ip1 & mask[l1]
t2 += 2**(32-l2)
l2 -= 1
ip2 = ip2 & mask[l2]
r = (ip1 & mask[l1-1], l1-1, a1+a2+t1+t2)
return (t1+t2, r)
def solution(cidrs, M):
cidrs = sorted((ip, l, 0) for ip, l in cidrs)
while len(cidrs)>M:
t = (None, float('+inf'), None)
for i, (x, y) in enumerate(zip(cidrs, cidrs[1:])):
m, r = f(x, y)
if m<t[1]:
t = (i, m, r)
if m==0:
break
cidrs[t[0]] = t[2]
del cidrs[t[0]+1]
s = sum(x[2] for x in cidrs)
cidrs = [x[:2] for x in cidrs]
return cidrs, s
def main():
M = 20
a = get_data()
b = list(map(cidr4_to_node, a))
cidrs, s = solution(b, M)
cidrs = sorted([make_cidr4(*x) for x in cidrs])
print(cidrs, s, sep='\n')
if __name__=='__main__':
import cProfile
main()
+20
View File
@@ -0,0 +1,20 @@
mask = [((1 << i) - 1) << (32 - i) for i in range(33)]
def get_data(input_file='cidr4.txt'):
with open(input_file, "r") as file:
return file.read().splitlines()
def cidr4_to_node(cidr4: str):
ip_address, mask_len = cidr4.strip().split("/")
mask_len = int(mask_len)
a, b, c, d = list(map(int, ip_address.split(".")))
ip = a * 256**3 + b * 256**2 + c * 256**1 + d * 256**0
return ip, mask_len
def make_cidr4(ip, mask_len) -> str:
lst = [str(ip >> (i << 3) & 0xFF) for i in reversed(range(4))]
ip_address = ".".join(lst)
return f"{ip_address}/{mask_len}"
View File
+70
View File
@@ -0,0 +1,70 @@
from vpn_manager.cidr4_merge.cidr4_merger import find_neighbors, merge_nodes, solution
def test_true():
assert True
def test_merge_nodes():
assert merge_nodes((0, 32), (1, 32)) == ((0, 31), 0)
assert merge_nodes((0, 32), (2, 32)) == ((0, 30), 2)
assert merge_nodes((0, 32), (5, 32)) == ((0, 29), 6)
assert merge_nodes((3, 32), (4, 32)) == ((0, 29), 6)
assert merge_nodes((0, 32), (4, 30)) == ((0, 29), 3)
assert merge_nodes((0, 32), (6, 31)) == ((0, 29), 5)
def test_find_neighbors():
assert find_neighbors([(0, 32), (2, 32), (4, 32), (6, 32)]) == []
assert find_neighbors([(0, 32), (1, 32)]) == [(0, (0, 32), (1, 32))]
assert find_neighbors(
[
(0, 32),
(1, 32),
(6, 32),
(7, 32),
]
) == [
(0, (0, 32), (1, 32)),
(2, (6, 32), (7, 32)),
]
assert find_neighbors(
[
(0, 32),
(1, 32),
(6, 32),
]
) == [
(0, (0, 32), (1, 32)),
]
def test_solution():
assert solution(
[
(0, 32),
(3, 32),
(4, 32),
],
2,
) == ([(0, 30), (4, 32)], 2)
assert solution(
[
(0, 32),
(3, 32),
(4, 32),
(7, 32),
],
2,
) == ([(0, 29)], 4)
assert solution(
[
(0, 32),
(3, 32),
(4, 32),
(7, 32),
],
1,
) == ([(0, 29)], 4)
+56
View File
@@ -0,0 +1,56 @@
from vpn_manager.cidr4_merge.fast import solution as fast
from vpn_manager.cidr4_merge.precise import solution as precise, f
from vpn_manager.cidr4_merge.util import *
def test_true():
assert True
#cidrs = list(map(cidr4_to_node, get_data()))
def test_fast_single_lifting():
assert ([(0, 30)], 0) ==\
fast([(0, 31), (2, 31)], 1)
assert ([(0, 29)], 2) ==\
fast([(0, 30), (4, 31)], 1)
assert ([(0, 29)], 3) ==\
fast([(0, 30), (4, 32)], 1)
def test_fast_double_lifting():
assert ([(0, 29)], 4) ==\
fast([(0, 31), (4, 31)], 1)
def test_fast_subnets():
assert ([(0, 30)], 0) ==\
fast([(0, 30), (0, 31)], 1)
assert ([(0, 29)], 0) ==\
fast([(0, 29), (4, 31)], 1)
def test_precise_single_lifting():
assert ([(0, 30)], 0) ==\
precise([(0, 31), (2, 31)], 1)
assert ([(0, 29)], 2) ==\
precise([(0, 30), (4, 31)], 1)
assert ([(0, 29)], 3) ==\
precise([(0, 30), (4, 32)], 1)
def test_precise_double_lifting():
assert ([(0, 29)], 4) ==\
precise([(0, 31), (4, 31)], 1)
def test_precise_subnets():
assert ([(0, 30)], 0) ==\
precise([(0, 30), (0, 31)], 1)
assert ([(0, 29)], 0) ==\
precise([(0, 29), (4, 31)], 1)
def test_precise_f_single_lifting():
assert (0, (0, 30, 0)) ==\
f((0, 31, 0), (2, 31, 0))
assert (2, (0, 29, 2)) ==\
f((0, 30, 0), (4, 31, 0))
assert (3, (0, 29, 3)) ==\
f((0, 30, 0), (4, 32, 0))
+30
View File
@@ -0,0 +1,30 @@
from vpn_manager.cidr4_merge.util import cidr4_to_node, make_cidr4
def test_true():
assert True
def test_cidr4_to_node():
assert cidr4_to_node("4.78.139.0/24") == (72256256, 24)
assert cidr4_to_node("0.0.0.0/32") == (0, 32)
assert cidr4_to_node("23.234.30.0/24") == (401219072, 24)
assert cidr4_to_node("172.217.0.0/19") == (2899902464, 19)
assert cidr4_to_node("23.225.141.0/24") == (400657664, 24)
assert cidr4_to_node("31.13.94.0/23") == (520969728, 23)
assert cidr4_to_node("0.0.0.0/2") == (0, 2)
assert cidr4_to_node("64.0.0.0/2") == (1073741824, 2)
assert cidr4_to_node("128.0.0.0/2") == (2147483648, 2)
assert cidr4_to_node("192.0.0.0/2") == (3221225472, 2)
def test_make_cidr4():
assert make_cidr4(72256256, 24) == "4.78.139.0/24"
assert make_cidr4(0, 32) == "0.0.0.0/32"
assert make_cidr4(401219072, 24) == "23.234.30.0/24"
assert make_cidr4(2899902464, 19) == "172.217.0.0/19"
assert make_cidr4(400657664, 24) == "23.225.141.0/24"
assert make_cidr4(520969728, 23) == "31.13.94.0/23"
Generated
+637
View File
@@ -0,0 +1,637 @@
version = 1
revision = 3
requires-python = ">=3.8"
resolution-markers = [
"python_full_version >= '3.10'",
"python_full_version == '3.9.*'",
"python_full_version >= '3.8.1' and python_full_version < '3.9'",
"python_full_version < '3.8.1'",
]
[[package]]
name = "black"
version = "24.8.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.8.1' and python_full_version < '3.9'",
"python_full_version < '3.8.1'",
]
dependencies = [
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "mypy-extensions", marker = "python_full_version < '3.9'" },
{ name = "packaging", marker = "python_full_version < '3.9'" },
{ name = "pathspec", marker = "python_full_version < '3.9'" },
{ name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "tomli", marker = "python_full_version < '3.9'" },
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/04/b0/46fb0d4e00372f4a86a6f8efa3cb193c9f64863615e39010b1477e010578/black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f", size = 644810, upload-time = "2024-08-02T17:43:18.405Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/47/6e/74e29edf1fba3887ed7066930a87f698ffdcd52c5dbc263eabb06061672d/black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6", size = 1632092, upload-time = "2024-08-02T17:47:26.911Z" },
{ url = "https://files.pythonhosted.org/packages/ab/49/575cb6c3faee690b05c9d11ee2e8dba8fbd6d6c134496e644c1feb1b47da/black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb", size = 1457529, upload-time = "2024-08-02T17:47:29.109Z" },
{ url = "https://files.pythonhosted.org/packages/7a/b4/d34099e95c437b53d01c4aa37cf93944b233066eb034ccf7897fa4e5f286/black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42", size = 1757443, upload-time = "2024-08-02T17:46:20.306Z" },
{ url = "https://files.pythonhosted.org/packages/87/a0/6d2e4175ef364b8c4b64f8441ba041ed65c63ea1db2720d61494ac711c15/black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a", size = 1418012, upload-time = "2024-08-02T17:47:20.33Z" },
{ url = "https://files.pythonhosted.org/packages/08/a6/0a3aa89de9c283556146dc6dbda20cd63a9c94160a6fbdebaf0918e4a3e1/black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1", size = 1615080, upload-time = "2024-08-02T17:48:05.467Z" },
{ url = "https://files.pythonhosted.org/packages/db/94/b803d810e14588bb297e565821a947c108390a079e21dbdcb9ab6956cd7a/black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af", size = 1438143, upload-time = "2024-08-02T17:47:30.247Z" },
{ url = "https://files.pythonhosted.org/packages/a5/b5/f485e1bbe31f768e2e5210f52ea3f432256201289fd1a3c0afda693776b0/black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4", size = 1738774, upload-time = "2024-08-02T17:46:17.837Z" },
{ url = "https://files.pythonhosted.org/packages/a8/69/a000fc3736f89d1bdc7f4a879f8aaf516fb03613bb51a0154070383d95d9/black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af", size = 1427503, upload-time = "2024-08-02T17:46:22.654Z" },
{ url = "https://files.pythonhosted.org/packages/a2/a8/05fb14195cfef32b7c8d4585a44b7499c2a4b205e1662c427b941ed87054/black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368", size = 1646132, upload-time = "2024-08-02T17:49:52.843Z" },
{ url = "https://files.pythonhosted.org/packages/41/77/8d9ce42673e5cb9988f6df73c1c5c1d4e9e788053cccd7f5fb14ef100982/black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed", size = 1448665, upload-time = "2024-08-02T17:47:54.479Z" },
{ url = "https://files.pythonhosted.org/packages/cc/94/eff1ddad2ce1d3cc26c162b3693043c6b6b575f538f602f26fe846dfdc75/black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018", size = 1762458, upload-time = "2024-08-02T17:46:19.384Z" },
{ url = "https://files.pythonhosted.org/packages/28/ea/18b8d86a9ca19a6942e4e16759b2fa5fc02bbc0eb33c1b866fcd387640ab/black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2", size = 1436109, upload-time = "2024-08-02T17:46:52.97Z" },
{ url = "https://files.pythonhosted.org/packages/9f/d4/ae03761ddecc1a37d7e743b89cccbcf3317479ff4b88cfd8818079f890d0/black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd", size = 1617322, upload-time = "2024-08-02T17:51:20.203Z" },
{ url = "https://files.pythonhosted.org/packages/14/4b/4dfe67eed7f9b1ddca2ec8e4418ea74f0d1dc84d36ea874d618ffa1af7d4/black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2", size = 1442108, upload-time = "2024-08-02T17:50:40.824Z" },
{ url = "https://files.pythonhosted.org/packages/97/14/95b3f91f857034686cae0e73006b8391d76a8142d339b42970eaaf0416ea/black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e", size = 1745786, upload-time = "2024-08-02T17:46:02.939Z" },
{ url = "https://files.pythonhosted.org/packages/95/54/68b8883c8aa258a6dde958cd5bdfada8382bec47c5162f4a01e66d839af1/black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920", size = 1426754, upload-time = "2024-08-02T17:46:38.603Z" },
{ url = "https://files.pythonhosted.org/packages/13/b2/b3f24fdbb46f0e7ef6238e131f13572ee8279b70f237f221dd168a9dba1a/black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c", size = 1631706, upload-time = "2024-08-02T17:49:57.606Z" },
{ url = "https://files.pythonhosted.org/packages/d9/35/31010981e4a05202a84a3116423970fd1a59d2eda4ac0b3570fbb7029ddc/black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e", size = 1457429, upload-time = "2024-08-02T17:49:12.764Z" },
{ url = "https://files.pythonhosted.org/packages/27/25/3f706b4f044dd569a20a4835c3b733dedea38d83d2ee0beb8178a6d44945/black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47", size = 1756488, upload-time = "2024-08-02T17:46:08.067Z" },
{ url = "https://files.pythonhosted.org/packages/63/72/79375cd8277cbf1c5670914e6bd4c1b15dea2c8f8e906dc21c448d0535f0/black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb", size = 1417721, upload-time = "2024-08-02T17:46:42.637Z" },
{ url = "https://files.pythonhosted.org/packages/27/1e/83fa8a787180e1632c3d831f7e58994d7aaf23a0961320d21e84f922f919/black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed", size = 206504, upload-time = "2024-08-02T17:43:15.747Z" },
]
[[package]]
name = "black"
version = "25.11.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.9.*'",
]
dependencies = [
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
{ name = "mypy-extensions", marker = "python_full_version == '3.9.*'" },
{ name = "packaging", marker = "python_full_version == '3.9.*'" },
{ name = "pathspec", marker = "python_full_version == '3.9.*'" },
{ name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
{ name = "pytokens", marker = "python_full_version == '3.9.*'" },
{ name = "tomli", marker = "python_full_version == '3.9.*'" },
{ name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8c/ad/33adf4708633d047950ff2dfdea2e215d84ac50ef95aff14a614e4b6e9b2/black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08", size = 655669, upload-time = "2025-11-10T01:53:50.558Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/d2/6caccbc96f9311e8ec3378c296d4f4809429c43a6cd2394e3c390e86816d/black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e", size = 1743501, upload-time = "2025-11-10T01:59:06.202Z" },
{ url = "https://files.pythonhosted.org/packages/69/35/b986d57828b3f3dccbf922e2864223197ba32e74c5004264b1c62bc9f04d/black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0", size = 1597308, upload-time = "2025-11-10T01:57:58.633Z" },
{ url = "https://files.pythonhosted.org/packages/39/8e/8b58ef4b37073f52b64a7b2dd8c9a96c84f45d6f47d878d0aa557e9a2d35/black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37", size = 1656194, upload-time = "2025-11-10T01:57:10.909Z" },
{ url = "https://files.pythonhosted.org/packages/8d/30/9c2267a7955ecc545306534ab88923769a979ac20a27cf618d370091e5dd/black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03", size = 1347996, upload-time = "2025-11-10T01:57:22.391Z" },
{ url = "https://files.pythonhosted.org/packages/c4/62/d304786b75ab0c530b833a89ce7d997924579fb7484ecd9266394903e394/black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a", size = 1727891, upload-time = "2025-11-10T02:01:40.507Z" },
{ url = "https://files.pythonhosted.org/packages/82/5d/ffe8a006aa522c9e3f430e7b93568a7b2163f4b3f16e8feb6d8c3552761a/black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170", size = 1581875, upload-time = "2025-11-10T01:57:51.192Z" },
{ url = "https://files.pythonhosted.org/packages/cb/c8/7c8bda3108d0bb57387ac41b4abb5c08782b26da9f9c4421ef6694dac01a/black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc", size = 1642716, upload-time = "2025-11-10T01:56:51.589Z" },
{ url = "https://files.pythonhosted.org/packages/34/b9/f17dea34eecb7cc2609a89627d480fb6caea7b86190708eaa7eb15ed25e7/black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e", size = 1352904, upload-time = "2025-11-10T01:59:26.252Z" },
{ url = "https://files.pythonhosted.org/packages/7f/12/5c35e600b515f35ffd737da7febdb2ab66bb8c24d88560d5e3ef3d28c3fd/black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac", size = 1772831, upload-time = "2025-11-10T02:03:47Z" },
{ url = "https://files.pythonhosted.org/packages/1a/75/b3896bec5a2bb9ed2f989a970ea40e7062f8936f95425879bbe162746fe5/black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96", size = 1608520, upload-time = "2025-11-10T01:58:46.895Z" },
{ url = "https://files.pythonhosted.org/packages/f3/b5/2bfc18330eddbcfb5aab8d2d720663cd410f51b2ed01375f5be3751595b0/black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd", size = 1682719, upload-time = "2025-11-10T01:56:55.24Z" },
{ url = "https://files.pythonhosted.org/packages/96/fb/f7dc2793a22cdf74a72114b5ed77fe3349a2e09ef34565857a2f917abdf2/black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409", size = 1362684, upload-time = "2025-11-10T01:57:07.639Z" },
{ url = "https://files.pythonhosted.org/packages/ad/47/3378d6a2ddefe18553d1115e36aea98f4a90de53b6a3017ed861ba1bd3bc/black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b", size = 1772446, upload-time = "2025-11-10T02:02:16.181Z" },
{ url = "https://files.pythonhosted.org/packages/ba/4b/0f00bfb3d1f7e05e25bfc7c363f54dc523bb6ba502f98f4ad3acf01ab2e4/black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd", size = 1607983, upload-time = "2025-11-10T02:02:52.502Z" },
{ url = "https://files.pythonhosted.org/packages/99/fe/49b0768f8c9ae57eb74cc10a1f87b4c70453551d8ad498959721cc345cb7/black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993", size = 1682481, upload-time = "2025-11-10T01:57:12.35Z" },
{ url = "https://files.pythonhosted.org/packages/55/17/7e10ff1267bfa950cc16f0a411d457cdff79678fbb77a6c73b73a5317904/black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c", size = 1363869, upload-time = "2025-11-10T01:58:24.608Z" },
{ url = "https://files.pythonhosted.org/packages/67/c0/cc865ce594d09e4cd4dfca5e11994ebb51604328489f3ca3ae7bb38a7db5/black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170", size = 1771358, upload-time = "2025-11-10T02:03:33.331Z" },
{ url = "https://files.pythonhosted.org/packages/37/77/4297114d9e2fd2fc8ab0ab87192643cd49409eb059e2940391e7d2340e57/black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545", size = 1612902, upload-time = "2025-11-10T01:59:33.382Z" },
{ url = "https://files.pythonhosted.org/packages/de/63/d45ef97ada84111e330b2b2d45e1dd163e90bd116f00ac55927fb6bf8adb/black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda", size = 1680571, upload-time = "2025-11-10T01:57:04.239Z" },
{ url = "https://files.pythonhosted.org/packages/ff/4b/5604710d61cdff613584028b4cb4607e56e148801ed9b38ee7970799dab6/black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664", size = 1382599, upload-time = "2025-11-10T01:57:57.427Z" },
{ url = "https://files.pythonhosted.org/packages/d5/9a/5b2c0e3215fe748fcf515c2dd34658973a1210bf610e24de5ba887e4f1c8/black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06", size = 1743063, upload-time = "2025-11-10T02:02:43.175Z" },
{ url = "https://files.pythonhosted.org/packages/a1/20/245164c6efc27333409c62ba54dcbfbe866c6d1957c9a6c0647786e950da/black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2", size = 1596867, upload-time = "2025-11-10T02:00:17.157Z" },
{ url = "https://files.pythonhosted.org/packages/ca/6f/1a3859a7da205f3d50cf3a8bec6bdc551a91c33ae77a045bb24c1f46ab54/black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc", size = 1655678, upload-time = "2025-11-10T01:57:09.028Z" },
{ url = "https://files.pythonhosted.org/packages/56/1a/6dec1aeb7be90753d4fcc273e69bc18bfd34b353223ed191da33f7519410/black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc", size = 1347452, upload-time = "2025-11-10T01:57:01.871Z" },
{ url = "https://files.pythonhosted.org/packages/00/5d/aed32636ed30a6e7f9efd6ad14e2a0b0d687ae7c8c7ec4e4a557174b895c/black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b", size = 204918, upload-time = "2025-11-10T01:53:48.917Z" },
]
[[package]]
name = "black"
version = "25.12.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
]
dependencies = [
{ name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "mypy-extensions", marker = "python_full_version >= '3.10'" },
{ name = "packaging", marker = "python_full_version >= '3.10'" },
{ name = "pathspec", marker = "python_full_version >= '3.10'" },
{ name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "pytokens", marker = "python_full_version >= '3.10'" },
{ name = "tomli", marker = "python_full_version == '3.10.*'" },
{ name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c4/d9/07b458a3f1c525ac392b5edc6b191ff140b596f9d77092429417a54e249d/black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7", size = 659264, upload-time = "2025-12-08T01:40:52.501Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/37/d5/8d3145999d380e5d09bb00b0f7024bf0a8ccb5c07b5648e9295f02ec1d98/black-25.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f85ba1ad15d446756b4ab5f3044731bf68b777f8f9ac9cdabd2425b97cd9c4e8", size = 1895720, upload-time = "2025-12-08T01:46:58.197Z" },
{ url = "https://files.pythonhosted.org/packages/06/97/7acc85c4add41098f4f076b21e3e4e383ad6ed0a3da26b2c89627241fc11/black-25.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546eecfe9a3a6b46f9d69d8a642585a6eaf348bcbbc4d87a19635570e02d9f4a", size = 1727193, upload-time = "2025-12-08T01:52:26.674Z" },
{ url = "https://files.pythonhosted.org/packages/24/f0/fdf0eb8ba907ddeb62255227d29d349e8256ef03558fbcadfbc26ecfe3b2/black-25.12.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17dcc893da8d73d8f74a596f64b7c98ef5239c2cd2b053c0f25912c4494bf9ea", size = 1774506, upload-time = "2025-12-08T01:46:25.721Z" },
{ url = "https://files.pythonhosted.org/packages/e4/f5/9203a78efe00d13336786b133c6180a9303d46908a9aa72d1104ca214222/black-25.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:09524b0e6af8ba7a3ffabdfc7a9922fb9adef60fed008c7cd2fc01f3048e6e6f", size = 1416085, upload-time = "2025-12-08T01:46:06.073Z" },
{ url = "https://files.pythonhosted.org/packages/ba/cc/7a6090e6b081c3316282c05c546e76affdce7bf7a3b7d2c3a2a69438bd01/black-25.12.0-cp310-cp310-win_arm64.whl", hash = "sha256:b162653ed89eb942758efeb29d5e333ca5bb90e5130216f8369857db5955a7da", size = 1226038, upload-time = "2025-12-08T01:45:29.388Z" },
{ url = "https://files.pythonhosted.org/packages/60/ad/7ac0d0e1e0612788dbc48e62aef8a8e8feffac7eb3d787db4e43b8462fa8/black-25.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0cfa263e85caea2cff57d8f917f9f51adae8e20b610e2b23de35b5b11ce691a", size = 1877003, upload-time = "2025-12-08T01:43:29.967Z" },
{ url = "https://files.pythonhosted.org/packages/e8/dd/a237e9f565f3617a88b49284b59cbca2a4f56ebe68676c1aad0ce36a54a7/black-25.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a2f578ae20c19c50a382286ba78bfbeafdf788579b053d8e4980afb079ab9be", size = 1712639, upload-time = "2025-12-08T01:52:46.756Z" },
{ url = "https://files.pythonhosted.org/packages/12/80/e187079df1ea4c12a0c63282ddd8b81d5107db6d642f7d7b75a6bcd6fc21/black-25.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e1b65634b0e471d07ff86ec338819e2ef860689859ef4501ab7ac290431f9b", size = 1758143, upload-time = "2025-12-08T01:45:29.137Z" },
{ url = "https://files.pythonhosted.org/packages/93/b5/3096ccee4f29dc2c3aac57274326c4d2d929a77e629f695f544e159bfae4/black-25.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a3fa71e3b8dd9f7c6ac4d818345237dfb4175ed3bf37cd5a581dbc4c034f1ec5", size = 1420698, upload-time = "2025-12-08T01:45:53.379Z" },
{ url = "https://files.pythonhosted.org/packages/7e/39/f81c0ffbc25ffbe61c7d0385bf277e62ffc3e52f5ee668d7369d9854fadf/black-25.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:51e267458f7e650afed8445dc7edb3187143003d52a1b710c7321aef22aa9655", size = 1229317, upload-time = "2025-12-08T01:46:35.606Z" },
{ url = "https://files.pythonhosted.org/packages/d1/bd/26083f805115db17fda9877b3c7321d08c647df39d0df4c4ca8f8450593e/black-25.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f96b7c98c1ddaeb07dc0f56c652e25bdedaac76d5b68a059d998b57c55594a", size = 1924178, upload-time = "2025-12-08T01:49:51.048Z" },
{ url = "https://files.pythonhosted.org/packages/89/6b/ea00d6651561e2bdd9231c4177f4f2ae19cc13a0b0574f47602a7519b6ca/black-25.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05dd459a19e218078a1f98178c13f861fe6a9a5f88fc969ca4d9b49eb1809783", size = 1742643, upload-time = "2025-12-08T01:49:59.09Z" },
{ url = "https://files.pythonhosted.org/packages/6d/f3/360fa4182e36e9875fabcf3a9717db9d27a8d11870f21cff97725c54f35b/black-25.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1f68c5eff61f226934be6b5b80296cf6939e5d2f0c2f7d543ea08b204bfaf59", size = 1800158, upload-time = "2025-12-08T01:44:27.301Z" },
{ url = "https://files.pythonhosted.org/packages/f8/08/2c64830cb6616278067e040acca21d4f79727b23077633953081c9445d61/black-25.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:274f940c147ddab4442d316b27f9e332ca586d39c85ecf59ebdea82cc9ee8892", size = 1426197, upload-time = "2025-12-08T01:45:51.198Z" },
{ url = "https://files.pythonhosted.org/packages/d4/60/a93f55fd9b9816b7432cf6842f0e3000fdd5b7869492a04b9011a133ee37/black-25.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:169506ba91ef21e2e0591563deda7f00030cb466e747c4b09cb0a9dae5db2f43", size = 1237266, upload-time = "2025-12-08T01:45:10.556Z" },
{ url = "https://files.pythonhosted.org/packages/c8/52/c551e36bc95495d2aa1a37d50566267aa47608c81a53f91daa809e03293f/black-25.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a05ddeb656534c3e27a05a29196c962877c83fa5503db89e68857d1161ad08a5", size = 1923809, upload-time = "2025-12-08T01:46:55.126Z" },
{ url = "https://files.pythonhosted.org/packages/a0/f7/aac9b014140ee56d247e707af8db0aae2e9efc28d4a8aba92d0abd7ae9d1/black-25.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ec77439ef3e34896995503865a85732c94396edcc739f302c5673a2315e1e7f", size = 1742384, upload-time = "2025-12-08T01:49:37.022Z" },
{ url = "https://files.pythonhosted.org/packages/74/98/38aaa018b2ab06a863974c12b14a6266badc192b20603a81b738c47e902e/black-25.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e509c858adf63aa61d908061b52e580c40eae0dfa72415fa47ac01b12e29baf", size = 1798761, upload-time = "2025-12-08T01:46:05.386Z" },
{ url = "https://files.pythonhosted.org/packages/16/3a/a8ac542125f61574a3f015b521ca83b47321ed19bb63fe6d7560f348bfe1/black-25.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:252678f07f5bac4ff0d0e9b261fbb029fa530cfa206d0a636a34ab445ef8ca9d", size = 1429180, upload-time = "2025-12-08T01:45:34.903Z" },
{ url = "https://files.pythonhosted.org/packages/e6/2d/bdc466a3db9145e946762d52cd55b1385509d9f9004fec1c97bdc8debbfb/black-25.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bc5b1c09fe3c931ddd20ee548511c64ebf964ada7e6f0763d443947fd1c603ce", size = 1239350, upload-time = "2025-12-08T01:46:09.458Z" },
{ url = "https://files.pythonhosted.org/packages/35/46/1d8f2542210c502e2ae1060b2e09e47af6a5e5963cb78e22ec1a11170b28/black-25.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0a0953b134f9335c2434864a643c842c44fba562155c738a2a37a4d61f00cad5", size = 1917015, upload-time = "2025-12-08T01:53:27.987Z" },
{ url = "https://files.pythonhosted.org/packages/41/37/68accadf977672beb8e2c64e080f568c74159c1aaa6414b4cd2aef2d7906/black-25.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2355bbb6c3b76062870942d8cc450d4f8ac71f9c93c40122762c8784df49543f", size = 1741830, upload-time = "2025-12-08T01:54:36.861Z" },
{ url = "https://files.pythonhosted.org/packages/ac/76/03608a9d8f0faad47a3af3a3c8c53af3367f6c0dd2d23a84710456c7ac56/black-25.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9678bd991cc793e81d19aeeae57966ee02909877cb65838ccffef24c3ebac08f", size = 1791450, upload-time = "2025-12-08T01:44:52.581Z" },
{ url = "https://files.pythonhosted.org/packages/06/99/b2a4bd7dfaea7964974f947e1c76d6886d65fe5d24f687df2d85406b2609/black-25.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:97596189949a8aad13ad12fcbb4ae89330039b96ad6742e6f6b45e75ad5cfd83", size = 1452042, upload-time = "2025-12-08T01:46:13.188Z" },
{ url = "https://files.pythonhosted.org/packages/b2/7c/d9825de75ae5dd7795d007681b752275ea85a1c5d83269b4b9c754c2aaab/black-25.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:778285d9ea197f34704e3791ea9404cd6d07595745907dd2ce3da7a13627b29b", size = 1267446, upload-time = "2025-12-08T01:46:14.497Z" },
{ url = "https://files.pythonhosted.org/packages/68/11/21331aed19145a952ad28fca2756a1433ee9308079bd03bd898e903a2e53/black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828", size = 206191, upload-time = "2025-12-08T01:40:50.963Z" },
]
[[package]]
name = "click"
version = "8.1.8"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.9.*'",
"python_full_version >= '3.8.1' and python_full_version < '3.9'",
"python_full_version < '3.8.1'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
]
[[package]]
name = "click"
version = "8.3.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "exceptiongroup"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
]
[[package]]
name = "flake8"
version = "5.0.4"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.8.1'",
]
dependencies = [
{ name = "mccabe", marker = "python_full_version < '3.8.1'" },
{ name = "pycodestyle", version = "2.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8.1'" },
{ name = "pyflakes", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8.1'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/00/9808c62b2d529cefc69ce4e4a1ea42c0f855effa55817b7327ec5b75e60a/flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db", size = 145862, upload-time = "2022-08-03T23:21:27.108Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cf/a0/b881b63a17a59d9d07f5c0cc91a29182c8e8a9aa2bde5b3b2b16519c02f4/flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248", size = 61897, upload-time = "2022-08-03T23:21:25.027Z" },
]
[[package]]
name = "flake8"
version = "7.1.2"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.8.1' and python_full_version < '3.9'",
]
dependencies = [
{ name = "mccabe", marker = "python_full_version >= '3.8.1' and python_full_version < '3.9'" },
{ name = "pycodestyle", version = "2.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8.1' and python_full_version < '3.9'" },
{ name = "pyflakes", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8.1' and python_full_version < '3.9'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/58/16/3f2a0bb700ad65ac9663262905a025917c020a3f92f014d2ba8964b4602c/flake8-7.1.2.tar.gz", hash = "sha256:c586ffd0b41540951ae41af572e6790dbd49fc12b3aa2541685d253d9bd504bd", size = 48119, upload-time = "2025-02-16T18:45:44.296Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/35/f8/08d37b2cd89da306e3520bd27f8a85692122b42b56c0c2c3784ff09c022f/flake8-7.1.2-py2.py3-none-any.whl", hash = "sha256:1cbc62e65536f65e6d754dfe6f1bada7f5cf392d6f5db3c2b85892466c3e7c1a", size = 57745, upload-time = "2025-02-16T18:45:42.351Z" },
]
[[package]]
name = "flake8"
version = "7.3.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
"python_full_version == '3.9.*'",
]
dependencies = [
{ name = "mccabe", marker = "python_full_version >= '3.9'" },
{ name = "pycodestyle", version = "2.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "pyflakes", version = "3.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" },
]
[[package]]
name = "iniconfig"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.9.*'",
"python_full_version >= '3.8.1' and python_full_version < '3.9'",
"python_full_version < '3.8.1'",
]
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
]
[[package]]
name = "iniconfig"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
]
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
]
[[package]]
name = "mccabe"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
]
[[package]]
name = "platformdirs"
version = "4.3.6"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.8.1' and python_full_version < '3.9'",
"python_full_version < '3.8.1'",
]
sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302, upload-time = "2024-09-17T19:06:50.688Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439, upload-time = "2024-09-17T19:06:49.212Z" },
]
[[package]]
name = "platformdirs"
version = "4.4.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.9.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" },
]
[[package]]
name = "platformdirs"
version = "4.5.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
]
sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
]
[[package]]
name = "pluggy"
version = "1.5.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.8.1' and python_full_version < '3.9'",
"python_full_version < '3.8.1'",
]
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
"python_full_version == '3.9.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]]
name = "pycodestyle"
version = "2.9.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.8.1'",
]
sdist = { url = "https://files.pythonhosted.org/packages/b6/83/5bcaedba1f47200f0665ceb07bcb00e2be123192742ee0edfb66b600e5fd/pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785", size = 102127, upload-time = "2022-08-03T23:13:29.715Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/67/e4/fc77f1039c34b3612c4867b69cbb2b8a4e569720b1f19b0637002ee03aff/pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b", size = 41493, upload-time = "2022-08-03T23:13:27.416Z" },
]
[[package]]
name = "pycodestyle"
version = "2.12.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.8.1' and python_full_version < '3.9'",
]
sdist = { url = "https://files.pythonhosted.org/packages/43/aa/210b2c9aedd8c1cbeea31a50e42050ad56187754b34eb214c46709445801/pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521", size = 39232, upload-time = "2024-08-04T20:26:54.576Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/d8/a211b3f85e99a0daa2ddec96c949cac6824bd305b040571b82a03dd62636/pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3", size = 31284, upload-time = "2024-08-04T20:26:53.173Z" },
]
[[package]]
name = "pycodestyle"
version = "2.14.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
"python_full_version == '3.9.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" },
]
[[package]]
name = "pyflakes"
version = "2.5.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.8.1'",
]
sdist = { url = "https://files.pythonhosted.org/packages/07/92/f0cb5381f752e89a598dd2850941e7f570ac3cb8ea4a344854de486db152/pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3", size = 66388, upload-time = "2022-07-30T17:29:05.816Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/13/63178f59f74e53acc2165aee4b002619a3cfa7eeaeac989a9eb41edf364e/pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2", size = 66116, upload-time = "2022-07-30T17:29:04.179Z" },
]
[[package]]
name = "pyflakes"
version = "3.2.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.8.1' and python_full_version < '3.9'",
]
sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788, upload-time = "2024-01-05T00:28:47.703Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725, upload-time = "2024-01-05T00:28:45.903Z" },
]
[[package]]
name = "pyflakes"
version = "3.4.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
"python_full_version == '3.9.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" },
]
[[package]]
name = "pygments"
version = "2.19.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
[[package]]
name = "pytest"
version = "8.3.5"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.8.1' and python_full_version < '3.9'",
"python_full_version < '3.8.1'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version < '3.9' and sys_platform == 'win32'" },
{ name = "exceptiongroup", marker = "python_full_version < '3.9'" },
{ name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "packaging", marker = "python_full_version < '3.9'" },
{ name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "tomli", marker = "python_full_version < '3.9'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" },
]
[[package]]
name = "pytest"
version = "8.4.2"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.9.*'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version == '3.9.*' and sys_platform == 'win32'" },
{ name = "exceptiongroup", marker = "python_full_version == '3.9.*'" },
{ name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
{ name = "packaging", marker = "python_full_version == '3.9.*'" },
{ name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
{ name = "pygments", marker = "python_full_version == '3.9.*'" },
{ name = "tomli", marker = "python_full_version == '3.9.*'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
]
[[package]]
name = "pytest"
version = "9.0.2"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
{ name = "exceptiongroup", marker = "python_full_version == '3.10.*'" },
{ name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "packaging", marker = "python_full_version >= '3.10'" },
{ name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "pygments", marker = "python_full_version >= '3.10'" },
{ name = "tomli", marker = "python_full_version == '3.10.*'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
]
[[package]]
name = "pytokens"
version = "0.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" },
]
[[package]]
name = "tomli"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" },
{ url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" },
{ url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" },
{ url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" },
{ url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" },
{ url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" },
{ url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" },
{ url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" },
{ url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" },
{ url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" },
{ url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" },
{ url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" },
{ url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" },
{ url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" },
{ url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" },
{ url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" },
{ url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" },
{ url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" },
{ url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" },
{ url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" },
{ url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" },
{ url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" },
{ url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" },
{ url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" },
{ url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" },
{ url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" },
{ url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" },
{ url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" },
{ url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" },
{ url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" },
{ url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" },
{ url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" },
{ url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" },
{ url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" },
{ url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" },
{ url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" },
{ url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" },
{ url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" },
{ url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" },
{ url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" },
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
]
[[package]]
name = "typing-extensions"
version = "4.13.2"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.8.1' and python_full_version < '3.9'",
"python_full_version < '3.8.1'",
]
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
"python_full_version == '3.9.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "wcm"
version = "2024.12.0a0"
source = { editable = "." }
dependencies = [
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
]
[package.dev-dependencies]
dev = [
{ name = "black", version = "24.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "black", version = "25.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
{ name = "black", version = "25.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "flake8", version = "5.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.8.1'" },
{ name = "flake8", version = "7.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.8.1' and python_full_version < '3.9'" },
{ name = "flake8", version = "7.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
{ name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
]
[package.metadata]
requires-dist = [{ name = "click", specifier = ">=8.1" }]
[package.metadata.requires-dev]
dev = [
{ name = "black" },
{ name = "flake8" },
{ name = "pytest" },
]