├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── Makefile ├── README.md ├── dnsgen ├── __init__.py ├── cli.py ├── dnsgen.py └── words.txt ├── pyproject.toml ├── tests ├── test_cli.py └── test_dnsgen.py └── uv.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # IPython Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | venv/ 85 | ENV/ 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | *.npy 93 | *.pkl 94 | 95 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: 'https://github.com/pre-commit/pre-commit-hooks' 3 | rev: v5.0.0 4 | hooks: 5 | - id: end-of-file-fixer 6 | - id: trailing-whitespace 7 | - id: check-added-large-files 8 | - id: check-case-conflict 9 | - id: check-ast 10 | - id: check-toml 11 | - id: check-symlinks 12 | - id: check-xml 13 | - id: check-yaml 14 | - repo: https://github.com/astral-sh/ruff-pre-commit 15 | rev: v0.8.5 16 | hooks: 17 | # Run the linter. 18 | - id: ruff 19 | args: [ --fix ] 20 | # Run the formatter. 21 | - id: ruff-format 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Aleph Null s.r.o. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: lint check dev lint-check 2 | 3 | lint-check: 4 | uv run ruff check . 5 | uv run ruff format --check . 6 | uv run mypy --show-error-codes --pretty dnsgen/ 7 | 8 | lint: 9 | bash .git/hooks/pre-commit 10 | uv run ruff format ./dnsgen 11 | uv run ruff check --fix ./dnsgen 12 | 13 | dev: 14 | uv pip install . --extra dev 15 | uv run pre-commit install -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DNSGen 2.0 - Advanced DNS Name Permutation Engine 🚀 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![Python 3.9+](https://img.shields.io/badge/python-3.7+-blue.svg)](https://www.python.org/downloads/) 5 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 6 | 7 | DNSGen is a powerful and flexible DNS name permutation tool designed for security researchers and penetration testers. It generates intelligent domain name variations to assist in subdomain discovery and security assessments. 8 | 9 | ![DNSGen Banner](https://0xpatrik.com/content/images/2019/09/dnsgen-1.png) 10 | 11 | ## ✨ Features 12 | 13 | - 🔍 Smart domain name permutation engine 14 | - 🚄 Fast generation mode for quick assessments 15 | - 📝 Support for custom wordlists with comments 16 | - 🎯 Intelligent word extraction from existing domains 17 | - 🔧 Multiple permutation techniques 18 | - 🌍 Cloud-aware patterns and modern naming conventions 19 | 20 | ## 🚀 Quick Start 21 | 22 | ### Installation 23 | 24 | ```bash 25 | # Using pip 26 | python -m pip install dnsgen 27 | 28 | # Using uv (recommended for development) 29 | git clone https://github.com/AlephNullSK/dnsgen 30 | cd dnsgen/ 31 | python -m pip install uv 32 | uv sync 33 | ``` 34 | 35 | ### Basic Usage 36 | 37 | ```bash 38 | # Basic domain permutation 39 | dnsgen domains.txt 40 | 41 | # With custom wordlist and output file 42 | dnsgen -w custom_wordlist.txt -o results.txt domains.txt 43 | 44 | # Using fast mode for quick assessment 45 | dnsgen -f domains.txt 46 | 47 | # Pipe with massdns for resolution 48 | cat domains.txt | dnsgen - | massdns -r resolvers.txt -t A -o J --flush 2>/dev/null 49 | ``` 50 | 51 | ## 🛠️ Permutation Techniques 52 | 53 | DNSGen 2.0 implements multiple sophisticated permutation techniques: 54 | 55 | ### Core Permutators 56 | 57 | 1. **Word Insertion** 58 | - Inserts words between domain levels 59 | - Example: `api.example.com` → `staging.api.example.com` 60 | 61 | 2. **Number Manipulation** 62 | - Intelligently modifies existing numbers 63 | - Example: `api2.example.com` → `api1.example.com`, `api3.example.com` 64 | 65 | 3. **Word Affixing** 66 | - Prepends/appends words to levels 67 | - Example: `api.example.com` → `devapi.example.com`, `api-dev.example.com` 68 | 69 | ### Cloud & Modern Infrastructure Permutators 70 | 71 | 4. **Cloud Provider Patterns** 72 | - Adds cloud-specific naming patterns 73 | - Example: `example.com` → `api-aws.example.com`, `storage-azure.example.com` 74 | 75 | 5. **Region Prefixes** 76 | - Adds geographical region patterns 77 | - Example: `api.example.com` → `us-east.api.example.com` 78 | 79 | 6. **Microservice Patterns** 80 | - Generates microservice-style names 81 | - Example: `example.com` → `auth-service.example.com`, `user-api.example.com` 82 | 83 | ### DevOps & Tooling Permutators 84 | 85 | 7. **Internal Tooling** 86 | - Adds common internal tool subdomains 87 | - Example: `example.com` → `jenkins.internal.example.com` 88 | 89 | 8. **Port Prefixing** 90 | - Adds common port numbers 91 | - Example: `api.example.com` → `8080.api.example.com` 92 | 93 | ## 📋 Command Line Options 94 | 95 | ```bash 96 | dnsgen [OPTIONS] FILENAME 97 | 98 | Options: 99 | -l, --wordlen INTEGER Min length of custom words (default: 6) 100 | -w, --wordlist PATH Path to custom wordlist 101 | -f, --fast Fast generation mode 102 | -o, --output PATH Output file path 103 | -v, --verbose Enable verbose logging 104 | --help Show this message and exit 105 | ``` 106 | 107 | ## 🔧 Advanced Usage 108 | 109 | ### Custom Wordlists 110 | 111 | DNSGen 2.0 supports commented wordlists for better organization: 112 | 113 | ```text 114 | # Environment Names 115 | dev 116 | staging 117 | prod 118 | 119 | # Cloud Providers 120 | aws 121 | azure 122 | gcp 123 | 124 | # Tools and Services 125 | jenkins 126 | gitlab 127 | grafana 128 | ``` 129 | 130 | ### Integration with MassDNS 131 | 132 | Get clean resolved domains: 133 | ```bash 134 | # Generate and resolve 135 | dnsgen hosts.txt > wordlist.txt 136 | massdns -r resolvers.txt -o S wordlist.txt | grep -e ' A ' | \ 137 | cut -d 'A' -f 1 | rev | cut -d "." -f1 --complement | \ 138 | rev | sort | uniq > resolved_domains.txt 139 | ``` 140 | 141 | ## 🤝 Contributing 142 | 143 | Contributions are welcome! Here's how you can help: 144 | 145 | 1. Fork the repository 146 | 2. Create a feature branch: `git checkout -b feature/amazing-feature` 147 | 3. Commit your changes: `git commit -m 'Add amazing feature'` 148 | 4. Push to the branch: `git push origin feature/amazing-feature` 149 | 5. Open a Pull Request 150 | 151 | See [CONTRIBUTING.md](CONTRIBUTING.md) for more details. 152 | 153 | ## 📚 Resources 154 | 155 | - [Subdomain Enumeration: 2019 Workflow](https://0xpatrik.com/subdomain-enumeration-2019/) 156 | - [Subdomain Enumeration: Doing it a Bit Smarter](https://0xpatrik.com/subdomain-enumeration-smarter/) 157 | - [Project Documentation](docs/README.md) 158 | 159 | ## 📜 License 160 | 161 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 162 | 163 | ## 🙏 Acknowledgments 164 | 165 | - Original concept by [Aleph Null s.r.o.](https://alephnull.sk) 166 | - Inspired by [altdns](https://github.com/infosec-au/altdns) 167 | - [massdns](https://github.com/blechschmidt/massdns) for DNS resolution 168 | 169 | ## 📊 Project Status 170 | 171 | - ✅ Core functionality complete 172 | - 🏗️ Adding more permutation techniques 173 | - 📝 Improving documentation 174 | - 🧪 Adding tests 175 | 176 | --- 177 | 178 |

Made with ❤️ by the security community

-------------------------------------------------------------------------------- /dnsgen/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | DNSGen - DNS name permutation generator 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | A tool for generating DNS name variations for domain discovery and security testing. 6 | 7 | Basic usage: 8 | >>> from dnsgen import DomainGenerator, create_generator 9 | >>> generator = create_generator() 10 | >>> domains = ["api.example.com"] 11 | >>> variations = list(generator.generate(domains)) 12 | 13 | The package provides several ways to generate domain variations: 14 | 1. Using the DomainGenerator class directly 15 | 2. Using the create_generator factory function 16 | 3. Using the generate convenience function for simple cases 17 | """ 18 | 19 | from typing import List, Iterator, Optional 20 | import warnings 21 | 22 | from .dnsgen import ( 23 | DomainGenerator, 24 | create_generator, 25 | DomainPartsType, 26 | PermutatorFunc, 27 | ) 28 | 29 | __author__ = "Aleph Null s.r.o." 30 | __all__ = [ 31 | "DomainGenerator", 32 | "create_generator", 33 | "generate", 34 | "DomainPartsType", 35 | "PermutatorFunc", 36 | ] 37 | 38 | 39 | def generate( 40 | domains: List[str], 41 | wordlist_path: Optional[str] = None, 42 | wordlen: int = 5, 43 | fast_mode: bool = False, 44 | ) -> Iterator[str]: 45 | """ 46 | Convenience function to generate domain variations without explicit generator creation. 47 | 48 | Args: 49 | domains: List of domain names to generate variations from 50 | wordlist_path: Optional path to custom wordlist file 51 | wordlen: Minimum length for custom word extraction 52 | fast_mode: Whether to use fast generation mode 53 | 54 | Returns: 55 | Iterator yielding generated domain variations 56 | 57 | Examples: 58 | >>> from dnsgen import generate 59 | >>> domains = ["api.example.com"] 60 | >>> variations = list(generate(domains)) 61 | 62 | # With custom wordlist and options 63 | >>> variations = list(generate( 64 | ... domains, 65 | ... wordlist_path="custom_words.txt", 66 | ... wordlen=4, 67 | ... fast_mode=True 68 | ... )) 69 | """ 70 | generator = create_generator(wordlist_path) 71 | yield from generator.generate(domains, wordlen=wordlen, fast_mode=fast_mode) 72 | 73 | 74 | # Version compatibility check 75 | import sys 76 | 77 | if sys.version_info < (3, 7): 78 | warnings.warn( 79 | "DNSGen requires Python 3.7 or higher for full functionality. " 80 | "Some features may not work correctly.", 81 | RuntimeWarning 82 | ) 83 | 84 | # Type checking availability notification 85 | try: 86 | from typing import Protocol 87 | 88 | TYPING_EXTENSIONS_AVAILABLE = True 89 | except ImportError: 90 | TYPING_EXTENSIONS_AVAILABLE = False 91 | warnings.warn( 92 | "typing_extensions not found. Type checking capabilities will be limited.", 93 | ImportWarning 94 | ) -------------------------------------------------------------------------------- /dnsgen/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | DNSGen - DNS name permutation generator CLI. 4 | Generates variations of domain names for discovery and security testing. 5 | """ 6 | 7 | import logging 8 | import sys 9 | from pathlib import Path 10 | from typing import TextIO, Set, Optional 11 | 12 | import click 13 | from rich.console import Console 14 | from rich.logging import RichHandler 15 | from rich.progress import Progress, SpinnerColumn, TextColumn 16 | 17 | from . import dnsgen 18 | 19 | # Configure logging with rich 20 | logging.basicConfig(level=logging.INFO, format="%(message)s", handlers=[RichHandler(rich_tracebacks=True)]) 21 | logger = logging.getLogger("dnsgen") 22 | console = Console() 23 | 24 | 25 | def validate_wordlen(ctx: click.Context, param: click.Parameter, value: int) -> int: 26 | """Validate the word length parameter.""" 27 | if value < 1 or value > 100: 28 | raise click.BadParameter("Word length must be between 1 and 100") 29 | return value 30 | 31 | 32 | def setup_generator(wordlist: Optional[Path], wordlen: int, fast: bool) -> dnsgen.DomainGenerator: 33 | """ 34 | Set up and configure the domain generator. 35 | 36 | Args: 37 | wordlist: Optional path to custom wordlist 38 | wordlen: Minimum length for word extraction 39 | fast: Whether to use fast generation mode 40 | 41 | Returns: 42 | Configured DomainGenerator instance 43 | """ 44 | try: 45 | generator = dnsgen.create_generator(str(wordlist) if wordlist else None) 46 | logger.info("Generator initialized successfully") 47 | return generator 48 | except Exception as e: 49 | logger.error(f"Failed to initialize generator: {e}") 50 | sys.exit(1) 51 | 52 | 53 | def process_domains(domains: Set[str], generator: dnsgen.DomainGenerator, wordlen: int, fast: bool) -> Set[str]: 54 | """ 55 | Process input domains and generate variations. 56 | 57 | Args: 58 | domains: Set of input domains 59 | generator: Configured DomainGenerator instance 60 | wordlen: Minimum length for word extraction 61 | fast: Whether to use fast generation mode 62 | 63 | Returns: 64 | Set of generated domain variations 65 | """ 66 | with Progress( 67 | SpinnerColumn(), 68 | TextColumn("[progress.description]{task.description}"), 69 | console=console, 70 | ) as progress: 71 | task = progress.add_task("Generating domain variations...", total=None) 72 | 73 | try: 74 | generated = set(generator.generate(list(domains), wordlen=wordlen, fast_mode=fast)) 75 | progress.update(task, completed=True) 76 | return generated 77 | except Exception as e: 78 | logger.error(f"Error during domain generation: {e}") 79 | sys.exit(1) 80 | 81 | 82 | def write_output(domains: Set[str], output_file: Optional[Path]) -> None: 83 | """ 84 | Write generated domains to output file or stdout. 85 | 86 | Args: 87 | domains: Set of generated domains 88 | output_file: Optional path to output file 89 | """ 90 | try: 91 | if output_file: 92 | with output_file.open("w") as f: 93 | for domain in sorted(domains): 94 | f.write(f"{domain}\n") 95 | logger.info(f"Results written to {output_file}") 96 | else: 97 | for domain in sorted(domains): 98 | click.echo(domain) 99 | except Exception as e: 100 | logger.error(f"Error writing output: {e}") 101 | sys.exit(1) 102 | 103 | 104 | @click.command(help="Generate DNS name permutations for domain discovery.") 105 | @click.option( 106 | "-l", 107 | "--wordlen", 108 | default=6, 109 | help="Minimum length of custom words extracted from domains.", 110 | callback=validate_wordlen, 111 | show_default=True, 112 | type=int, 113 | ) 114 | @click.option( 115 | "-w", 116 | "--wordlist", 117 | help="Path to custom wordlist file.", 118 | type=click.Path(exists=True, readable=True, path_type=Path), 119 | ) 120 | @click.option( 121 | "-f", 122 | "--fast", 123 | is_flag=True, 124 | help="Use fast generation mode (fewer permutations).", 125 | ) 126 | @click.option( 127 | "-o", 128 | "--output", 129 | help="Output file path.", 130 | type=click.Path(writable=True, path_type=Path), 131 | ) 132 | @click.option( 133 | "-v", 134 | "--verbose", 135 | is_flag=True, 136 | help="Enable verbose logging.", 137 | ) 138 | @click.argument( 139 | "input_file", 140 | type=click.File(mode="r"), 141 | ) 142 | def main( 143 | wordlen: int, 144 | wordlist: Optional[Path], 145 | input_file: TextIO, 146 | fast: bool, 147 | output: Optional[Path], 148 | verbose: bool, 149 | ) -> None: 150 | """ 151 | DNSGen CLI main function. 152 | 153 | Reads domains from input file, generates variations, and outputs results. 154 | """ 155 | if verbose: 156 | logger.setLevel(logging.DEBUG) 157 | 158 | # Read input domains 159 | try: 160 | input_domains = {line.strip() for line in input_file if line.strip()} 161 | logger.info(f"Read {len(input_domains)} domains from input file") 162 | except Exception as e: 163 | logger.error(f"Error reading input file: {e}") 164 | sys.exit(1) 165 | 166 | # Setup generator and process domains 167 | generator = setup_generator(wordlist, wordlen, fast) 168 | generated_domains = process_domains(input_domains, generator, wordlen, fast) 169 | 170 | # Output results 171 | logger.info(f"Generated {len(generated_domains)} unique domain variations") 172 | write_output(generated_domains, output) 173 | 174 | 175 | if __name__ == "__main__": 176 | main() 177 | -------------------------------------------------------------------------------- /dnsgen/dnsgen.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import annotations 3 | 4 | import itertools 5 | import pathlib 6 | from typing import Callable, List, Set, Iterator, Optional 7 | from dataclasses import dataclass 8 | import re 9 | 10 | import tldextract 11 | from tldextract.tldextract import ExtractResult 12 | from dataclasses import field 13 | 14 | # Type aliases 15 | DomainPartsType = List[str] 16 | PermutatorFunc = Callable[[DomainPartsType], List[str]] 17 | 18 | 19 | @dataclass 20 | class DomainGenerator: 21 | """Main class for handling domain name permutations.""" 22 | 23 | words: List[str] 24 | num_count: int = 3 25 | permutators: List[PermutatorFunc] = field(default_factory=list) 26 | fast_permutators: List[PermutatorFunc] = field(default_factory=list) 27 | 28 | def __post_init__(self) -> None: 29 | """Initialize permutator registries.""" 30 | if self.permutators is None: 31 | self.permutators = [] 32 | if self.fast_permutators is None: 33 | self.fast_permutators = [] 34 | 35 | def register_permutator(self, fast: bool = False) -> Callable[[PermutatorFunc], PermutatorFunc]: 36 | """ 37 | Decorator to register domain permutation functions. 38 | 39 | Args: 40 | fast: If True, the permutator is registered for fast generation mode 41 | 42 | Returns: 43 | Decorator function that registers the permutator 44 | """ 45 | 46 | def decorator(func: PermutatorFunc) -> PermutatorFunc: 47 | if fast: 48 | self.fast_permutators.append(func) 49 | self.permutators.append(func) 50 | return func 51 | 52 | return decorator 53 | 54 | def partiate_domain(self, domain: str) -> DomainPartsType: 55 | """ 56 | Split domain based on subdomain levels. 57 | Root+TLD is taken as one part, regardless of its levels. 58 | 59 | Args: 60 | domain: Domain name to split 61 | 62 | Returns: 63 | List of domain parts 64 | 65 | Example: 66 | >>> partiate_domain("test.1.foo.example.com") 67 | ['test', '1', 'foo', 'example.com'] 68 | """ 69 | ext: ExtractResult = tldextract.extract(domain.lower()) 70 | parts: DomainPartsType = ext.subdomain.split(".") + [ext.registered_domain] 71 | return parts 72 | 73 | def extract_custom_words(self, domains: List[str], wordlen: int) -> Set[str]: 74 | """ 75 | Extract custom words from domain names based on naming conventions. 76 | 77 | Args: 78 | domains: List of domain names to analyze 79 | wordlen: Minimum length of words to extract 80 | 81 | Returns: 82 | Set of extracted words meeting the length criterion 83 | """ 84 | valid_tokens: Set[str] = set() 85 | 86 | for domain in domains: 87 | partition = self.partiate_domain(domain)[:-1] 88 | tokens = set(itertools.chain(*[word.lower().split("-") for word in partition])) 89 | tokens = tokens.union({word.lower() for word in partition}) 90 | valid_tokens.update({t for t in tokens if len(t) >= wordlen}) 91 | 92 | return valid_tokens 93 | 94 | @property 95 | def active_permutators(self) -> List[PermutatorFunc]: 96 | """Get the list of currently active permutators.""" 97 | return self.fast_permutators if self.fast_mode else self.permutators 98 | 99 | def generate(self, domains: List[str], wordlen: int = 5, fast_mode: bool = False) -> Iterator[str]: 100 | """ 101 | Generate domain permutations from provided domains. 102 | 103 | Args: 104 | domains: List of base domains to permutate 105 | wordlen: Minimum length for custom word extraction 106 | fast_mode: If True, use only fast permutators 107 | 108 | Yields: 109 | Generated domain variations 110 | """ 111 | self.fast_mode = fast_mode 112 | 113 | for domain in set(domains): 114 | parts = self.partiate_domain(domain) 115 | for permutator in self.active_permutators: 116 | yield from permutator(parts) 117 | 118 | 119 | def create_generator(wordlist_path: Optional[str | pathlib.Path] = None) -> DomainGenerator: 120 | """ 121 | Create and initialize a DomainGenerator instance. 122 | 123 | Args: 124 | wordlist_path: Optional path to custom wordlist file 125 | 126 | Returns: 127 | Configured DomainGenerator instance 128 | """ 129 | if wordlist_path is None: 130 | wordlist_path = pathlib.Path(__file__).parent / "words.txt" 131 | 132 | # Convert to Path object if string 133 | if isinstance(wordlist_path, str): 134 | wordlist_path = pathlib.Path(wordlist_path) 135 | 136 | with open(wordlist_path) as f: 137 | # Filter out comments (lines starting with #) and empty lines 138 | lines = f.read().splitlines() 139 | # Filter out comments and empty lines 140 | words = [line.strip() for line in lines if line.strip() and not line.strip().startswith("#")] 141 | 142 | generator = DomainGenerator(words=words) 143 | 144 | # Register permutators 145 | @generator.register_permutator() 146 | def insert_word_every_index(parts: DomainPartsType) -> List[str]: 147 | """ 148 | Insert words between existing domain levels. 149 | 150 | Example: 151 | Input: "api.example.com" 152 | Output: "staging.api.example.com" 153 | "api.staging.example.com" 154 | "admin.api.example.com" 155 | "api.admin.example.com" 156 | """ 157 | domains = [] 158 | for w in generator.words: 159 | for i in range(len(parts)): 160 | tmp_parts = parts[:-1] 161 | tmp_parts.insert(i, w) 162 | domains.append(".".join(tmp_parts + [parts[-1]])) 163 | return domains 164 | 165 | @generator.register_permutator(fast=True) 166 | def modify_numbers(parts: DomainPartsType) -> List[str]: 167 | """ 168 | Increase and decrease numbers found in domain parts. 169 | 170 | Example: 171 | Input: "api2.example.com" 172 | Output: "api1.example.com" 173 | "api3.example.com" 174 | "api4.example.com" 175 | 176 | Input: "v2.api.example.com" 177 | Output: "v1.api.example.com" 178 | "v3.api.example.com" 179 | "v4.api.example.com" 180 | """ 181 | domains = [] 182 | parts_joined = ".".join(parts[:-1]) 183 | digits = re.findall(r"\d{1,3}", parts_joined) 184 | 185 | for d in digits: 186 | # Increase numbers 187 | for m in range(generator.num_count): 188 | replacement = str(int(d) + 1 + m).zfill(len(d)) 189 | tmp_domain = parts_joined.replace(d, replacement) 190 | domains.append(f"{tmp_domain}.{parts[-1]}") 191 | 192 | # Decrease numbers 193 | for m in range(generator.num_count): 194 | new_digit = int(d) - 1 - m 195 | if new_digit >= 0: 196 | replacement = str(new_digit).zfill(len(d)) 197 | tmp_domain = parts_joined.replace(d, replacement) 198 | domains.append(f"{tmp_domain}.{parts[-1]}") 199 | 200 | return domains 201 | 202 | @generator.register_permutator() 203 | def environment_prefix(parts: DomainPartsType) -> List[str]: 204 | """ 205 | Add common environment prefixes to domain parts. 206 | 207 | Example: 208 | Input: "api.example.com" 209 | Output: "dev.api.example.com" 210 | "staging.api.example.com" 211 | "uat.api.example.com" 212 | "prod.api.example.com" 213 | "test.api.example.com" 214 | """ 215 | environments = ["dev", "staging", "uat", "prod", "test"] 216 | domains = [] 217 | 218 | for env in environments: 219 | tmp_parts = parts[:-1] 220 | tmp_parts.insert(0, env) 221 | domains.append(".".join(tmp_parts + [parts[-1]])) 222 | 223 | return domains 224 | 225 | @generator.register_permutator() 226 | def cloud_provider_additions(parts: DomainPartsType) -> List[str]: 227 | """ 228 | Add common cloud provider related subdomains. 229 | 230 | Example: 231 | Input: "example.com" 232 | Output: "api-aws.example.com" 233 | "cdn-aws.example.com" 234 | "storage-aws.example.com" 235 | "api-azure.example.com" 236 | "cdn-azure.example.com" 237 | "storage-azure.example.com" 238 | "api-gcp.example.com" 239 | ... 240 | """ 241 | cloud_terms = ["aws", "azure", "gcp", "k8s", "cloud"] 242 | service_terms = ["api", "cdn", "storage", "auth", "db"] 243 | 244 | domains = [] 245 | for term in cloud_terms: 246 | for service in service_terms: 247 | tmp_parts = parts[:-1] 248 | tmp_parts.insert(0, f"{service}-{term}") 249 | domains.append(".".join(tmp_parts + [parts[-1]])) 250 | 251 | return domains 252 | 253 | @generator.register_permutator() 254 | def region_prefixes(parts: DomainPartsType) -> List[str]: 255 | """ 256 | Add common region/location prefixes to domain parts. 257 | 258 | Example: 259 | Input: "api.example.com" 260 | Output: "us-east.api.example.com" 261 | "us-west.api.example.com" 262 | "eu-west.api.example.com" 263 | "ap-south.api.example.com" 264 | "eu-central.api.example.com" 265 | """ 266 | regions = ["us-east", "us-west", "eu-west", "eu-central", "ap-south", "ap-northeast", "sa-east", "af-south"] 267 | domains = [] 268 | 269 | for region in regions: 270 | tmp_parts = parts[:-1] 271 | tmp_parts.insert(0, region) 272 | domains.append(".".join(tmp_parts + [parts[-1]])) 273 | 274 | return domains 275 | 276 | @generator.register_permutator() 277 | def microservice_patterns(parts: DomainPartsType) -> List[str]: 278 | """ 279 | Add common microservice naming patterns. 280 | 281 | Example: 282 | Input: "example.com" 283 | Output: "auth-service.example.com" 284 | "user-service.example.com" 285 | "payment-svc.example.com" 286 | "notification-svc.example.com" 287 | "auth-api.example.com" 288 | "user-api.example.com" 289 | """ 290 | services = ["auth", "user", "payment", "notification", "order", "inventory"] 291 | suffixes = ["service", "svc", "api", "app"] 292 | domains = [] 293 | 294 | for service in services: 295 | for suffix in suffixes: 296 | tmp_parts = parts[:-1] 297 | tmp_parts.insert(0, f"{service}-{suffix}") 298 | domains.append(".".join(tmp_parts + [parts[-1]])) 299 | 300 | return domains 301 | 302 | @generator.register_permutator() 303 | def internal_tooling(parts: DomainPartsType) -> List[str]: 304 | """ 305 | Add common internal tool and platform subdomains. 306 | 307 | Example: 308 | Input: "example.com" 309 | Output: "jenkins.internal.example.com" 310 | "gitlab.internal.example.com" 311 | "monitoring.internal.example.com" 312 | "jenkins.tools.example.com" 313 | "gitlab.tools.example.com" 314 | "monitoring.tools.example.com" 315 | """ 316 | tools = ["jenkins", "gitlab", "grafana", "kibana", "prometheus", "monitoring", "jira"] 317 | prefixes = ["internal", "tools", "admin"] 318 | domains = [] 319 | 320 | for tool in tools: 321 | for prefix in prefixes: 322 | tmp_parts = parts[:-1] 323 | tmp_parts.extend([prefix, tool]) 324 | domains.append(".".join(tmp_parts + [parts[-1]])) 325 | # Also try tool first, then prefix 326 | tmp_parts = parts[:-1] 327 | tmp_parts.extend([tool, prefix]) 328 | domains.append(".".join(tmp_parts + [parts[-1]])) 329 | 330 | return domains 331 | 332 | @generator.register_permutator(fast=True) 333 | def common_ports(parts: DomainPartsType) -> List[str]: 334 | """ 335 | Add common port numbers as prefixes. 336 | 337 | Example: 338 | Input: "api.example.com" 339 | Output: "8080.api.example.com" 340 | "8443.api.example.com" 341 | "3000.api.example.com" 342 | "port-8080.api.example.com" 343 | "port-8443.api.example.com" 344 | """ 345 | ports = ["8080", "8443", "3000", "5000", "9000", "8888"] 346 | domains = [] 347 | 348 | for port in ports: 349 | # Add port directly as subdomain 350 | tmp_parts = parts[:-1] 351 | tmp_parts.insert(0, port) 352 | domains.append(".".join(tmp_parts + [parts[-1]])) 353 | 354 | # Add with 'port-' prefix 355 | tmp_parts = parts[:-1] 356 | tmp_parts.insert(0, f"port-{port}") 357 | domains.append(".".join(tmp_parts + [parts[-1]])) 358 | 359 | return domains 360 | 361 | return generator 362 | -------------------------------------------------------------------------------- /dnsgen/words.txt: -------------------------------------------------------------------------------- 1 | # Environment and Stage Names 2 | dev 3 | development 4 | staging 5 | stage 6 | uat 7 | test 8 | testing 9 | prod 10 | production 11 | sandbox 12 | demo 13 | poc 14 | prototype 15 | preview 16 | beta 17 | alpha 18 | gamma 19 | canary 20 | 21 | # Infrastructure and Cloud 22 | aws 23 | azure 24 | gcp 25 | cloud 26 | k8s 27 | kubernetes 28 | docker 29 | container 30 | pod 31 | node 32 | cluster 33 | registry 34 | consul 35 | istio 36 | vault 37 | terraform 38 | helm 39 | rancher 40 | 41 | # Regions 42 | us-east 43 | us-west 44 | eu-west 45 | eu-central 46 | ap-south 47 | ap-southeast 48 | ap-northeast 49 | sa-east 50 | af-south 51 | central 52 | north 53 | south 54 | east 55 | west 56 | 57 | # Services and Functions 58 | api 59 | rest 60 | grpc 61 | graphql 62 | soap 63 | webhook 64 | socket 65 | ws 66 | wss 67 | tcp 68 | udp 69 | dns 70 | cdn 71 | waf 72 | load-balancer 73 | lb 74 | gateway 75 | proxy 76 | cache 77 | redis 78 | memcached 79 | queue 80 | mq 81 | rabbitmq 82 | kafka 83 | pubsub 84 | service 85 | svc 86 | app 87 | web 88 | mobile 89 | desktop 90 | cli 91 | worker 92 | scheduler 93 | cron 94 | timer 95 | notifier 96 | processor 97 | 98 | # Storage and Databases 99 | db 100 | database 101 | sql 102 | mysql 103 | postgres 104 | postgresql 105 | mongodb 106 | mongo 107 | cassandra 108 | redis 109 | elastic 110 | elasticsearch 111 | neo4j 112 | dynamodb 113 | rds 114 | aurora 115 | s3 116 | storage 117 | backup 118 | archive 119 | blob 120 | bucket 121 | file 122 | object 123 | 124 | # Security and Access 125 | auth 126 | oauth 127 | oidc 128 | sso 129 | login 130 | logout 131 | register 132 | admin 133 | root 134 | sudo 135 | sys 136 | system 137 | security 138 | secure 139 | private 140 | public 141 | internal 142 | external 143 | dmz 144 | firewall 145 | fw 146 | vpn 147 | certificate 148 | cert 149 | ssl 150 | tls 151 | acme 152 | lets-encrypt 153 | token 154 | key 155 | secret 156 | password 157 | user 158 | group 159 | role 160 | policy 161 | permissions 162 | rbac 163 | 164 | # Monitoring and Operations 165 | monitor 166 | monitoring 167 | logging 168 | log 169 | trace 170 | tracing 171 | metrics 172 | grafana 173 | prometheus 174 | kibana 175 | logstash 176 | splunk 177 | nagios 178 | zabbix 179 | alert 180 | alerting 181 | status 182 | health 183 | heartbeat 184 | analytics 185 | dashboard 186 | report 187 | stats 188 | statistics 189 | 190 | # Development Tools 191 | git 192 | github 193 | gitlab 194 | bitbucket 195 | jenkins 196 | travis 197 | circleci 198 | build 199 | deploy 200 | release 201 | pipeline 202 | ci 203 | cd 204 | cicd 205 | code 206 | repo 207 | repository 208 | artifact 209 | package 210 | npm 211 | pip 212 | maven 213 | gradle 214 | yarn 215 | composer 216 | 217 | # Common Applications 218 | jira 219 | confluence 220 | wiki 221 | docs 222 | documentation 223 | portal 224 | blog 225 | cms 226 | crm 227 | erp 228 | hr 229 | admin 230 | panel 231 | board 232 | forum 233 | 234 | # Microservices 235 | user-service 236 | auth-service 237 | payment-service 238 | order-service 239 | billing-service 240 | shipping-service 241 | notification-service 242 | email-service 243 | sms-service 244 | profile-service 245 | search-service 246 | recommendation-service 247 | inventory-service 248 | 249 | # API Versions and Patterns 250 | v1 251 | v2 252 | v3 253 | v1beta 254 | v2beta 255 | v3beta 256 | api-v1 257 | api-v2 258 | api-v3 259 | rest-v1 260 | rest-v2 261 | graphql-v1 262 | 263 | # Network and Protocols 264 | http 265 | https 266 | ftp 267 | sftp 268 | ssh 269 | ldap 270 | smtp 271 | imap 272 | pop3 273 | dns 274 | dhcp 275 | 276 | # Common Ports 277 | port-80 278 | port-443 279 | port-8080 280 | port-8443 281 | port-3000 282 | port-3306 283 | port-5432 284 | port-27017 285 | 286 | # Common Prefixes/Suffixes 287 | my 288 | new 289 | old 290 | tmp 291 | temp 292 | test 293 | debug 294 | legacy 295 | archive 296 | backup 297 | cache 298 | static 299 | media 300 | assets 301 | public 302 | private 303 | shared 304 | common 305 | 306 | # Geographic and Business 307 | global 308 | local 309 | regional 310 | corporate 311 | corp 312 | inc 313 | ltd 314 | business 315 | enterprise 316 | org 317 | edu 318 | gov 319 | mil 320 | 321 | # Technical Roles 322 | admin 323 | devops 324 | sre 325 | dba 326 | security 327 | infosec 328 | support 329 | helpdesk 330 | tech 331 | 332 | # Specific Cloud Services 333 | lambda 334 | ecs 335 | eks 336 | aks 337 | gke 338 | cloudfunctions 339 | appengine 340 | heroku 341 | netlify 342 | vercel 343 | 344 | # Data and Analytics 345 | data 346 | analytics 347 | bi 348 | ml 349 | ai 350 | model 351 | training 352 | inference 353 | dataset 354 | warehouse 355 | lake 356 | stream 357 | batch 358 | etl 359 | import 360 | export 361 | 362 | # Web Technologies 363 | spa 364 | pwa 365 | ssr 366 | csr 367 | jamstack 368 | nextjs 369 | react 370 | vue 371 | angular 372 | svelte 373 | 374 | # Mobile 375 | ios 376 | android 377 | mobile 378 | app 379 | api 380 | backend 381 | frontend -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "dnsgen" 3 | version = "2.0.3" 4 | description = "Advanced DNS name permutation engine for security assessments" 5 | readme = "README.md" 6 | authors = [ 7 | { name = "Aleph Null s.r.o.", email = "hello@alephnull.sk" }, 8 | ] 9 | keywords = ["dns", "security", "pentest", "subdomain", "enumeration"] 10 | classifiers = [ 11 | "Development Status :: 5 - Production/Stable", 12 | "License :: OSI Approved :: MIT License", 13 | "Natural Language :: English", 14 | "Operating System :: OS Independent", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.7", 17 | "Programming Language :: Python :: 3.8", 18 | "Programming Language :: Python :: 3.9", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Topic :: Internet :: Name Service (DNS)", 22 | "Topic :: Security", 23 | "Typing :: Typed", 24 | ] 25 | requires-python = ">=3.9" 26 | dependencies = [ 27 | "click>=8.1.8", 28 | "rich>=13.9.4", 29 | "tldextract>=5.1.3", 30 | ] 31 | 32 | [dependency-groups] 33 | dev = [ 34 | "mypy>=1.14.1", 35 | "pre-commit>=4.0.1", 36 | "pytest>=8.3.4", 37 | "ruff>=0.8.5" 38 | ] 39 | 40 | [project.urls] 41 | Homepage = "https://github.com/AlephNullSK/dnsgen" 42 | Repository = "https://github.com/AlephNullSK/dnsgen" 43 | 44 | [project.scripts] 45 | dnsgen = "dnsgen.cli:main" 46 | 47 | [tool.uv] 48 | package = true 49 | 50 | [build-system] 51 | requires = ["hatchling"] 52 | build-backend = "hatchling.build" 53 | 54 | [tool.hatch.build.targets.wheel] 55 | only-include = ["dnsgen/"] 56 | 57 | [tool.hatch.build.targets.wheel.sources] 58 | "dnsgen" = "" 59 | 60 | [tool.ruff] 61 | line-length = 120 62 | target-version = "py312" 63 | exclude = [ 64 | ".direnv", 65 | ".eggs", 66 | ".git", 67 | ".git-rewrite", 68 | ".hg", 69 | ".ipynb_checkpoints", 70 | ".mypy_cache", 71 | ".nox", 72 | ".pants.d", 73 | ".pyenv", 74 | ".pytest_cache", 75 | ".pytype", 76 | ".ruff_cache", 77 | "__init__.py", 78 | "site-packages/", 79 | "build", 80 | "dist", 81 | "venv" 82 | ] 83 | 84 | [tool.ruff.lint] 85 | select = ["I", "E", "F", "YTT"] 86 | ignore = ["E203", "E266", "E501", "F403", "F401", "E731", "F841", "B905", "I001"] 87 | fixable = ["ALL"] 88 | unfixable = [] 89 | 90 | [tool.pytest.ini_options] 91 | minversion = "6.0" 92 | testpaths = [ 93 | "tests", 94 | ] 95 | markers = [ 96 | "slow: marks tests as slow (deselect with '-m \"not slow\"')", 97 | "integration: marks tests as integration tests", 98 | "cli: marks tests as CLI tests" 99 | ] 100 | 101 | filterwarnings = [ 102 | # Specific warnings 103 | "ignore::pytest.PytestDeprecationWarning", 104 | "ignore::pytest.PytestCollectionWarning", 105 | "ignore:pkg_resources is deprecated as an API", 106 | "ignore::DeprecationWarning", 107 | "ignore::RuntimeWarning" 108 | ] 109 | 110 | [tool.mypy] 111 | python_version = "3.12" 112 | show_error_codes = true 113 | exclude = [".venv/"] 114 | ignore_missing_imports = true 115 | disable_error_code = ["import-untyped"] 116 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | # tests/test_cli.py 2 | import os 3 | from pathlib import Path 4 | from click.testing import CliRunner 5 | import pytest 6 | from dnsgen.cli import main 7 | 8 | 9 | @pytest.fixture 10 | def runner(): 11 | """Fixture providing a Click CLI test runner.""" 12 | return CliRunner() 13 | 14 | 15 | @pytest.fixture 16 | def input_file(tmp_path: Path) -> Path: 17 | """Fixture creating a temporary input file with domain names.""" 18 | content = """ 19 | api.example.com 20 | dev.test.com 21 | staging.company.com 22 | """ 23 | input_path = tmp_path / "input_domains.txt" 24 | input_path.write_text(content.strip()) 25 | return input_path 26 | 27 | 28 | @pytest.fixture 29 | def wordlist_file(tmp_path: Path) -> Path: 30 | """Fixture creating a temporary wordlist file.""" 31 | content = """ 32 | # Test wordlist 33 | dev 34 | staging 35 | prod 36 | api 37 | """ 38 | wordlist_path = tmp_path / "test_wordlist.txt" 39 | wordlist_path.write_text(content) 40 | return wordlist_path 41 | 42 | 43 | def test_basic_cli_execution(runner: CliRunner, input_file: Path): 44 | """Test basic CLI execution with minimal arguments.""" 45 | result = runner.invoke(main, [str(input_file)]) 46 | assert result.exit_code == 0 47 | assert len(result.output.splitlines()) > 0 48 | 49 | 50 | def test_cli_with_wordlist(runner: CliRunner, input_file: Path, wordlist_file: Path): 51 | """Test CLI execution with custom wordlist.""" 52 | result = runner.invoke(main, [ 53 | '-w', str(wordlist_file), 54 | str(input_file) 55 | ]) 56 | assert result.exit_code == 0 57 | assert len(result.output.splitlines()) > 0 58 | 59 | 60 | def test_cli_with_output_file(runner: CliRunner, input_file: Path, tmp_path: Path): 61 | """Test CLI execution with output file.""" 62 | output_file = tmp_path / "output.txt" 63 | result = runner.invoke(main, [ 64 | '-o', str(output_file), 65 | str(input_file) 66 | ]) 67 | assert result.exit_code == 0 68 | assert output_file.exists() 69 | assert len(output_file.read_text().splitlines()) > 0 70 | 71 | 72 | def test_cli_fast_mode(runner: CliRunner, input_file: Path): 73 | """Test CLI execution in fast mode.""" 74 | result = runner.invoke(main, [ 75 | '-f', 76 | str(input_file) 77 | ]) 78 | assert result.exit_code == 0 79 | assert len(result.output.splitlines()) > 0 80 | 81 | 82 | def test_cli_invalid_input_file(runner: CliRunner): 83 | """Test CLI handling of nonexistent input file.""" 84 | result = runner.invoke(main, ['nonexistent.txt']) 85 | assert result.exit_code != 0 86 | assert "Error" in result.output 87 | 88 | 89 | def test_cli_invalid_wordlist(runner: CliRunner, input_file: Path): 90 | """Test CLI handling of nonexistent wordlist.""" 91 | result = runner.invoke(main, [ 92 | '-w', 'nonexistent_wordlist.txt', 93 | str(input_file) 94 | ]) 95 | assert result.exit_code != 0 96 | assert "Error" in result.output 97 | 98 | 99 | def test_cli_invalid_wordlen(runner: CliRunner, input_file: Path): 100 | """Test CLI handling of invalid word length.""" 101 | result = runner.invoke(main, [ 102 | '-l', '0', # Invalid length 103 | str(input_file) 104 | ]) 105 | assert result.exit_code != 0 106 | assert "Error" in result.output 107 | 108 | 109 | def test_cli_stdin_input(runner: CliRunner): 110 | """Test CLI handling of stdin input.""" 111 | input_data = "api.example.com\ndev.test.com\n" 112 | result = runner.invoke(main, ['-'], input=input_data) 113 | assert result.exit_code == 0 114 | assert len(result.output.splitlines()) > 0 115 | 116 | 117 | @pytest.mark.slow 118 | def test_cli_large_input(runner: CliRunner, tmp_path: Path): 119 | """Test CLI handling of large input (marked as slow test).""" 120 | # Create large input file 121 | input_file = tmp_path / "large_input.txt" 122 | domains = [f"test{i}.example.com\n" for i in range(100)] 123 | input_file.write_text("".join(domains)) 124 | 125 | result = runner.invoke(main, [str(input_file)]) 126 | assert result.exit_code == 0 127 | assert len(result.output.splitlines()) > 0 128 | -------------------------------------------------------------------------------- /tests/test_dnsgen.py: -------------------------------------------------------------------------------- 1 | # tests/test_dnsgen.py 2 | import os 3 | from pathlib import Path 4 | from typing import List, Set 5 | import pytest 6 | from dnsgen.dnsgen import DomainGenerator, create_generator 7 | 8 | 9 | @pytest.fixture 10 | def sample_domains() -> List[str]: 11 | """Fixture providing sample domain names for testing.""" 12 | return [ 13 | "api.example.com", 14 | "dev-api01.test.com", 15 | "staging-auth.prod.company.com", 16 | "v2.api.service.org" 17 | ] 18 | 19 | 20 | @pytest.fixture 21 | def sample_wordlist(tmp_path: Path) -> Path: 22 | """Fixture creating a temporary wordlist file.""" 23 | content = """ 24 | # Environment 25 | dev 26 | staging 27 | prod 28 | 29 | # Services 30 | api 31 | auth 32 | service 33 | 34 | # Version 35 | v1 36 | v2 37 | v3 38 | """ 39 | wordlist_path = tmp_path / "test_wordlist.txt" 40 | wordlist_path.write_text(content) 41 | return wordlist_path 42 | 43 | 44 | @pytest.fixture 45 | def generator(sample_wordlist: Path) -> DomainGenerator: 46 | """Fixture providing a configured DomainGenerator instance.""" 47 | return create_generator(sample_wordlist) 48 | 49 | 50 | def test_partiate_domain(generator: DomainGenerator): 51 | """Test domain partitioning functionality.""" 52 | test_cases = [ 53 | ("api.example.com", ["api", "example.com"]), 54 | ("dev.api.example.com", ["dev", "api", "example.com"]), 55 | ("test.sub.domain.example.co.uk", ["test", "sub", "domain", "example.co.uk"]) 56 | ] 57 | 58 | for domain, expected in test_cases: 59 | assert generator.partiate_domain(domain) == expected 60 | 61 | 62 | def test_extract_custom_words(generator: DomainGenerator): 63 | """Test custom word extraction from domains.""" 64 | domains = [ 65 | "development-api.example.com", 66 | "staging-auth.test.com", 67 | "prod-service.company.com" 68 | ] 69 | 70 | # Test with different word lengths 71 | assert "development" in generator.extract_custom_words(domains, 6) 72 | assert "staging" in generator.extract_custom_words(domains, 4) 73 | assert "api" not in generator.extract_custom_words(domains, 4) # Too short 74 | 75 | 76 | def test_word_insertion_permutator(generator: DomainGenerator): 77 | """Test word insertion permutation technique.""" 78 | domain = "api.example.com" 79 | parts = generator.partiate_domain(domain) 80 | 81 | variations = generator.generate([domain]) 82 | variations_list = list(variations) 83 | 84 | # Check some expected variations 85 | assert "dev.api.example.com" in variations_list 86 | assert "api.staging.example.com" in variations_list 87 | 88 | 89 | def test_number_manipulation_permutator(generator: DomainGenerator): 90 | """Test number manipulation in domains.""" 91 | domain = "api2.example.com" 92 | variations = list(generator.generate([domain])) 93 | 94 | # Check number increments and decrements 95 | assert "api1.example.com" in variations 96 | assert "api3.example.com" in variations 97 | 98 | 99 | def test_region_prefix_permutator(generator: DomainGenerator): 100 | """Test region prefix permutations.""" 101 | domain = "api.example.com" 102 | variations = list(generator.generate([domain])) 103 | 104 | # Check region patterns 105 | assert "us-east.api.example.com" in variations 106 | assert "eu-west.api.example.com" in variations 107 | 108 | 109 | def test_fast_mode_generation(generator: DomainGenerator): 110 | """Test fast mode generation with fewer permutations.""" 111 | domain = "api.example.com" 112 | 113 | # Compare number of variations between normal and fast mode 114 | normal_variations = set(generator.generate([domain], fast_mode=False)) 115 | fast_variations = set(generator.generate([domain], fast_mode=True)) 116 | 117 | # Fast mode should generate fewer variations 118 | assert len(fast_variations) < len(normal_variations) 119 | 120 | 121 | def test_invalid_wordlist_handling(): 122 | """Test handling of invalid wordlist path.""" 123 | with pytest.raises(FileNotFoundError): 124 | create_generator("nonexistent_wordlist.txt") 125 | 126 | 127 | # Integration Tests 128 | 129 | def test_full_generation_pipeline(sample_domains: List[str], generator: DomainGenerator): 130 | """Integration test for the full generation pipeline.""" 131 | # Test complete pipeline 132 | variations = list(generator.generate(sample_domains)) 133 | 134 | # Basic validation 135 | assert len(variations) > 0 136 | assert all(isinstance(domain, str) for domain in variations) 137 | assert all("." in domain for domain in variations) 138 | 139 | 140 | # Edge Cases 141 | 142 | def test_empty_domain_handling(generator: DomainGenerator): 143 | """Test handling of empty domain input.""" 144 | variations = list(generator.generate([])) 145 | assert len(variations) == 0 146 | 147 | 148 | def test_special_character_domains(generator: DomainGenerator): 149 | """Test handling of domains with special characters.""" 150 | domain = "api-test_01.example.com" 151 | variations = list(generator.generate([domain])) 152 | assert len(variations) > 0 153 | 154 | 155 | def test_very_long_domain(generator: DomainGenerator): 156 | """Test handling of very long domain names.""" 157 | long_domain = "a" * 30 + ".example.com" 158 | variations = list(generator.generate([long_domain])) 159 | assert len(variations) > 0 160 | assert all(len(domain) <= 253 for domain in variations) # DNS max length 161 | 162 | 163 | # Performance Tests 164 | 165 | @pytest.mark.slow 166 | @pytest.mark.integration 167 | def test_large_input_performance(generator: DomainGenerator): 168 | """Test performance with large input (marked as slow test).""" 169 | large_domains = [f"test{i}.example.com" for i in range(100)] 170 | variations = list(generator.generate(large_domains)) 171 | assert len(variations) > 0 -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.9" 3 | 4 | [[package]] 5 | name = "certifi" 6 | version = "2024.12.14" 7 | source = { registry = "https://pypi.org/simple" } 8 | sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } 9 | wheels = [ 10 | { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, 11 | ] 12 | 13 | [[package]] 14 | name = "cfgv" 15 | version = "3.4.0" 16 | source = { registry = "https://pypi.org/simple" } 17 | sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } 18 | wheels = [ 19 | { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, 20 | ] 21 | 22 | [[package]] 23 | name = "charset-normalizer" 24 | version = "3.4.1" 25 | source = { registry = "https://pypi.org/simple" } 26 | sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } 27 | wheels = [ 28 | { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, 29 | { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, 30 | { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, 31 | { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, 32 | { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, 33 | { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, 34 | { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, 35 | { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, 36 | { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, 37 | { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, 38 | { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, 39 | { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, 40 | { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, 41 | { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, 42 | { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, 43 | { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, 44 | { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, 45 | { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, 46 | { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, 47 | { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, 48 | { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, 49 | { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, 50 | { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, 51 | { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, 52 | { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, 53 | { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, 54 | { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, 55 | { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, 56 | { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, 57 | { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, 58 | { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, 59 | { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, 60 | { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, 61 | { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, 62 | { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, 63 | { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, 64 | { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, 65 | { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, 66 | { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, 67 | { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, 68 | { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, 69 | { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, 70 | { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, 71 | { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, 72 | { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, 73 | { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, 74 | { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, 75 | { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, 76 | { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, 77 | { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, 78 | { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, 79 | { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, 80 | { url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867 }, 81 | { url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385 }, 82 | { url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367 }, 83 | { url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928 }, 84 | { url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203 }, 85 | { url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082 }, 86 | { url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053 }, 87 | { url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625 }, 88 | { url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549 }, 89 | { url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945 }, 90 | { url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595 }, 91 | { url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453 }, 92 | { url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811 }, 93 | { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, 94 | ] 95 | 96 | [[package]] 97 | name = "click" 98 | version = "8.1.8" 99 | source = { registry = "https://pypi.org/simple" } 100 | dependencies = [ 101 | { name = "colorama", marker = "sys_platform == 'win32'" }, 102 | ] 103 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } 104 | wheels = [ 105 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, 106 | ] 107 | 108 | [[package]] 109 | name = "colorama" 110 | version = "0.4.6" 111 | source = { registry = "https://pypi.org/simple" } 112 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 113 | wheels = [ 114 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 115 | ] 116 | 117 | [[package]] 118 | name = "distlib" 119 | version = "0.3.9" 120 | source = { registry = "https://pypi.org/simple" } 121 | sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } 122 | wheels = [ 123 | { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, 124 | ] 125 | 126 | [[package]] 127 | name = "dnsgen" 128 | version = "2.0.3" 129 | source = { editable = "." } 130 | dependencies = [ 131 | { name = "click" }, 132 | { name = "rich" }, 133 | { name = "tldextract" }, 134 | ] 135 | 136 | [package.dev-dependencies] 137 | dev = [ 138 | { name = "mypy" }, 139 | { name = "pre-commit" }, 140 | { name = "pytest" }, 141 | { name = "ruff" }, 142 | ] 143 | 144 | [package.metadata] 145 | requires-dist = [ 146 | { name = "click", specifier = ">=8.1.8" }, 147 | { name = "rich", specifier = ">=13.9.4" }, 148 | { name = "tldextract", specifier = ">=5.1.3" }, 149 | ] 150 | 151 | [package.metadata.requires-dev] 152 | dev = [ 153 | { name = "mypy", specifier = ">=1.14.1" }, 154 | { name = "pre-commit", specifier = ">=4.0.1" }, 155 | { name = "pytest", specifier = ">=8.3.4" }, 156 | { name = "ruff", specifier = ">=0.8.5" }, 157 | ] 158 | 159 | [[package]] 160 | name = "exceptiongroup" 161 | version = "1.2.2" 162 | source = { registry = "https://pypi.org/simple" } 163 | sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } 164 | wheels = [ 165 | { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, 166 | ] 167 | 168 | [[package]] 169 | name = "filelock" 170 | version = "3.16.1" 171 | source = { registry = "https://pypi.org/simple" } 172 | sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } 173 | wheels = [ 174 | { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, 175 | ] 176 | 177 | [[package]] 178 | name = "identify" 179 | version = "2.6.4" 180 | source = { registry = "https://pypi.org/simple" } 181 | sdist = { url = "https://files.pythonhosted.org/packages/49/a5/7de3053524ee006b91099968d7ecb2e0b420f7ae728094394c33e8a2a2b9/identify-2.6.4.tar.gz", hash = "sha256:285a7d27e397652e8cafe537a6cc97dd470a970f48fb2e9d979aa38eae5513ac", size = 99209 } 182 | wheels = [ 183 | { url = "https://files.pythonhosted.org/packages/a2/9d/52f036403ae86474804f699c0d084b4b071e333a390b20269bb8accc65e0/identify-2.6.4-py2.py3-none-any.whl", hash = "sha256:993b0f01b97e0568c179bb9196391ff391bfb88a99099dbf5ce392b68f42d0af", size = 99072 }, 184 | ] 185 | 186 | [[package]] 187 | name = "idna" 188 | version = "3.10" 189 | source = { registry = "https://pypi.org/simple" } 190 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 191 | wheels = [ 192 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 193 | ] 194 | 195 | [[package]] 196 | name = "iniconfig" 197 | version = "2.0.0" 198 | source = { registry = "https://pypi.org/simple" } 199 | sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } 200 | wheels = [ 201 | { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, 202 | ] 203 | 204 | [[package]] 205 | name = "markdown-it-py" 206 | version = "3.0.0" 207 | source = { registry = "https://pypi.org/simple" } 208 | dependencies = [ 209 | { name = "mdurl" }, 210 | ] 211 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } 212 | wheels = [ 213 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, 214 | ] 215 | 216 | [[package]] 217 | name = "mdurl" 218 | version = "0.1.2" 219 | source = { registry = "https://pypi.org/simple" } 220 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } 221 | wheels = [ 222 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, 223 | ] 224 | 225 | [[package]] 226 | name = "mypy" 227 | version = "1.14.1" 228 | source = { registry = "https://pypi.org/simple" } 229 | dependencies = [ 230 | { name = "mypy-extensions" }, 231 | { name = "tomli", marker = "python_full_version < '3.11'" }, 232 | { name = "typing-extensions" }, 233 | ] 234 | sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051 } 235 | wheels = [ 236 | { url = "https://files.pythonhosted.org/packages/9b/7a/87ae2adb31d68402da6da1e5f30c07ea6063e9f09b5e7cfc9dfa44075e74/mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb", size = 11211002 }, 237 | { url = "https://files.pythonhosted.org/packages/e1/23/eada4c38608b444618a132be0d199b280049ded278b24cbb9d3fc59658e4/mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0", size = 10358400 }, 238 | { url = "https://files.pythonhosted.org/packages/43/c9/d6785c6f66241c62fd2992b05057f404237deaad1566545e9f144ced07f5/mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d", size = 12095172 }, 239 | { url = "https://files.pythonhosted.org/packages/c3/62/daa7e787770c83c52ce2aaf1a111eae5893de9e004743f51bfcad9e487ec/mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b", size = 12828732 }, 240 | { url = "https://files.pythonhosted.org/packages/1b/a2/5fb18318a3637f29f16f4e41340b795da14f4751ef4f51c99ff39ab62e52/mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427", size = 13012197 }, 241 | { url = "https://files.pythonhosted.org/packages/28/99/e153ce39105d164b5f02c06c35c7ba958aaff50a2babba7d080988b03fe7/mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f", size = 9780836 }, 242 | { url = "https://files.pythonhosted.org/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c", size = 11120432 }, 243 | { url = "https://files.pythonhosted.org/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1", size = 10279515 }, 244 | { url = "https://files.pythonhosted.org/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8", size = 12025791 }, 245 | { url = "https://files.pythonhosted.org/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f", size = 12749203 }, 246 | { url = "https://files.pythonhosted.org/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1", size = 12885900 }, 247 | { url = "https://files.pythonhosted.org/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae", size = 9777869 }, 248 | { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668 }, 249 | { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060 }, 250 | { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167 }, 251 | { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341 }, 252 | { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991 }, 253 | { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016 }, 254 | { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097 }, 255 | { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728 }, 256 | { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965 }, 257 | { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660 }, 258 | { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198 }, 259 | { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276 }, 260 | { url = "https://files.pythonhosted.org/packages/ca/1f/186d133ae2514633f8558e78cd658070ba686c0e9275c5a5c24a1e1f0d67/mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35", size = 11200493 }, 261 | { url = "https://files.pythonhosted.org/packages/af/fc/4842485d034e38a4646cccd1369f6b1ccd7bc86989c52770d75d719a9941/mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc", size = 10357702 }, 262 | { url = "https://files.pythonhosted.org/packages/b4/e6/457b83f2d701e23869cfec013a48a12638f75b9d37612a9ddf99072c1051/mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9", size = 12091104 }, 263 | { url = "https://files.pythonhosted.org/packages/f1/bf/76a569158db678fee59f4fd30b8e7a0d75bcbaeef49edd882a0d63af6d66/mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb", size = 12830167 }, 264 | { url = "https://files.pythonhosted.org/packages/43/bc/0bc6b694b3103de9fed61867f1c8bd33336b913d16831431e7cb48ef1c92/mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60", size = 13013834 }, 265 | { url = "https://files.pythonhosted.org/packages/b0/79/5f5ec47849b6df1e6943d5fd8e6632fbfc04b4fd4acfa5a5a9535d11b4e2/mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c", size = 9781231 }, 266 | { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905 }, 267 | ] 268 | 269 | [[package]] 270 | name = "mypy-extensions" 271 | version = "1.0.0" 272 | source = { registry = "https://pypi.org/simple" } 273 | sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } 274 | wheels = [ 275 | { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, 276 | ] 277 | 278 | [[package]] 279 | name = "nodeenv" 280 | version = "1.9.1" 281 | source = { registry = "https://pypi.org/simple" } 282 | sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } 283 | wheels = [ 284 | { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, 285 | ] 286 | 287 | [[package]] 288 | name = "packaging" 289 | version = "24.2" 290 | source = { registry = "https://pypi.org/simple" } 291 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } 292 | wheels = [ 293 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, 294 | ] 295 | 296 | [[package]] 297 | name = "platformdirs" 298 | version = "4.3.6" 299 | source = { registry = "https://pypi.org/simple" } 300 | sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } 301 | wheels = [ 302 | { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, 303 | ] 304 | 305 | [[package]] 306 | name = "pluggy" 307 | version = "1.5.0" 308 | source = { registry = "https://pypi.org/simple" } 309 | sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } 310 | wheels = [ 311 | { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, 312 | ] 313 | 314 | [[package]] 315 | name = "pre-commit" 316 | version = "4.0.1" 317 | source = { registry = "https://pypi.org/simple" } 318 | dependencies = [ 319 | { name = "cfgv" }, 320 | { name = "identify" }, 321 | { name = "nodeenv" }, 322 | { name = "pyyaml" }, 323 | { name = "virtualenv" }, 324 | ] 325 | sdist = { url = "https://files.pythonhosted.org/packages/2e/c8/e22c292035f1bac8b9f5237a2622305bc0304e776080b246f3df57c4ff9f/pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2", size = 191678 } 326 | wheels = [ 327 | { url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 }, 328 | ] 329 | 330 | [[package]] 331 | name = "pygments" 332 | version = "2.18.0" 333 | source = { registry = "https://pypi.org/simple" } 334 | sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } 335 | wheels = [ 336 | { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, 337 | ] 338 | 339 | [[package]] 340 | name = "pytest" 341 | version = "8.3.4" 342 | source = { registry = "https://pypi.org/simple" } 343 | dependencies = [ 344 | { name = "colorama", marker = "sys_platform == 'win32'" }, 345 | { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, 346 | { name = "iniconfig" }, 347 | { name = "packaging" }, 348 | { name = "pluggy" }, 349 | { name = "tomli", marker = "python_full_version < '3.11'" }, 350 | ] 351 | sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } 352 | wheels = [ 353 | { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, 354 | ] 355 | 356 | [[package]] 357 | name = "pyyaml" 358 | version = "6.0.2" 359 | source = { registry = "https://pypi.org/simple" } 360 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } 361 | wheels = [ 362 | { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, 363 | { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, 364 | { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, 365 | { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, 366 | { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, 367 | { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, 368 | { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, 369 | { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, 370 | { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, 371 | { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, 372 | { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, 373 | { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, 374 | { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, 375 | { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, 376 | { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, 377 | { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, 378 | { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, 379 | { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, 380 | { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, 381 | { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, 382 | { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, 383 | { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, 384 | { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, 385 | { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, 386 | { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, 387 | { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, 388 | { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, 389 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, 390 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, 391 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, 392 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, 393 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, 394 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, 395 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, 396 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, 397 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, 398 | { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, 399 | { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, 400 | { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, 401 | { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, 402 | { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, 403 | { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, 404 | { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, 405 | { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, 406 | { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, 407 | ] 408 | 409 | [[package]] 410 | name = "requests" 411 | version = "2.32.3" 412 | source = { registry = "https://pypi.org/simple" } 413 | dependencies = [ 414 | { name = "certifi" }, 415 | { name = "charset-normalizer" }, 416 | { name = "idna" }, 417 | { name = "urllib3" }, 418 | ] 419 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } 420 | wheels = [ 421 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, 422 | ] 423 | 424 | [[package]] 425 | name = "requests-file" 426 | version = "2.1.0" 427 | source = { registry = "https://pypi.org/simple" } 428 | dependencies = [ 429 | { name = "requests" }, 430 | ] 431 | sdist = { url = "https://files.pythonhosted.org/packages/72/97/bf44e6c6bd8ddbb99943baf7ba8b1a8485bcd2fe0e55e5708d7fee4ff1ae/requests_file-2.1.0.tar.gz", hash = "sha256:0f549a3f3b0699415ac04d167e9cb39bccfb730cb832b4d20be3d9867356e658", size = 6891 } 432 | wheels = [ 433 | { url = "https://files.pythonhosted.org/packages/d7/25/dd878a121fcfdf38f52850f11c512e13ec87c2ea72385933818e5b6c15ce/requests_file-2.1.0-py2.py3-none-any.whl", hash = "sha256:cf270de5a4c5874e84599fc5778303d496c10ae5e870bfa378818f35d21bda5c", size = 4244 }, 434 | ] 435 | 436 | [[package]] 437 | name = "rich" 438 | version = "13.9.4" 439 | source = { registry = "https://pypi.org/simple" } 440 | dependencies = [ 441 | { name = "markdown-it-py" }, 442 | { name = "pygments" }, 443 | { name = "typing-extensions", marker = "python_full_version < '3.11'" }, 444 | ] 445 | sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } 446 | wheels = [ 447 | { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, 448 | ] 449 | 450 | [[package]] 451 | name = "ruff" 452 | version = "0.8.5" 453 | source = { registry = "https://pypi.org/simple" } 454 | sdist = { url = "https://files.pythonhosted.org/packages/25/5d/4b5403f3e89837decfd54c51bea7f94b7d3fae77e08858603d0e04d7ad17/ruff-0.8.5.tar.gz", hash = "sha256:1098d36f69831f7ff2a1da3e6407d5fbd6dfa2559e4f74ff2d260c5588900317", size = 3454835 } 455 | wheels = [ 456 | { url = "https://files.pythonhosted.org/packages/73/f8/03391745a703ce11678eb37c48ae89ec60396ea821e9d0bcea7c8e88fd91/ruff-0.8.5-py3-none-linux_armv6l.whl", hash = "sha256:5ad11a5e3868a73ca1fa4727fe7e33735ea78b416313f4368c504dbeb69c0f88", size = 10626889 }, 457 | { url = "https://files.pythonhosted.org/packages/55/74/83bb74a44183b904216f3edfb9995b89830c83aaa6ce84627f74da0e0cf8/ruff-0.8.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f69ab37771ea7e0715fead8624ec42996d101269a96e31f4d31be6fc33aa19b7", size = 10398233 }, 458 | { url = "https://files.pythonhosted.org/packages/e8/7a/a162a4feb3ef85d594527165e366dde09d7a1e534186ff4ba5d127eda850/ruff-0.8.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b5462d7804558ccff9c08fe8cbf6c14b7efe67404316696a2dde48297b1925bb", size = 10001843 }, 459 | { url = "https://files.pythonhosted.org/packages/e7/9f/5ee5dcd135411402e35b6ec6a8dfdadbd31c5cd1c36a624d356a38d76090/ruff-0.8.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d56de7220a35607f9fe59f8a6d018e14504f7b71d784d980835e20fc0611cd50", size = 10872507 }, 460 | { url = "https://files.pythonhosted.org/packages/b6/67/db2df2dd4a34b602d7f6ebb1b3744c8157f0d3579973ffc58309c9c272e8/ruff-0.8.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9d99cf80b0429cbebf31cbbf6f24f05a29706f0437c40413d950e67e2d4faca4", size = 10377200 }, 461 | { url = "https://files.pythonhosted.org/packages/fe/ff/fe3a6a73006bced73e60d171d154a82430f61d97e787f511a24bd6302611/ruff-0.8.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b75ac29715ac60d554a049dbb0ef3b55259076181c3369d79466cb130eb5afd", size = 11433155 }, 462 | { url = "https://files.pythonhosted.org/packages/e3/95/c1d1a1fe36658c1f3e1b47e1cd5f688b72d5786695b9e621c2c38399a95e/ruff-0.8.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c9d526a62c9eda211b38463528768fd0ada25dad524cb33c0e99fcff1c67b5dc", size = 12139227 }, 463 | { url = "https://files.pythonhosted.org/packages/1b/fe/644b70d473a27b5112ac7a3428edcc1ce0db775c301ff11aa146f71886e0/ruff-0.8.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:587c5e95007612c26509f30acc506c874dab4c4abbacd0357400bd1aa799931b", size = 11697941 }, 464 | { url = "https://files.pythonhosted.org/packages/00/39/4f83e517ec173e16a47c6d102cd22a1aaebe80e1208a1f2e83ab9a0e4134/ruff-0.8.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:622b82bf3429ff0e346835ec213aec0a04d9730480cbffbb6ad9372014e31bbd", size = 12967686 }, 465 | { url = "https://files.pythonhosted.org/packages/1a/f6/52a2973ff108d74b5da706a573379eea160bece098f7cfa3f35dc4622710/ruff-0.8.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f99be814d77a5dac8a8957104bdd8c359e85c86b0ee0e38dca447cb1095f70fb", size = 11253788 }, 466 | { url = "https://files.pythonhosted.org/packages/ce/1f/3b30f3c65b1303cb8e268ec3b046b77ab21ed8e26921cfc7e8232aa57f2c/ruff-0.8.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01c048f9c3385e0fd7822ad0fd519afb282af9cf1778f3580e540629df89725", size = 10860360 }, 467 | { url = "https://files.pythonhosted.org/packages/a5/a8/2a3ea6bacead963f7aeeba0c61815d9b27b0d638e6a74984aa5cc5d27733/ruff-0.8.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7512e8cb038db7f5db6aae0e24735ff9ea03bb0ed6ae2ce534e9baa23c1dc9ea", size = 10457922 }, 468 | { url = "https://files.pythonhosted.org/packages/17/47/8f9514b670969aab57c5fc826fb500a16aee8feac1bcf8a91358f153a5ba/ruff-0.8.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:762f113232acd5b768d6b875d16aad6b00082add40ec91c927f0673a8ec4ede8", size = 10958347 }, 469 | { url = "https://files.pythonhosted.org/packages/0d/d6/78a9af8209ad99541816d74f01ce678fc01ebb3f37dd7ab8966646dcd92b/ruff-0.8.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:03a90200c5dfff49e4c967b405f27fdfa81594cbb7c5ff5609e42d7fe9680da5", size = 11328882 }, 470 | { url = "https://files.pythonhosted.org/packages/54/77/5c8072ec7afdfdf42c7a4019044486a2b6c85ee73617f8875ec94b977fed/ruff-0.8.5-py3-none-win32.whl", hash = "sha256:8710ffd57bdaa6690cbf6ecff19884b8629ec2a2a2a2f783aa94b1cc795139ed", size = 8802515 }, 471 | { url = "https://files.pythonhosted.org/packages/bc/b6/47d2b06784de8ae992c45cceb2a30f3f205b3236a629d7ca4c0c134839a2/ruff-0.8.5-py3-none-win_amd64.whl", hash = "sha256:4020d8bf8d3a32325c77af452a9976a9ad6455773bcb94991cf15bd66b347e47", size = 9684231 }, 472 | { url = "https://files.pythonhosted.org/packages/bf/5e/ffee22bf9f9e4b2669d1f0179ae8804584939fb6502b51f2401e26b1e028/ruff-0.8.5-py3-none-win_arm64.whl", hash = "sha256:134ae019ef13e1b060ab7136e7828a6d83ea727ba123381307eb37c6bd5e01cb", size = 9124741 }, 473 | ] 474 | 475 | [[package]] 476 | name = "tldextract" 477 | version = "5.1.3" 478 | source = { registry = "https://pypi.org/simple" } 479 | dependencies = [ 480 | { name = "filelock" }, 481 | { name = "idna" }, 482 | { name = "requests" }, 483 | { name = "requests-file" }, 484 | ] 485 | sdist = { url = "https://files.pythonhosted.org/packages/4a/4f/eee4bebcbad25a798bf55601d3a4aee52003bebcf9e55fce08b91ca541a9/tldextract-5.1.3.tar.gz", hash = "sha256:d43c7284c23f5dc8a42fd0fee2abede2ff74cc622674e4cb07f514ab3330c338", size = 125033 } 486 | wheels = [ 487 | { url = "https://files.pythonhosted.org/packages/c6/86/aebe15fa40a992c446be5cf14e70e58a251277494c14d26bdbcff0e658fd/tldextract-5.1.3-py3-none-any.whl", hash = "sha256:78de310cc2ca018692de5ddf320f9d6bd7c5cf857d0fd4f2175f0cdf4440ea75", size = 104923 }, 488 | ] 489 | 490 | [[package]] 491 | name = "tomli" 492 | version = "2.2.1" 493 | source = { registry = "https://pypi.org/simple" } 494 | sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } 495 | wheels = [ 496 | { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, 497 | { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, 498 | { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, 499 | { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, 500 | { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, 501 | { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, 502 | { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, 503 | { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, 504 | { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, 505 | { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, 506 | { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, 507 | { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, 508 | { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, 509 | { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, 510 | { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, 511 | { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, 512 | { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, 513 | { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, 514 | { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, 515 | { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, 516 | { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, 517 | { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, 518 | { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, 519 | { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, 520 | { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, 521 | { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, 522 | { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, 523 | { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, 524 | { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, 525 | { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, 526 | { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, 527 | ] 528 | 529 | [[package]] 530 | name = "typing-extensions" 531 | version = "4.12.2" 532 | source = { registry = "https://pypi.org/simple" } 533 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 534 | wheels = [ 535 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 536 | ] 537 | 538 | [[package]] 539 | name = "urllib3" 540 | version = "2.3.0" 541 | source = { registry = "https://pypi.org/simple" } 542 | sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } 543 | wheels = [ 544 | { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, 545 | ] 546 | 547 | [[package]] 548 | name = "virtualenv" 549 | version = "20.28.1" 550 | source = { registry = "https://pypi.org/simple" } 551 | dependencies = [ 552 | { name = "distlib" }, 553 | { name = "filelock" }, 554 | { name = "platformdirs" }, 555 | ] 556 | sdist = { url = "https://files.pythonhosted.org/packages/50/39/689abee4adc85aad2af8174bb195a819d0be064bf55fcc73b49d2b28ae77/virtualenv-20.28.1.tar.gz", hash = "sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329", size = 7650532 } 557 | wheels = [ 558 | { url = "https://files.pythonhosted.org/packages/51/8f/dfb257ca6b4e27cb990f1631142361e4712badab8e3ca8dc134d96111515/virtualenv-20.28.1-py3-none-any.whl", hash = "sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb", size = 4276719 }, 559 | ] 560 | --------------------------------------------------------------------------------