diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 00000000..dc3b43ff
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,100 @@
+name: Deploy Documentation
+
+on:
+ workflow_run:
+ workflows: ["Lint", "Run Python đ tests"]
+ types:
+ - completed
+ branches:
+ - main
+
+jobs:
+ # Check if docs changed and all checks passed
+ check-and-deploy:
+ runs-on: ubuntu-latest
+ # Only proceed if the triggering workflow succeeded
+ if: github.event.workflow_run.conclusion == 'success'
+ permissions:
+ contents: read
+ pages: write
+ id-token: write
+ actions: read
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.workflow_run.head_branch }}
+
+ # Check if both required workflows have passed for this commit
+ - name: Check workflow status
+ id: check-workflows
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const commitSha = context.payload.workflow_run.head_sha;
+ const owner = context.repo.owner;
+ const repo = context.repo.repo;
+
+ // Get all workflow runs for this commit
+ const { data: runs } = await github.rest.actions.listWorkflowRunsForRepo({
+ owner,
+ repo,
+ head_sha: commitSha,
+ per_page: 100
+ });
+
+ // Check if both Lint and test workflows passed
+ const lintRun = runs.workflow_runs.find(run => run.name === 'Lint');
+ const testRun = runs.workflow_runs.find(run => run.name === 'Run Python đ tests');
+
+ const bothPassed =
+ lintRun?.conclusion === 'success' &&
+ testRun?.conclusion === 'success';
+
+ core.setOutput('both-passed', bothPassed);
+
+ # Check if docs changed in this commit
+ - uses: dorny/paths-filter@v3
+ id: filter
+ if: steps.check-workflows.outputs.both-passed == 'true'
+ with:
+ filters: |
+ docs:
+ - 'docs/**'
+ - 'mkdocs.yml'
+
+ # Only deploy if docs actually changed and both workflows passed
+ - name: Install uv
+ if: steps.filter.outputs.docs == 'true'
+ uses: astral-sh/setup-uv@v7
+ with:
+ enable-cache: true
+ - name: Set up Python
+ if: steps.filter.outputs.docs == 'true'
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+ - name: Install dependencies
+ if: steps.filter.outputs.docs == 'true'
+ run: |
+ uv sync --extra docs --extra dev
+ # Ensure package is installed in editable mode for mkdocstrings
+ uv pip install -e ".[docs]"
+ - name: Setup Pages
+ if: steps.filter.outputs.docs == 'true'
+ uses: actions/configure-pages@v5
+ - name: Build with MkDocs
+ if: steps.filter.outputs.docs == 'true'
+ run: uv run mkdocs build
+ - name: Upload artifact
+ if: steps.filter.outputs.docs == 'true'
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: ./site
+ - name: Deploy to GitHub Pages
+ if: steps.filter.outputs.docs == 'true'
+ id: deployment
+ uses: actions/deploy-pages@v4
+
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 00000000..dadefd31
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,19 @@
+name: Lint
+
+on: [push, pull_request]
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+ - name: Install ruff
+ run: pip install ruff==0.6.3
+ - name: Run ruff check
+ run: ruff check .
+ - name: Run ruff format check
+ run: ruff format --check .
+
diff --git a/.github/workflows/runtests.yml b/.github/workflows/runtests.yml
index 99467e99..96a455cc 100644
--- a/.github/workflows/runtests.yml
+++ b/.github/workflows/runtests.yml
@@ -7,13 +7,13 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ["3.7", "3.8", "3.9", "3.10"]
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
fail-fast: false
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
diff --git a/.gitignore b/.gitignore
index fc77404d..7a4872a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,19 +2,88 @@
.idea/
*.xml
*.iml
+.vscode/
+*.swp
+*.swo
+*~
+.project
+.pydevproject
+.settings/
# Python
__pycache__/
*.py[cod]
*$py.class
+*.so
+.Python
# Environments
.env
+.env.*
+!.env.example
.venv/
venv/
env/
+ENV/
+env.bak/
+venv.bak/
# Distribution / Packaging
dist/
build/
*.egg-info/
+.eggs/
+*.egg
+*.whl
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Testing
+.pytest_cache/
+.coverage
+.coverage.*
+htmlcov/
+.tox/
+.nox/
+.hypothesis/
+.pytest_cache/
+test-results/
+*.cover
+*.log
+.cache
+
+# Type checking
+.mypy_cache/
+.dmypy.json
+dmypy.json
+.pyre/
+.pytype/
+.ruff_cache/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+*.ipynb_checkpoints/
+
+# MkDocs
+site/
+.mkdocs_cache/
+
+# OS
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+desktop.ini
+
+# Temporary files
+*.tmp
+*.temp
+*.bak
+*.swp
+*~
+
+# Pre-commit
+.pre-commit-config.yaml.bak
\ No newline at end of file
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..c1b0f902
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,8 @@
+repos:
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.6.3
+ hooks:
+ - id: ruff
+ args: [--fix, --exit-non-zero-on-fix]
+ - id: ruff-format
+
diff --git a/Pipfile b/Pipfile
deleted file mode 100644
index 5cf3ffa0..00000000
--- a/Pipfile
+++ /dev/null
@@ -1,15 +0,0 @@
-[[source]]
-url = "https://pypi.org/simple"
-verify_ssl = true
-name = "pypi"
-
-[packages]
-mechanize = "*"
-mechanicalsoup = "*"
-
-[dev-packages]
-black = "*"
-flake8 = "*"
-
-[requires]
-python_version = "3.7"
diff --git a/README.md b/README.md
index c0f6bb86..2653eb50 100644
--- a/README.md
+++ b/README.md
@@ -1,275 +1,362 @@
-# Handelsregister API
+# Handelsregister
-Das Handelsregister stellt ein öffentliches Verzeichnis dar, das im Rahmen des Registerrechts Eintragungen ĂŒber die angemeldeten Kaufleute in einem bestimmten geografischen Raum fĂŒhrt.
-Eintragungspflichtig sind die im HGB, AktG und GmbHG abschlieĂend aufgezĂ€hlten Tatsachen oder RechtsverhĂ€ltnisse. EintragungsfĂ€hig sind weitere Tatsachen, wenn Sinn und Zweck des Handelsregisters die Eintragung erfordern und fĂŒr ihre Eintragung ein erhebliches Interesse des Rechtsverkehrs besteht.
+Python-Package fĂŒr das gemeinsame Registerportal der deutschen BundeslĂ€nder.
-Die Einsichtnahme in das Handelsregister sowie in die dort eingereichten Dokumente ist daher gemÀà § 9 Abs. 1 HGB jeder und jedem zu Informationszwecken gestattet, wobei es unzulÀssig ist, mehr als 60 Abrufe pro Stunde zu tÀtigen (vgl. [Nutzungsordnung](https://www.handelsregister.de/rp_web/information.xhtml)). Die Recherche nach einzelnen Firmen, die Einsicht in die UnternehmenstrÀgerdaten und die Nutzung der Handelsregisterbekanntmachungen ist kostenfrei möglich.
+Nutzbar als **Kommandozeilen-Tool** oder als **Library** in eigenen Anwendungen.
-**Achtung:** Das Registerportal ist regelmĂ€Ăig das Ziel automatisierter Massenabfragen. Den AusfĂŒhrungen der [FAQs](https://www.handelsregister.de/rp_web/information.xhtml) zufolge erreiche die Frequenz dieser Abfragen sehr hĂ€ufig eine Höhe, bei der die StraftatbestĂ€nde der Rechtsnormen §§303a, b StGB vorliege. Mehr als 60 Abrufe pro Stunde widersprechen der Nutzungsordnung.
+## Rechtliche Hinweise
+Das Handelsregister stellt ein öffentliches Verzeichnis dar, das im Rahmen des Registerrechts Eintragungen ĂŒber die angemeldeten Kaufleute in einem bestimmten geografischen Raum fĂŒhrt. Eintragungspflichtig sind die im HGB, AktG und GmbHG abschlieĂend aufgezĂ€hlten Tatsachen oder RechtsverhĂ€ltnisse.
-## Handelsregister
+Die Einsichtnahme in das Handelsregister sowie in die dort eingereichten Dokumente ist gemÀà **§ 9 Abs. 1 HGB** jeder Person zu Informationszwecken gestattet. Die Recherche nach einzelnen Firmen, die Einsicht in die UnternehmenstrÀgerdaten und die Nutzung der Handelsregisterbekanntmachungen ist kostenfrei möglich.
-### Datenstruktur
+> **â ïž Achtung:** Es ist unzulĂ€ssig, mehr als **60 Abrufe pro Stunde** zu tĂ€tigen (vgl. [Nutzungsordnung](https://www.handelsregister.de/rp_web/information.xhtml)). Das Registerportal ist regelmĂ€Ăig das Ziel automatisierter Massenabfragen. Den [FAQs](https://www.handelsregister.de/rp_web/information.xhtml) zufolge erreiche die Frequenz dieser Abfragen sehr hĂ€ufig eine Höhe, bei der die StraftatbestĂ€nde der Rechtsnormen **§§ 303a, b StGB** vorliegen.
-***URL:*** https://www.handelsregister.de/rp_web/erweitertesuche.xhtml
-
-Das gemeinsame Registerportal der LĂ€nder ermöglicht jeder und jedem die Recherche nach einzelnen Firmen zu Informationszwecken. EintrĂ€ge lassen sich dabei ĂŒber verschiedene Parameter im Body eines POST-request filtern:
-
-
-**Parameter:** *schlagwoerter* (Optional)
-
-Schlagwörter (z.B. Test). ZulĂ€ssige Platzhalterzeichen sind fĂŒr die Suche nach genauen Firmennamen (siehe Parameter *schlagwortOptionen*) \* und ? - wobei das Sternchen fĂŒr beliebig viele (auch kein) Zeichen steht, das Fragezeichen hingegen fĂŒr genau ein Zeichen.
-
-
-**Parameter:** *schlagwortOptionen* (Optional)
-- 1
-- 2
-- 3
-
-Schlagwortoptionen: 1=alle Schlagwörter enthalten; 2=mindestens ein Schlagwort enthalten; 3=den genauen Firmennamen enthalten.
-
-
-**Parameter:** *suchOptionenAehnlich* (Optional)
-- true
-
-true=Ă€hnlich lautende Schlagwörter enthalten. Unter der Ăhnlichkeitssuche ist die sogenannte phonetische Suche zu verstehen. Hierbei handelt es sich um ein Verfahren, welches Zeichenketten und Ă€hnlich ausgesprochene Worte als identisch erkennt. Grundlage fĂŒr die Vergleichsoperation ist hier die insbesondere im Bereich der öffentlichen Verwaltung angewandte sogenannte Kölner Phonetik.
-
-
-**Parameter:** *suchOptionenGeloescht* (Optional)
-- true
-
-true=auch gelöschte Formen finden.
-
-
-**Parameter:** *suchOptionenNurZNneuenRechts* (Optional)
-- true
-
-true=nur nach Zweigniederlassungen neuen Rechts suchen.
-
-
-**Parameter:** *btnSuche* (Optional)
-- Suchen
-
-Button "Suchen"
-
-
-**Parameter:** *suchTyp* (Optional)
-- n
-- e
-
-Suchtyp: n=normal; e=extended. Die normale Suche erlaubt eine Suche ĂŒber den gesamten Registerdatenbestand der LĂ€nder anhand einer ĂŒberschaubaren Anzahl von Suchkriterien. Die erweiterte Suche bietet neben den Auswahlkriterien der normalen Suche die selektive Suche in den DatenbestĂ€nden ausgewĂ€hlter LĂ€nder, die Suche nach Rechtsformen und die Suche nach Adressen an.
-
-
-**Parameter:** *ergebnisseProSeite* (Optional)
-- 10
-- 25
-- 50
-- 100
-
-Ergebnisse pro Seite.
-
-
-**Parameter:** *niederlassung* (Optional)
-
-Niederlassung / Sitz. ZulĂ€ssige Platzhalterzeichen sind \* und ? - wobei das Sternchen fĂŒr beliebig viele (auch kein) Zeichen steht, das Fragezeichen hingegen fĂŒr genau ein Zeichen.
-
-
-**Parameter:** *bundeslandBW* (Optional)
-- on
-
-EintrĂ€ge aus Baden-WĂŒrttemberg
-
-
-**Parameter:** *bundeslandBY* (Optional)
-- on
-
-EintrÀge aus Bayern
-
-
-**Parameter:** *bundeslandBE* (Optional)
-- on
-
-EintrÀge aus Berlin
-
-
-**Parameter:** *bundeslandBR* (Optional)
-- on
-
-EintrÀge aus Bradenburg
-
-
-**Parameter:** *bundeslandHB* (Optional)
-- on
-
-EintrÀge aus Bremen
-
-
-**Parameter:** *bundeslandHH* (Optional)
-- on
-
-EintrÀge aus Hamburg
-
-
-**Parameter:** *bundeslandHE* (Optional)
-- on
+## Installation
-EintrÀge aus Hessen
+Installation mit [uv](https://docs.astral.sh/uv/) (empfohlen):
+```bash
+git clone https://github.com/bundesAPI/handelsregister.git
+cd handelsregister
+uv sync
+```
-**Parameter:** *bundeslandMV* (Optional)
-- on
-
-EintrÀge aus Mecklenburg-Vorpommern
-
-
-**Parameter:** *bundeslandNI* (Optional)
-- on
-
-EintrÀge aus Niedersachsen
-
-
-**Parameter:** *bundeslandNW* (Optional)
-- on
-
-EintrÀge aus Nordrhein-Westfalen
-
-
-**Parameter:** *bundeslandRP* (Optional)
-- on
-
-EintrÀge aus Rheinland-Pfalz
-
-
-**Parameter:** *bundeslandSL* (Optional)
-- on
-
-EintrÀge aus Saarland
-
-
-**Parameter:** *bundeslandSN* (Optional)
-- on
-
-EintrÀge aus Sachsen
-
-
-**Parameter:** *bundeslandST* (Optional)
-- on
-
-EintrÀge aus Sachsen-Anhalt
-
-
-**Parameter:** *bundeslandSH* (Optional)
-- on
-
-EintrÀge aus Schleswig-Holstein
-
-
-**Parameter:** *bundeslandTH* (Optional)
-- on
-
-EintrĂ€ge aus ThĂŒringen
-
-
-**Parameter:** *registerArt* (Optional)
-- alle
-- HRA
-- HRB
-- GnR
-- PR
-- VR
-
-Registerart (Angaben nur zur Hauptniederlassung): alle; HRA; HRB; GnR; PR; VR.
-
+Alternativ mit pip:
-**Parameter:** *registerNummer* (Optional)
+```bash
+pip install git+https://github.com/bundesAPI/handelsregister.git
+```
-Registernummer (Angaben nur zur Hauptniederlassung).
+## Verwendung als Library
+
+### Einfache API
+
+```python
+from handelsregister import search, State, RegisterType
+
+# Einfache Suche
+unternehmen = search("Deutsche Bahn")
+
+# Mit Optionen (empfohlen: Enums fĂŒr AutovervollstĂ€ndigung)
+banken = search(
+ keywords="Bank",
+ states=[State.BE, State.HH], # Nur Berlin und Hamburg
+ register_type=RegisterType.HRB, # Nur HRB-EintrÀge
+ include_deleted=False, # Keine gelöschten
+)
+
+# String-basierte API funktioniert weiterhin
+banken = search(
+ keywords="Bank",
+ states=["BE", "HH"],
+ register_type="HRB",
+ include_deleted=False,
+)
+
+# Ergebnisse verarbeiten
+for firma in banken:
+ print(f"{firma['name']} - {firma['register_num']}")
+ print(f" Gericht: {firma['court']}")
+ print(f" Status: {firma['status']}")
+```
+### Erweiterte API
+
+FĂŒr mehr Kontrolle kann die `HandelsRegister`-Klasse direkt verwendet werden:
+
+```python
+from handelsregister import (
+ HandelsRegister, SearchOptions, SearchCache,
+ State, KeywordMatch, RegisterType
+)
+
+# Mit SearchOptions (empfohlen: Enums)
+options = SearchOptions(
+ keywords="Energie",
+ keyword_option=KeywordMatch.ALL,
+ states=[State.BY, State.BW],
+ register_type=RegisterType.HRB,
+ similar_sounding=True, # Phonetische Suche
+ results_per_page=100,
+)
+
+# String-basierte API funktioniert weiterhin
+options = SearchOptions(
+ keywords="Energie",
+ keyword_option="all",
+ states=["BY", "BW"],
+ register_type="HRB",
+ similar_sounding=True,
+ results_per_page=100,
+)
+
+hr = HandelsRegister(debug=False)
+hr.open_startpage()
+ergebnisse = hr.search_with_options(options)
+
+# Mit eigenem Cache
+cache = SearchCache(ttl_seconds=7200) # 2 Stunden TTL
+hr = HandelsRegister(cache=cache)
+```
-**Parameter:** *registerGericht* (Optional)
+### Detailabruf
-Registergericht (Angaben nur zur Hauptniederlassung). Beispielsweise D3201 fĂŒr Ansbach
+Zu Suchergebnissen können erweiterte Unternehmensinformationen abgerufen werden:
+```python
+from handelsregister import search, get_details, KeywordMatch
-**Parameter:** *rechtsform* (Optional)
-- 1
-- 2
-- 3
-- 4
-- 5
-- 6
-- 7
-- 8
-- 9
-- 10
-- 12
-- 13
-- 14
-- 15
-- 16
-- 17
-- 18
-- 19
-- 40
-- 46
-- 48
-- 49
-- 51
-- 52
-- 53
-- 54
-- 55
+# Erst suchen (empfohlen: Enum)
+unternehmen = search("GASAG AG", keyword_option=KeywordMatch.EXACT)
-Rechtsform (Angaben nur zur Hauptniederlassung). 1=Aktiengesellschaft; 2=eingetragene Genossenschaft; 3=eingetragener Verein; 4=Einzelkauffrau; 5=Einzelkaufmann; 6=EuropÀische Aktiengesellschaft (SE); 7=EuropÀische wirtschaftliche Interessenvereinigung; 8=Gesellschaft mit beschrÀnkter Haftung; 9=HRA Juristische Person; 10=Kommanditgesellschaft; 12=Offene Handelsgesellschaft; 13=Partnerschaft; 14=Rechtsform auslÀndischen Rechts GnR; 15=Rechtsform auslÀndischen Rechts HRA; 16=Rechtsform auslÀndischen Rechts HRb; 17=Rechtsform auslÀndischen Rechts PR; 18=Seerechtliche Gesellschaft; 19=Versicherungsverein auf Gegenseitigkeit; 40=Anstalt öffentlichen Rechts; 46=Bergrechtliche Gesellschaft; 48=Körperschaft öffentlichen Rechts; 49= EuropÀische Genossenschaft (SCE); 51=Stiftung privaten Rechts; 52=Stiftung öffentlichen Rechts; 53=HRA sonstige Rechtsformen; 54=Sonstige juristische Person; 55=Einzelkaufmann/Einzelkauffrau
+# String-basierte API funktioniert weiterhin
+unternehmen = search("GASAG AG", keyword_option="exact")
+# Dann Details abrufen
+if unternehmen:
+ details = get_details(unternehmen[0], detail_type="SI")
+
+ print(f"Firma: {details.name}")
+ print(f"Rechtsform: {details.legal_form}")
+ print(f"Kapital: {details.capital} {details.currency}")
+ print(f"Adresse: {details.address}")
+
+ for gf in details.representatives:
+ print(f" {gf.role}: {gf.name}")
+```
-**Parameter:** *postleitzahl* (Optional)
+**VerfĂŒgbare Detail-Typen:**
+
+| Typ | Beschreibung |
+|-----|--------------|
+| `SI` | Strukturierter Registerinhalt (empfohlen, maschinenlesbar) |
+| `AD` | Aktueller Abdruck (formatierter Text) |
+| `UT` | UnternehmenstrÀger (Gesellschafter/Inhaber) |
+
+### RĂŒckgabeformat
+
+**Suchergebnisse** werden als Dictionary zurĂŒckgegeben:
+
+```python
+{
+ 'name': 'GASAG AG',
+ 'court': 'Berlin District court Berlin (Charlottenburg) HRB 44343',
+ 'register_num': 'HRB 44343 B',
+ 'state': 'Berlin',
+ 'status': 'currently registered',
+ 'statusCurrent': 'CURRENTLY_REGISTERED',
+ 'documents': 'ADCDHDDKUTVĂSI',
+ 'history': [('Alter Firmenname', 'Berlin')]
+}
+```
-Postleitzahl (Angaben nur zur Hauptniederlassung). Beispielsweise 90537 fĂŒr Feucht. ZulĂ€ssige Platzhalterzeichen sind \* und ? - wobei das Sternchen fĂŒr beliebig viele (auch kein) Zeichen steht, das Fragezeichen hingegen fĂŒr genau ein Zeichen.
+**CompanyDetails** enthÀlt erweiterte Informationen:
+
+```python
+{
+ 'name': 'GASAG AG',
+ 'register_num': 'HRB 44343 B',
+ 'court': 'Amtsgericht Berlin (Charlottenburg)',
+ 'state': 'Berlin',
+ 'status': 'aktuell',
+ 'legal_form': 'Aktiengesellschaft',
+ 'capital': '307.200.000',
+ 'currency': 'EUR',
+ 'address': {
+ 'street': 'GASAG-Platz 1',
+ 'postal_code': '10965',
+ 'city': 'Berlin',
+ 'country': 'Deutschland'
+ },
+ 'purpose': 'Versorgung mit Energie...',
+ 'representatives': [
+ {'name': 'Dr. Max Mustermann', 'role': 'Vorstand', 'location': 'Berlin'}
+ ],
+ 'owners': [],
+ 'registration_date': '01.01.1990',
+ 'last_update': None,
+ 'deletion_date': None
+}
+```
+## Verwendung als CLI
+Die CLI kann ĂŒber `handelsregister` oder die kĂŒrzere Variante `hrg` aufgerufen werden.
-**Parameter:** *ort* (Optional)
+### Kommandozeilen-Schnittstelle
-Ort (Angaben nur zur Hauptniederlassung). Beispielsweise Feucht. ZulĂ€ssige Platzhalterzeichen sind \* und ? - wobei das Sternchen fĂŒr beliebig viele (auch kein) Zeichen steht, das Fragezeichen hingegen fĂŒr genau ein Zeichen.
+```
+handelsregister.py [-h] [-d] [-f] [-j] -s SCHLAGWĂRTER [-so OPTION]
+ [--states CODES] [--register-type TYP]
+ [--register-number NUMMER] [--include-deleted]
+ [--similar-sounding] [--results-per-page N]
+ [--details] [--detail-type TYP]
+
+Optionen:
+ -h, --help Hilfe anzeigen
+ -d, --debug Debug-Modus aktivieren
+ -f, --force Cache ignorieren und neue Daten abrufen
+ -j, --json Ausgabe als JSON
+
+Suchparameter:
+ -s, --schlagwoerter Suchbegriffe (erforderlich)
+ -so, --schlagwortOptionen
+ Suchmodus: all=alle Begriffe; min=mindestens einer; exact=exakter Name
+ --states CODES Kommagetrennte Bundesland-Codes (z.B. BE,BY,HH)
+ --register-type TYP Registerart filtern (HRA, HRB, GnR, PR, VR)
+ --register-number Nach bestimmter Registernummer suchen
+ --include-deleted Auch gelöschte EintrÀge anzeigen
+ --similar-sounding Phonetische Suche (Kölner Phonetik)
+ --results-per-page N Ergebnisse pro Seite (10, 25, 50, 100)
+
+Detailoptionen:
+ --details Erweiterte Unternehmensinfos abrufen
+ --detail-type TYP Art der Details: SI=strukturiert, AD=Abdruck, UT=Inhaber
+```
+### Bundesland-Codes
+
+| Code | Bundesland |
+|------|------------|
+| BW | Baden-WĂŒrttemberg |
+| BY | Bayern |
+| BE | Berlin |
+| BR | Brandenburg |
+| HB | Bremen |
+| HH | Hamburg |
+| HE | Hessen |
+| MV | Mecklenburg-Vorpommern |
+| NI | Niedersachsen |
+| NW | Nordrhein-Westfalen |
+| RP | Rheinland-Pfalz |
+| SL | Saarland |
+| SN | Sachsen |
+| ST | Sachsen-Anhalt |
+| SH | Schleswig-Holstein |
+| TH | ThĂŒringen |
+
+### Beispiele
+
+```bash
+# Einfache Suche
+uv run handelsregister -s "Deutsche Bahn" -so all
+# Oder kĂŒrzer:
+uv run hrg -s "Deutsche Bahn" -so all
+
+# Suche mit JSON-Ausgabe
+uv run handelsregister -s "GASAG AG" -so exact --json
+# Oder:
+uv run hrg -s "GASAG AG" -so exact --json
+
+# Nach Bundesland und Registerart filtern
+uv run handelsregister -s "Bank" --states BE,HH --register-type HRB
+# Oder:
+uv run hrg -s "Bank" --states BE,HH --register-type HRB
+
+# Gelöschte EintrÀge mit phonetischer Suche
+uv run handelsregister -s "Mueller" --include-deleted --similar-sounding
+
+# Cache ignorieren (neue Daten abrufen)
+uv run handelsregister -s "Volkswagen" -f --debug
+
+# Mit Detailabruf (GeschĂ€ftsfĂŒhrer, Kapital, Adresse)
+uv run handelsregister -s "GASAG AG" --details
+
+# Details als JSON (fĂŒr Weiterverarbeitung)
+uv run handelsregister -s "GASAG AG" --details --json
+
+# Spezifischer Detail-Typ (UnternehmenstrÀger)
+uv run handelsregister -s "Test GmbH" --details --detail-type UT
+```
+## Tests
-**Parameter:** *strasse* (Optional)
+```bash
+# Unit-Tests ausfĂŒhren (schnell, ohne Netzwerkzugriff)
+uv run pytest
-StraĂe (Angaben nur zur Hauptniederlassung). Beispielsweise TeststraĂe 2. ZulĂ€ssige Platzhalterzeichen sind \* und ? - wobei das Sternchen fĂŒr beliebig viele (auch kein) Zeichen steht, das Fragezeichen hingegen fĂŒr genau ein Zeichen.
+# Alle Tests inkl. Integrationstests (greift auf Live-API zu)
+uv run pytest -m integration
-### Installation with poetry
-Example installation and execution with [poetry](https://python-poetry.org/):
-```commandline
-git clone https://github.com/bundesAPI/handelsregister.git
-cd handelsregister
-poetry install
-poetry run python handelsregister.py -s "deutsche bahn" -so all
-```
-Run tests:
-```commandline
-poetry run python -m pytest
+# Mit ausfĂŒhrlicher Ausgabe
+uv run pytest -v
```
+## API-Parameter
-### Command-line Interface
-
-Das CLI ist _work in progress_ und
+***URL:*** https://www.handelsregister.de/rp_web/erweitertesuche.xhtml
-```
-usage: handelsregister.py [-h] [-d] [-f] -s SCHLAGWOERTER [-so {all,min,exact}]
-
-A handelsregister CLI
-
-options:
- -h, --help show this help message and exit
- -d, --debug Enable debug mode and activate logging
- -f, --force Force a fresh pull and skip the cache
- -s SCHLAGWOERTER, --schlagwoerter SCHLAGWOERTER
- Search for the provided keywords
- -so {all,min,exact}, --schlagwortOptionen {all,min,exact}
- Keyword options: all=contain all keywords; min=contain at least one
- keyword; exact=contain the exact company name.
-```
+Das gemeinsame Registerportal der LĂ€nder ermöglicht die Recherche nach einzelnen Firmen zu Informationszwecken. EintrĂ€ge lassen sich ĂŒber verschiedene Parameter filtern:
+
+### Suchparameter
+
+| Parameter | Werte | Beschreibung |
+|-----------|-------|--------------|
+| `schlagwoerter` | Text | Suchbegriffe. Platzhalter: `*` (beliebig viele Zeichen), `?` (genau ein Zeichen) |
+| `schlagwortOptionen` | 1, 2, 3 | 1=alle enthalten; 2=mindestens einer; 3=exakter Firmenname |
+| `suchOptionenAehnlich` | true | Phonetische Suche (Kölner Phonetik) |
+| `suchOptionenGeloescht` | true | Auch gelöschte Firmen finden |
+| `ergebnisseProSeite` | 10, 25, 50, 100 | Anzahl Ergebnisse pro Seite |
+
+### Filterparameter
+
+| Parameter | Werte | Beschreibung |
+|-----------|-------|--------------|
+| `registerArt` | alle, HRA, HRB, GnR, PR, VR | Registerart |
+| `registerNummer` | Text | Registernummer |
+| `registerGericht` | Code | Registergericht (z.B. D3201 fĂŒr Ansbach) |
+| `niederlassung` | Text | Niederlassung / Sitz |
+| `postleitzahl` | Text | Postleitzahl |
+| `ort` | Text | Ort |
+| `strasse` | Text | StraĂe |
+
+### Bundesland-Filter
+
+| Parameter | Bundesland |
+|-----------|------------|
+| `bundeslandBW` | Baden-WĂŒrttemberg |
+| `bundeslandBY` | Bayern |
+| `bundeslandBE` | Berlin |
+| `bundeslandBR` | Brandenburg |
+| `bundeslandHB` | Bremen |
+| `bundeslandHH` | Hamburg |
+| `bundeslandHE` | Hessen |
+| `bundeslandMV` | Mecklenburg-Vorpommern |
+| `bundeslandNI` | Niedersachsen |
+| `bundeslandNW` | Nordrhein-Westfalen |
+| `bundeslandRP` | Rheinland-Pfalz |
+| `bundeslandSL` | Saarland |
+| `bundeslandSN` | Sachsen |
+| `bundeslandST` | Sachsen-Anhalt |
+| `bundeslandSH` | Schleswig-Holstein |
+| `bundeslandTH` | ThĂŒringen |
+
+### Rechtsformen
+
+| Code | Rechtsform |
+|------|------------|
+| 1 | Aktiengesellschaft |
+| 2 | Eingetragene Genossenschaft |
+| 3 | Eingetragener Verein |
+| 4 | Einzelkauffrau |
+| 5 | Einzelkaufmann |
+| 6 | EuropÀische Aktiengesellschaft (SE) |
+| 7 | EuropÀische wirtschaftliche Interessenvereinigung |
+| 8 | Gesellschaft mit beschrÀnkter Haftung |
+| 9 | HRA Juristische Person |
+| 10 | Kommanditgesellschaft |
+| 12 | Offene Handelsgesellschaft |
+| 13 | Partnerschaft |
+| 18 | Seerechtliche Gesellschaft |
+| 19 | Versicherungsverein auf Gegenseitigkeit |
+| 40 | Anstalt öffentlichen Rechts |
+| 46 | Bergrechtliche Gesellschaft |
+| 48 | Körperschaft öffentlichen Rechts |
+| 49 | EuropÀische Genossenschaft (SCE) |
+| 51 | Stiftung privaten Rechts |
+| 52 | Stiftung öffentlichen Rechts |
+
+## Lizenz
+
+Dieses Projekt ist Teil der [bundesAPI](https://github.com/bundesAPI) Initiative.
diff --git a/conftest.py b/conftest.py
new file mode 100644
index 00000000..97ba8f3b
--- /dev/null
+++ b/conftest.py
@@ -0,0 +1,52 @@
+"""Pytest configuration for handelsregister tests."""
+
+import contextlib
+
+import pytest
+
+from handelsregister import HandelsRegister
+
+
+def pytest_collection_modifyitems(config, items):
+ """Skip integration tests by default unless explicitly requested."""
+ if config.getoption("-m"):
+ # If markers are explicitly specified, respect them
+ return
+
+ skip_integration = pytest.mark.skip(
+ reason="Integration tests skipped by default. Use -m integration to run."
+ )
+ for item in items:
+ if "integration" in item.keywords:
+ item.add_marker(skip_integration)
+
+
+@pytest.fixture(scope="class")
+def shared_hr_client(request):
+ """Shared HandelsRegister client for integration tests to reduce API calls.
+
+ This fixture creates a single HandelsRegister instance that is shared across
+ all tests in a test class. The startpage is opened once, and the browser
+ session is reused to minimize API requests.
+
+ Usage:
+ def test_something(self, shared_hr_client):
+ # Use shared_hr_client instead of creating a new instance
+ results = shared_hr_client.search_with_options(...)
+ """
+ # Only create shared client for integration tests
+ if "integration" not in request.keywords:
+ return None
+
+ # Create a shared client instance
+ client = HandelsRegister(debug=False)
+ # Open startpage once for the entire test class
+ client.open_startpage()
+
+ # Yield client to tests
+ yield client
+
+ # Cleanup after all tests in the class are done
+ if hasattr(client.browser, "close"):
+ with contextlib.suppress(Exception):
+ client.browser.close()
diff --git a/docs/api/classes.de.md b/docs/api/classes.de.md
new file mode 100644
index 00000000..e12235cc
--- /dev/null
+++ b/docs/api/classes.de.md
@@ -0,0 +1,132 @@
+# Klassen
+
+Diese Seite dokumentiert die Hauptklassen des Handelsregister-Packages.
+
+---
+
+## HandelsRegister
+
+::: handelsregister.HandelsRegister
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+ members:
+ - __init__
+ - search
+ - get_details
+
+### Verwendungsbeispiele
+
+```python
+from handelsregister import HandelsRegister
+
+# Instanz erstellen
+hr = HandelsRegister()
+
+# Suchen
+firmen = hr.search("Deutsche Bahn")
+
+# Details abrufen
+if firmen:
+ details = hr.get_details(firmen[0])
+```
+
+### Mit benutzerdefiniertem Cache
+
+```python
+from handelsregister import HandelsRegister, SearchCache
+
+# Benutzerdefinierter Cache mit 1-Stunden-TTL
+cache = SearchCache(ttl_hours=1)
+hr = HandelsRegister(cache=cache)
+
+# Ohne Cache
+hr = HandelsRegister(cache=None)
+```
+
+---
+
+## SearchCache
+
+::: handelsregister.SearchCache
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+ members:
+ - __init__
+ - get
+ - set
+ - clear
+ - cleanup_expired
+ - get_stats
+
+### Verwendungsbeispiele
+
+```python
+from handelsregister import SearchCache
+
+# Standard-Cache
+cache = SearchCache()
+
+# Benutzerdefinierte TTL
+cache = SearchCache(ttl_hours=1)
+
+# Benutzerdefiniertes Verzeichnis
+cache = SearchCache(cache_dir="/tmp/hr-cache")
+
+# Statistiken abrufen
+stats = cache.get_stats()
+print(f"EintrÀge: {stats['total']}")
+print(f"GröĂe: {stats['size_mb']:.2f} MB")
+
+# Bereinigen
+cache.cleanup_expired()
+
+# Alles löschen
+cache.clear()
+```
+
+---
+
+## ResultParser
+
+::: handelsregister.ResultParser
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Interne Verwendung
+
+Diese Klasse ist hauptsĂ€chlich fĂŒr die interne Verwendung. Sie parst HTML-Suchergebnisse vom Registerportal.
+
+```python
+from handelsregister import ResultParser
+
+parser = ResultParser()
+firmen = parser.parse(html_inhalt)
+```
+
+---
+
+## DetailsParser
+
+::: handelsregister.DetailsParser
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Interne Verwendung
+
+Diese Klasse ist hauptsĂ€chlich fĂŒr die interne Verwendung. Sie parst HTML-Detailseiten vom Registerportal.
+
+```python
+from handelsregister import DetailsParser
+
+parser = DetailsParser()
+details = parser.parse(html_inhalt)
+```
+
diff --git a/docs/api/classes.md b/docs/api/classes.md
new file mode 100644
index 00000000..1803e118
--- /dev/null
+++ b/docs/api/classes.md
@@ -0,0 +1,132 @@
+# Classes
+
+This page documents the main classes of the Handelsregister package.
+
+---
+
+## HandelsRegister
+
+::: handelsregister.HandelsRegister
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+ members:
+ - __init__
+ - search
+ - get_details
+
+### Usage Examples
+
+```python
+from handelsregister import HandelsRegister
+
+# Create instance
+hr = HandelsRegister()
+
+# Search
+companies = hr.search("Deutsche Bahn")
+
+# Get details
+if companies:
+ details = hr.get_details(companies[0])
+```
+
+### With Custom Cache
+
+```python
+from handelsregister import HandelsRegister, SearchCache
+
+# Custom cache with 1-hour TTL
+cache = SearchCache(ttl_hours=1)
+hr = HandelsRegister(cache=cache)
+
+# Without cache
+hr = HandelsRegister(cache=None)
+```
+
+---
+
+## SearchCache
+
+::: handelsregister.SearchCache
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+ members:
+ - __init__
+ - get
+ - set
+ - clear
+ - cleanup_expired
+ - get_stats
+
+### Usage Examples
+
+```python
+from handelsregister import SearchCache
+
+# Default cache
+cache = SearchCache()
+
+# Custom TTL
+cache = SearchCache(ttl_hours=1)
+
+# Custom directory
+cache = SearchCache(cache_dir="/tmp/hr-cache")
+
+# Get statistics
+stats = cache.get_stats()
+print(f"Entries: {stats['total']}")
+print(f"Size: {stats['size_mb']:.2f} MB")
+
+# Cleanup
+cache.cleanup_expired()
+
+# Clear all
+cache.clear()
+```
+
+---
+
+## ResultParser
+
+::: handelsregister.ResultParser
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Internal Use
+
+This class is primarily for internal use. It parses HTML search results from the register portal.
+
+```python
+from handelsregister import ResultParser
+
+parser = ResultParser()
+companies = parser.parse(html_content)
+```
+
+---
+
+## DetailsParser
+
+::: handelsregister.DetailsParser
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Internal Use
+
+This class is primarily for internal use. It parses HTML detail pages from the register portal.
+
+```python
+from handelsregister import DetailsParser
+
+parser = DetailsParser()
+details = parser.parse(html_content)
+```
+
diff --git a/docs/api/exceptions.de.md b/docs/api/exceptions.de.md
new file mode 100644
index 00000000..0a590fdd
--- /dev/null
+++ b/docs/api/exceptions.de.md
@@ -0,0 +1,220 @@
+# Exceptions
+
+Diese Seite dokumentiert die Exception-Typen fĂŒr die Fehlerbehandlung.
+
+---
+
+## Exception-Hierarchie
+
+```
+Exception
+âââ HandelsregisterError (Basis-Exception)
+ âââ NetworkError
+ âââ ParseError
+ âââ FormError
+ âââ CacheError
+```
+
+---
+
+## HandelsregisterError
+
+::: handelsregister.HandelsregisterError
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+Basis-Exception fĂŒr alle Handelsregister-bezogenen Fehler.
+
+### Verwendung
+
+```python
+from handelsregister import search, HandelsregisterError
+
+try:
+ firmen = search("Bank")
+except HandelsregisterError as e:
+ print(f"Handelsregister-Fehler: {e}")
+```
+
+---
+
+## NetworkError
+
+::: handelsregister.NetworkError
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+Wird ausgelöst, wenn die Verbindung zum Registerportal fehlschlÀgt.
+
+### Verwendung
+
+```python
+from handelsregister import search, NetworkError
+
+try:
+ firmen = search("Bank")
+except NetworkError as e:
+ print(f"Verbindung nicht möglich: {e}")
+ # SpÀter erneut versuchen oder Benutzer benachrichtigen
+```
+
+---
+
+## ParseError
+
+::: handelsregister.ParseError
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+Wird ausgelöst, wenn die HTML-Antwort nicht geparst werden kann.
+
+Dies deutet normalerweise darauf hin, dass das Registerportal seine HTML-Struktur geÀndert hat.
+
+### Verwendung
+
+```python
+from handelsregister import search, ParseError
+
+try:
+ firmen = search("Bank")
+except ParseError as e:
+ print(f"Antwort konnte nicht geparst werden: {e}")
+ print("Das Registerportal hat sich möglicherweise geÀndert.")
+ print("Bitte melden Sie dieses Problem auf GitHub.")
+```
+
+---
+
+## FormError
+
+::: handelsregister.FormError
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+Wird ausgelöst, wenn ein Fehler bei der FormularĂŒbermittlung auftritt.
+
+### Verwendung
+
+```python
+from handelsregister import search, FormError
+
+try:
+ firmen = search("Bank")
+except FormError as e:
+ print(f"Formular-Fehler: {e}")
+```
+
+---
+
+## CacheError
+
+::: handelsregister.CacheError
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+Wird ausgelöst, wenn ein Fehler mit dem Caching-System auftritt.
+
+### Verwendung
+
+```python
+from handelsregister import search, CacheError
+
+try:
+ firmen = search("Bank")
+except CacheError as e:
+ print(f"Cache-Fehler: {e}")
+ # Ohne Cache versuchen
+ firmen = search("Bank", force_refresh=True)
+```
+
+---
+
+## VollstÀndige Fehlerbehandlung
+
+```python
+from handelsregister import (
+ search,
+ get_details,
+ HandelsregisterError,
+ NetworkError,
+ ParseError,
+ FormError,
+ CacheError,
+)
+import logging
+
+logger = logging.getLogger(__name__)
+
+def robuste_suche(keywords, **kwargs):
+ """Suche mit umfassender Fehlerbehandlung."""
+ try:
+ return search(keywords, **kwargs)
+
+ except NetworkError as e:
+ logger.error(f"Verbindung fehlgeschlagen: {e}")
+ raise
+
+ except ParseError as e:
+ logger.error(f"Parse-Fehler: {e}")
+ raise
+
+ except FormError as e:
+ logger.error(f"Formular-Fehler: {e}")
+ raise
+
+ except CacheError as e:
+ logger.warning(f"Cache-Fehler: {e}, wiederhole ohne Cache")
+ return search(keywords, force_refresh=True, **kwargs)
+
+ except HandelsregisterError as e:
+ logger.error(f"Allgemeiner Fehler: {e}")
+ raise
+
+def robuste_details(firma):
+ """Details mit Fehlerbehandlung abrufen."""
+ try:
+ return get_details(firma)
+
+ except HandelsregisterError as e:
+ logger.error(f"Details fĂŒr {firma.name} nicht abrufbar: {e}")
+ return None
+```
+
+---
+
+## Rate-Limiting
+
+!!! warning "Rate-Limit"
+ Das Registerportal erlaubt maximal **60 Anfragen pro Stunde**. Es gibt zwar keinen dedizierten `RateLimitError`, aber das Ăberschreiten dieses Limits kann zu `NetworkError` oder Verbindungsproblemen fĂŒhren.
+
+### Rate-Limiting implementieren
+
+```python
+import time
+from handelsregister import search
+
+def suche_mit_verzoegerung(suchbegriffe_liste):
+ """Suche mit Rate-Limiting."""
+ ergebnisse = {}
+ for keywords in suchbegriffe_liste:
+ ergebnisse[keywords] = search(keywords)
+ time.sleep(60) # 1 Minute zwischen Anfragen
+ return ergebnisse
+```
+
+---
+
+## Siehe auch
+
+- [Als Library verwenden](../guide/library.md) â Beispiele zur Fehlerbehandlung
+- [Best Practices](../guide/library.md#best-practices) â Retry-Muster
diff --git a/docs/api/exceptions.md b/docs/api/exceptions.md
new file mode 100644
index 00000000..0fb804ea
--- /dev/null
+++ b/docs/api/exceptions.md
@@ -0,0 +1,220 @@
+# Exceptions
+
+This page documents the exception types for error handling.
+
+---
+
+## Exception Hierarchy
+
+```
+Exception
+âââ HandelsregisterError (Base exception)
+ âââ NetworkError
+ âââ ParseError
+ âââ FormError
+ âââ CacheError
+```
+
+---
+
+## HandelsregisterError
+
+::: handelsregister.HandelsregisterError
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+Base exception for all Handelsregister-related errors.
+
+### Usage
+
+```python
+from handelsregister import search, HandelsregisterError
+
+try:
+ companies = search("Bank")
+except HandelsregisterError as e:
+ print(f"Handelsregister error: {e}")
+```
+
+---
+
+## NetworkError
+
+::: handelsregister.NetworkError
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+Raised when connection to the register portal fails.
+
+### Usage
+
+```python
+from handelsregister import search, NetworkError
+
+try:
+ companies = search("Bank")
+except NetworkError as e:
+ print(f"Could not connect: {e}")
+ # Maybe try again later or notify user
+```
+
+---
+
+## ParseError
+
+::: handelsregister.ParseError
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+Raised when the HTML response cannot be parsed.
+
+This usually indicates the register portal has changed its HTML structure.
+
+### Usage
+
+```python
+from handelsregister import search, ParseError
+
+try:
+ companies = search("Bank")
+except ParseError as e:
+ print(f"Could not parse response: {e}")
+ print("The register portal may have changed.")
+ print("Please report this issue on GitHub.")
+```
+
+---
+
+## FormError
+
+::: handelsregister.FormError
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+Raised when there's an error with form submission.
+
+### Usage
+
+```python
+from handelsregister import search, FormError
+
+try:
+ companies = search("Bank")
+except FormError as e:
+ print(f"Form error: {e}")
+```
+
+---
+
+## CacheError
+
+::: handelsregister.CacheError
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+Raised when there's an error with the caching system.
+
+### Usage
+
+```python
+from handelsregister import search, CacheError
+
+try:
+ companies = search("Bank")
+except CacheError as e:
+ print(f"Cache error: {e}")
+ # Try without cache
+ companies = search("Bank", force_refresh=True)
+```
+
+---
+
+## Complete Error Handling
+
+```python
+from handelsregister import (
+ search,
+ get_details,
+ HandelsregisterError,
+ NetworkError,
+ ParseError,
+ FormError,
+ CacheError,
+)
+import logging
+
+logger = logging.getLogger(__name__)
+
+def robust_search(keywords, **kwargs):
+ """Search with comprehensive error handling."""
+ try:
+ return search(keywords, **kwargs)
+
+ except NetworkError as e:
+ logger.error(f"Connection failed: {e}")
+ raise
+
+ except ParseError as e:
+ logger.error(f"Parse error: {e}")
+ raise
+
+ except FormError as e:
+ logger.error(f"Form error: {e}")
+ raise
+
+ except CacheError as e:
+ logger.warning(f"Cache error: {e}, retrying without cache")
+ return search(keywords, use_cache=False, **kwargs)
+
+ except HandelsregisterError as e:
+ logger.error(f"General error: {e}")
+ raise
+
+def robust_get_details(company):
+ """Get details with error handling."""
+ try:
+ return get_details(company)
+
+ except HandelsregisterError as e:
+ logger.error(f"Could not get details for {company.name}: {e}")
+ return None
+```
+
+---
+
+## Rate Limiting
+
+!!! warning "Rate Limit"
+ The register portal allows a maximum of **60 requests per hour**. While there's no dedicated `RateLimitError`, exceeding this limit may result in `NetworkError` or connection issues.
+
+### Implementing Rate Limiting
+
+```python
+import time
+from handelsregister import search
+
+def search_with_delay(keywords_list):
+ """Search with rate limiting."""
+ results = {}
+ for keywords in keywords_list:
+ results[keywords] = search(keywords)
+ time.sleep(60) # 1 minute between requests
+ return results
+```
+
+---
+
+## See Also
+
+- [Using as Library](../guide/library.md) â Error handling examples
+- [Best Practices](../guide/library.md#best-practices) â Retry patterns
diff --git a/docs/api/functions.de.md b/docs/api/functions.de.md
new file mode 100644
index 00000000..0e8977ad
--- /dev/null
+++ b/docs/api/functions.de.md
@@ -0,0 +1,111 @@
+# Ăffentliche Funktionen
+
+Diese Seite dokumentiert die öffentlichen Funktionen des Handelsregister-Packages.
+
+---
+
+## search
+
+::: handelsregister.search
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Verwendungsbeispiele
+
+```python
+from handelsregister import search
+
+# Einfache Suche
+firmen = search("Deutsche Bahn")
+
+# Mit Filtern
+firmen = search(
+ keywords="Bank",
+ register_art="HRB",
+ register_gericht="Berlin"
+)
+
+# Suche nach Registernummer
+firmen = search(
+ schlagwoerter="",
+ register_nummer="12345",
+ register_gericht="Berlin (Charlottenburg)"
+)
+```
+
+### RĂŒckgabewert
+
+Gibt eine Liste von Dictionaries mit Unternehmensinformationen zurĂŒck:
+
+```python
+[
+ {
+ "name": "Deutsche Bank AG",
+ "court": "Frankfurt am Main",
+ "register_num": "HRB 12345",
+ "register_type": "HRB",
+ "status": "aktuell eingetragen",
+ "state": "HE",
+ "history": []
+ },
+ ...
+]
+```
+
+---
+
+## get_details
+
+::: handelsregister.get_details
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Verwendungsbeispiele
+
+```python
+from handelsregister import search, get_details
+
+# Zuerst suchen
+firmen = search("GASAG AG")
+
+# Dann Details abrufen
+if firmen:
+ details = get_details(firmen[0])
+
+ print(details.name)
+ print(details.capital)
+ print(details.address)
+ print(details.representatives)
+```
+
+### RĂŒckgabewert
+
+Gibt ein `CompanyDetails`-Objekt mit vollstĂ€ndigen Unternehmensinformationen zurĂŒck.
+
+Siehe [Datenmodelle: CompanyDetails](models.md#handelsregister.CompanyDetails) fĂŒr Details.
+
+---
+
+## main
+
+::: handelsregister.cli.main
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+Der CLI-Einstiegspunkt. Diese Funktion wird aufgerufen, wenn `handelsregister` ĂŒber die Kommandozeile ausgefĂŒhrt wird.
+
+### Verwendung
+
+```bash
+# Ăber Kommandozeile ausfĂŒhren
+handelsregister -s "Deutsche Bahn"
+
+# Oder ĂŒber Python
+python -m handelsregister -s "Deutsche Bahn"
+```
diff --git a/docs/api/functions.md b/docs/api/functions.md
new file mode 100644
index 00000000..c6e4279a
--- /dev/null
+++ b/docs/api/functions.md
@@ -0,0 +1,110 @@
+# Public Functions
+
+This page documents the public functions available in the Handelsregister package.
+
+---
+
+## search
+
+::: handelsregister.search
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Usage Examples
+
+```python
+from handelsregister import search
+
+# Simple search
+companies = search("Deutsche Bahn")
+
+# With filters
+companies = search(
+ keywords="Bank",
+ register_art="HRB",
+ register_gericht="Berlin"
+)
+
+# Search by register number
+companies = search(
+ schlagwoerter="",
+ register_nummer="12345",
+ register_gericht="Berlin (Charlottenburg)"
+)
+```
+
+### Return Value
+
+Returns a list of dictionaries with company information:
+
+```python
+[
+ {
+ "name": "Deutsche Bank AG",
+ "court": "Frankfurt am Main",
+ "register_num": "HRB 12345 B",
+ "state": "Hessen",
+ "status": "currently registered",
+ "history": []
+ },
+ ...
+]
+```
+
+---
+
+## get_details
+
+::: handelsregister.get_details
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Usage Examples
+
+```python
+from handelsregister import search, get_details
+
+# Search first
+companies = search("GASAG AG")
+
+# Then get details
+if companies:
+ details = get_details(companies[0])
+
+ print(details.name)
+ print(details.capital)
+ print(details.address)
+ print(details.representatives)
+```
+
+### Return Value
+
+Returns a `CompanyDetails` object with full company information.
+
+See [Data Models: CompanyDetails](models.md#handelsregister.CompanyDetails) for details.
+
+---
+
+## main
+
+::: handelsregister.cli.main
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+The CLI entry point. This function is called when running `handelsregister` from the command line.
+
+### Usage
+
+```bash
+# Run via command line
+handelsregister -s "Deutsche Bahn"
+
+# Or via Python
+python -m handelsregister -s "Deutsche Bahn"
+```
diff --git a/docs/api/index.de.md b/docs/api/index.de.md
new file mode 100644
index 00000000..07d36958
--- /dev/null
+++ b/docs/api/index.de.md
@@ -0,0 +1,155 @@
+# API-Referenz
+
+VollstĂ€ndige technische Dokumentation fĂŒr das Handelsregister-Package.
+
+## Ăbersicht
+
+Das Package stellt folgende Hauptkomponenten bereit:
+
+| Komponente | Typ | Beschreibung |
+|------------|-----|--------------|
+| [`search()`](functions.md#handelsregister.search) | Funktion | Unternehmen suchen |
+| [`get_details()`](functions.md#handelsregister.get_details) | Funktion | Detaillierte Unternehmensinformationen abrufen |
+| [`HandelsRegister`](classes.md#handelsregister.HandelsRegister) | Klasse | Hauptklasse fĂŒr Registerzugriff |
+| [`SearchCache`](classes.md#handelsregister.SearchCache) | Klasse | Caching-Mechanismus |
+| [`Company`](models.md#handelsregister.Company) | Dataclass | Unternehmen aus Suchergebnissen |
+| [`CompanyDetails`](models.md#handelsregister.CompanyDetails) | Dataclass | Detaillierte Unternehmensinformationen |
+
+---
+
+## Schnelllinks
+
+
+
+- :material-function:{ .lg .middle } __Funktionen__
+
+ ---
+
+ Ăffentliche Funktionen fĂŒr Suche und Datenabruf.
+
+ [:octicons-arrow-right-24: Funktionen](functions.md)
+
+- :material-class:{ .lg .middle } __Klassen__
+
+ ---
+
+ Kernklassen fĂŒr Registerzugriff und Caching.
+
+ [:octicons-arrow-right-24: Klassen](classes.md)
+
+- :material-database:{ .lg .middle } __Datenmodelle__
+
+ ---
+
+ Dataclasses fĂŒr strukturierte Datendarstellung.
+
+ [:octicons-arrow-right-24: Datenmodelle](models.md)
+
+- :material-alert-circle:{ .lg .middle } __Exceptions__
+
+ ---
+
+ Exception-Typen fĂŒr Fehlerbehandlung.
+
+ [:octicons-arrow-right-24: Exceptions](exceptions.md)
+
+
+
+---
+
+## Modulstruktur
+
+```
+handelsregister
+âââ search() # Hauptsuchfunktion
+âââ get_details() # Unternehmensdetails abrufen
+âââ clear_cache() # Cache löschen
+â
+âââ HandelsRegister # Hauptklasse
+â âââ search()
+â âââ get_details()
+â âââ ...
+â
+âââ SearchCache # Caching
+â âââ get()
+â âââ set()
+â âââ clear()
+â âââ ...
+â
+âââ Datenmodelle
+â âââ Company
+â âââ CompanyDetails
+â âââ Address
+â âââ Representative
+â âââ Owner
+â âââ HistoryEntry
+â
+âââ Exceptions
+ âââ SearchError
+ âââ RateLimitError
+ âââ ConnectionError
+ âââ ParseError
+```
+
+---
+
+## Verwendungsmuster
+
+```python
+from handelsregister import (
+ # Funktionen
+ search,
+ get_details,
+ clear_cache,
+
+ # Klassen
+ HandelsRegister,
+ SearchCache,
+
+ # Datenmodelle
+ Company,
+ CompanyDetails,
+ Address,
+ Representative,
+
+ # Exceptions
+ SearchError,
+ RateLimitError,
+)
+
+# Grundlegende Verwendung
+firmen = search("Deutsche Bahn")
+
+# Mit Fehlerbehandlung
+try:
+ firmen = search("Bank", states=["BE"])
+ for firma in firmen:
+ details = get_details(firma)
+ verarbeite(details)
+except RateLimitError:
+ print("Rate-Limit ĂŒberschritten")
+except SearchError as e:
+ print(f"Fehler: {e}")
+```
+
+---
+
+## Type Hints
+
+Das Package ist vollstÀndig typisiert. Sie können Type Hints in Ihrem Code verwenden:
+
+```python
+from handelsregister import search, get_details
+from handelsregister import Company, CompanyDetails
+from typing import List
+
+def finde_banken(bundesland: str) -> List[Company]:
+ """Findet alle Banken in einem Bundesland."""
+ return search("Bank", states=[bundesland], register_type="HRB")
+
+def hole_kapital(firma: Company) -> str | None:
+ """Gibt das Kapital eines Unternehmens zurĂŒck."""
+ details: CompanyDetails = get_details(firma)
+ return details.capital
+```
+
diff --git a/docs/api/index.md b/docs/api/index.md
new file mode 100644
index 00000000..ffce2da1
--- /dev/null
+++ b/docs/api/index.md
@@ -0,0 +1,155 @@
+# API Reference
+
+Complete technical documentation for the Handelsregister package.
+
+## Overview
+
+The package exposes the following main components:
+
+| Component | Type | Description |
+|-----------|------|-------------|
+| [`search()`](functions.md#handelsregister.search) | Function | Search for companies |
+| [`get_details()`](functions.md#handelsregister.get_details) | Function | Get detailed company information |
+| [`HandelsRegister`](classes.md#handelsregister.HandelsRegister) | Class | Main class for register access |
+| [`SearchCache`](classes.md#handelsregister.SearchCache) | Class | Caching mechanism |
+| [`Company`](models.md#handelsregister.Company) | Dataclass | Company from search results |
+| [`CompanyDetails`](models.md#handelsregister.CompanyDetails) | Dataclass | Detailed company information |
+
+---
+
+## Quick Links
+
+
+
+- :material-function:{ .lg .middle } __Functions__
+
+ ---
+
+ Public functions for searching and fetching data.
+
+ [:octicons-arrow-right-24: Functions](functions.md)
+
+- :material-class:{ .lg .middle } __Classes__
+
+ ---
+
+ Core classes for register access and caching.
+
+ [:octicons-arrow-right-24: Classes](classes.md)
+
+- :material-database:{ .lg .middle } __Data Models__
+
+ ---
+
+ Dataclasses for structured data representation.
+
+ [:octicons-arrow-right-24: Data Models](models.md)
+
+- :material-alert-circle:{ .lg .middle } __Exceptions__
+
+ ---
+
+ Exception types for error handling.
+
+ [:octicons-arrow-right-24: Exceptions](exceptions.md)
+
+
+
+---
+
+## Module Structure
+
+```
+handelsregister
+âââ search() # Main search function
+âââ get_details() # Get company details
+âââ clear_cache() # Clear the cache
+â
+âââ HandelsRegister # Main class
+â âââ search()
+â âââ get_details()
+â âââ ...
+â
+âââ SearchCache # Caching
+â âââ get()
+â âââ set()
+â âââ clear()
+â âââ ...
+â
+âââ Data Models
+â âââ Company
+â âââ CompanyDetails
+â âââ Address
+â âââ Representative
+â âââ Owner
+â âââ HistoryEntry
+â
+âââ Exceptions
+ âââ SearchError
+ âââ RateLimitError
+ âââ ConnectionError
+ âââ ParseError
+```
+
+---
+
+## Usage Pattern
+
+```python
+from handelsregister import (
+ # Functions
+ search,
+ get_details,
+ clear_cache,
+
+ # Classes
+ HandelsRegister,
+ SearchCache,
+
+ # Data Models
+ Company,
+ CompanyDetails,
+ Address,
+ Representative,
+
+ # Exceptions
+ SearchError,
+ RateLimitError,
+)
+
+# Basic usage
+companies = search("Deutsche Bahn")
+
+# With error handling
+try:
+ companies = search("Bank", states=["BE"])
+ for company in companies:
+ details = get_details(company)
+ process(details)
+except RateLimitError:
+ print("Rate limit exceeded")
+except SearchError as e:
+ print(f"Error: {e}")
+```
+
+---
+
+## Type Hints
+
+The package is fully typed. You can use type hints in your code:
+
+```python
+from handelsregister import search, get_details
+from handelsregister import Company, CompanyDetails
+from typing import List
+
+def find_banks(state: str) -> List[Company]:
+ """Find all banks in a state."""
+ return search("Bank", states=[state], register_type="HRB")
+
+def get_capital(company: Company) -> str | None:
+ """Get the capital of a company."""
+ details: CompanyDetails = get_details(company)
+ return details.capital
+```
+
diff --git a/docs/api/models.de.md b/docs/api/models.de.md
new file mode 100644
index 00000000..2d440d93
--- /dev/null
+++ b/docs/api/models.de.md
@@ -0,0 +1,240 @@
+# Datenmodelle
+
+Diese Seite dokumentiert die Dataclasses fĂŒr die strukturierte Datendarstellung.
+
+---
+
+## Company
+
+::: handelsregister.Company
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Felder
+
+| Feld | Typ | Beschreibung |
+|------|-----|--------------|
+| `name` | `str` | Firmenname |
+| `court` | `str` | Registergericht |
+| `register_num` | `str` | Registernummer (z.B. "HRB 12345") |
+| `register_type` | `str` | Registerart (HRA, HRB, etc.) |
+| `status` | `str` | Registrierungsstatus |
+| `state` | `str` | Bundesland-Code (z.B. "BE") |
+| `history` | `List[HistoryEntry]` | Historische EintrÀge |
+
+### Beispiel
+
+```python
+firma = Company(
+ name="Deutsche Bank AG",
+ court="Frankfurt am Main",
+ register_num="HRB 12345",
+ register_type="HRB",
+ status="aktuell eingetragen",
+ state="HE",
+ history=[]
+)
+```
+
+---
+
+## CompanyDetails
+
+::: handelsregister.CompanyDetails
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Felder
+
+| Feld | Typ | Beschreibung |
+|------|-----|--------------|
+| `name` | `str` | Firmenname |
+| `court` | `str` | Registergericht |
+| `register_number` | `str` | Registernummer |
+| `register_type` | `str` | Registerart |
+| `status` | `str` | Registrierungsstatus |
+| `capital` | `str \| None` | Stammkapital/Grundkapital |
+| `currency` | `str \| None` | WĂ€hrung (EUR) |
+| `address` | `Address \| None` | GeschÀftsadresse |
+| `representatives` | `List[Representative]` | GeschĂ€ftsfĂŒhrer, Vorstandsmitglieder |
+| `owners` | `List[Owner]` | Gesellschafter (Personengesellschaften) |
+| `business_purpose` | `str \| None` | Unternehmensgegenstand |
+| `history` | `List[HistoryEntry]` | VollstÀndige Historie |
+
+### Beispiel
+
+```python
+details = CompanyDetails(
+ name="GASAG AG",
+ court="Berlin (Charlottenburg)",
+ register_number="44343",
+ register_type="HRB",
+ status="aktuell eingetragen",
+ capital="306977800.00",
+ currency="EUR",
+ address=Address(
+ street="GASAG-Platz 1",
+ postal_code="10963",
+ city="Berlin",
+ country="Deutschland"
+ ),
+ representatives=[
+ Representative(name="Dr. Gerhard Holtmeier", role="Vorstandsvorsitzender")
+ ],
+ owners=[],
+ business_purpose="Versorgung mit Energie...",
+ history=[]
+)
+```
+
+---
+
+## Address
+
+::: handelsregister.Address
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Felder
+
+| Feld | Typ | Beschreibung |
+|------|-----|--------------|
+| `street` | `str \| None` | StraĂe und Hausnummer |
+| `postal_code` | `str \| None` | Postleitzahl |
+| `city` | `str \| None` | Stadt |
+| `country` | `str \| None` | Land |
+
+### Beispiel
+
+```python
+adresse = Address(
+ street="GASAG-Platz 1",
+ postal_code="10963",
+ city="Berlin",
+ country="Deutschland"
+)
+
+print(adresse)
+# GASAG-Platz 1
+# 10963 Berlin
+# Deutschland
+```
+
+---
+
+## Representative
+
+::: handelsregister.Representative
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Felder
+
+| Feld | Typ | Beschreibung |
+|------|-----|--------------|
+| `name` | `str` | VollstÀndiger Name |
+| `role` | `str \| None` | Rolle (GeschĂ€ftsfĂŒhrer, Vorstand, etc.) |
+| `birth_date` | `str \| None` | Geburtsdatum |
+| `location` | `str \| None` | Wohnort |
+
+### Beispiel
+
+```python
+vertreter = Representative(
+ name="Dr. Gerhard Holtmeier",
+ role="Vorstandsvorsitzender",
+ birth_date="1960-05-15",
+ location="Berlin"
+)
+```
+
+---
+
+## Owner
+
+::: handelsregister.Owner
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Felder
+
+| Feld | Typ | Beschreibung |
+|------|-----|--------------|
+| `name` | `str` | Gesellschaftername |
+| `owner_type` | `str \| None` | Typ (Kommanditist, KomplementÀr, etc.) |
+| `share` | `str \| None` | Einlagebetrag |
+| `liability` | `str \| None` | Haftungsbetrag |
+
+### Beispiel
+
+```python
+gesellschafter = Owner(
+ name="Max Mustermann",
+ owner_type="Kommanditist",
+ share="100000.00 EUR",
+ liability="100000.00 EUR"
+)
+```
+
+---
+
+## HistoryEntry
+
+::: handelsregister.HistoryEntry
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Felder
+
+| Feld | Typ | Beschreibung |
+|------|-----|--------------|
+| `date` | `str \| None` | Eintragungsdatum |
+| `entry_type` | `str \| None` | Art des Eintrags |
+| `content` | `str` | Eintragsinhalt |
+
+### Beispiel
+
+```python
+eintrag = HistoryEntry(
+ date="2024-01-15",
+ entry_type="Neueintragung",
+ content="Die Gesellschaft ist eingetragen..."
+)
+```
+
+---
+
+## SearchOptions
+
+::: handelsregister.SearchOptions
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Interne Verwendung
+
+Diese Dataclass wird intern verwendet, um Suchparameter zu ĂŒbergeben.
+
+```python
+optionen = SearchOptions(
+ keywords="Bank",
+ states=["BE", "HH"],
+ register_type="HRB",
+ include_deleted=False,
+ exact=False
+)
+```
+
diff --git a/docs/api/models.md b/docs/api/models.md
new file mode 100644
index 00000000..f500fd04
--- /dev/null
+++ b/docs/api/models.md
@@ -0,0 +1,245 @@
+# Data Models
+
+This page documents the dataclasses used for structured data representation.
+
+---
+
+## Company
+
+::: handelsregister.Company
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `name` | `str` | Company name |
+| `court` | `str` | Register court |
+| `register_num` | `str` | Register number (e.g., "HRB 12345 B") |
+| `state` | `str` | State name (e.g., "Berlin") |
+| `status` | `str` | Registration status |
+| `status_normalized` | `str` | Normalized status (e.g., "CURRENTLY_REGISTERED") |
+| `documents` | `str` | Available document types |
+| `history` | `List[HistoryEntry]` | Historical entries |
+
+### Example
+
+```python
+company = Company(
+ name="Deutsche Bank AG",
+ court="Frankfurt am Main",
+ register_num="HRB 12345",
+ state="Hessen",
+ status="currently registered",
+ status_normalized="CURRENTLY_REGISTERED",
+ documents="ADCDHDDKUTVĂSI",
+ history=[]
+)
+```
+
+---
+
+## CompanyDetails
+
+::: handelsregister.CompanyDetails
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `name` | `str` | Company name |
+| `register_num` | `str` | Register number (e.g., "HRB 44343 B") |
+| `court` | `str` | Register court |
+| `state` | `str` | State name |
+| `status` | `str` | Registration status |
+| `legal_form` | `str \| None` | Legal form (e.g., "Aktiengesellschaft") |
+| `capital` | `str \| None` | Share capital |
+| `currency` | `str \| None` | Currency (EUR) |
+| `address` | `Address \| None` | Business address |
+| `purpose` | `str \| None` | Business purpose |
+| `representatives` | `List[Representative]` | Directors, board members |
+| `owners` | `List[Owner]` | Shareholders (partnerships) |
+| `registration_date` | `str \| None` | Registration date |
+| `last_update` | `str \| None` | Last update date |
+| `deletion_date` | `str \| None` | Deletion date (if deleted) |
+
+### Example
+
+```python
+details = CompanyDetails(
+ name="GASAG AG",
+ register_num="HRB 44343 B",
+ court="Berlin (Charlottenburg)",
+ state="Berlin",
+ status="currently registered",
+ capital="306977800.00",
+ currency="EUR",
+ address=Address(
+ street="GASAG-Platz 1",
+ postal_code="10963",
+ city="Berlin",
+ country="Deutschland"
+ ),
+ representatives=[
+ Representative(name="Dr. Gerhard Holtmeier", role="Vorstandsvorsitzender")
+ ],
+ owners=[],
+ business_purpose="Versorgung mit Energie...",
+ history=[]
+)
+```
+
+---
+
+## Address
+
+::: handelsregister.Address
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `street` | `str \| None` | Street and house number |
+| `postal_code` | `str \| None` | Postal code |
+| `city` | `str \| None` | City |
+| `country` | `str \| None` | Country |
+
+### Example
+
+```python
+address = Address(
+ street="GASAG-Platz 1",
+ postal_code="10963",
+ city="Berlin",
+ country="Deutschland"
+)
+
+print(address)
+# GASAG-Platz 1
+# 10963 Berlin
+# Deutschland
+```
+
+---
+
+## Representative
+
+::: handelsregister.Representative
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `name` | `str` | Full name |
+| `role` | `str \| None` | Role (GeschĂ€ftsfĂŒhrer, Vorstand, etc.) |
+| `birth_date` | `str \| None` | Date of birth |
+| `location` | `str \| None` | Place of residence |
+
+### Example
+
+```python
+rep = Representative(
+ name="Dr. Gerhard Holtmeier",
+ role="Vorstandsvorsitzender",
+ birth_date="1960-05-15",
+ location="Berlin"
+)
+```
+
+---
+
+## Owner
+
+::: handelsregister.Owner
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `name` | `str` | Owner name |
+| `owner_type` | `str \| None` | Type (Kommanditist, KomplementÀr, etc.) |
+| `share` | `str \| None` | Share amount |
+| `liability` | `str \| None` | Liability amount |
+
+### Example
+
+```python
+owner = Owner(
+ name="Max Mustermann",
+ owner_type="Kommanditist",
+ share="100000.00 EUR",
+ liability="100000.00 EUR"
+)
+```
+
+---
+
+## HistoryEntry
+
+::: handelsregister.HistoryEntry
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Fields
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `date` | `str \| None` | Entry date |
+| `entry_type` | `str \| None` | Type of entry |
+| `content` | `str` | Entry content |
+
+### Example
+
+```python
+entry = HistoryEntry(
+ date="2024-01-15",
+ entry_type="Neueintragung",
+ content="Die Gesellschaft ist eingetragen..."
+)
+```
+
+---
+
+## SearchOptions
+
+::: handelsregister.SearchOptions
+ options:
+ show_root_heading: true
+ show_source: false
+ heading_level: 3
+
+### Internal Use
+
+This dataclass is used internally to pass search parameters.
+
+```python
+options = SearchOptions(
+ keywords="Bank",
+ keyword_option="all",
+ states=["BE", "HH"],
+ register_type="HRB",
+ include_deleted=False
+)
+```
+
diff --git a/docs/changelog.de.md b/docs/changelog.de.md
new file mode 100644
index 00000000..7b95e791
--- /dev/null
+++ b/docs/changelog.de.md
@@ -0,0 +1,90 @@
+# Changelog
+
+Alle wichtigen Ănderungen an diesem Projekt werden in dieser Datei dokumentiert.
+
+Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.0.0/),
+und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### HinzugefĂŒgt
+- Mehrsprachige Dokumentation (Englisch und Deutsch)
+- MkDocs Material Theme mit verbesserter Navigation
+- API-Referenz-Dokumentation mit mkdocstrings
+- Umfangreicher Beispielabschnitt
+- Referenztabellen fĂŒr BundeslĂ€nder-Codes, Registerarten und Rechtsformen
+
+### GeÀndert
+- Dokumentation fĂŒr bessere Organisation umstrukturiert
+- Verbesserte Code-Beispiele mit mehr Kontext
+
+---
+
+## [1.0.0] - 2024-01-15
+
+### HinzugefĂŒgt
+- Erstveröffentlichung
+- `search()`-Funktion fĂŒr Unternehmenssuche
+- `get_details()`-Funktion fĂŒr detaillierte Unternehmensinformationen
+- `HandelsRegister`-Klasse fĂŒr Low-Level-Zugriff
+- `SearchCache`-Klasse fĂŒr Ergebnis-Caching
+- Kommandozeilen-Interface (CLI)
+- Datenmodelle: `Company`, `CompanyDetails`, `Address`, `Representative`, `Owner`
+- Fehlerbehandlung: `SearchError`, `RateLimitError`, `ConnectionError`, `ParseError`
+- JSON-Ausgabeformat fĂŒr CLI
+- UnterstĂŒtzung fĂŒr Bundesland-Filterung
+- UnterstĂŒtzung fĂŒr Registerart-Filterung
+
+### Dokumentation
+- README mit Verwendungsbeispielen
+- Installationsanleitung
+- API-Dokumentation
+
+---
+
+## Versionshistorie
+
+| Version | Datum | Beschreibung |
+|---------|-------|--------------|
+| 1.0.0 | 2024-01-15 | Erstveröffentlichung |
+| 0.9.0 | 2023-12-01 | Beta-Release |
+| 0.1.0 | 2023-06-01 | Alpha-Release |
+
+---
+
+## Migrationsanleitungen
+
+### Migration von 0.x auf 1.0
+
+Keine Breaking Changes. Das 1.0-Release markiert API-StabilitÀt.
+
+---
+
+## Roadmap
+
+### Geplante Features
+
+- [ ] Async-UnterstĂŒtzung (`async/await`)
+- [ ] Detaillierteres Historie-Parsing
+- [ ] Dokumentenabruf
+- [ ] Webhook-Benachrichtigungen fĂŒr UnternehmensĂ€nderungen
+- [ ] Datenbank-Export-FunktionalitÀt
+
+### In Ăberlegung
+
+- GraphQL-API-Wrapper
+- UnternehmensĂŒberwachungsdienst
+- Tools fĂŒr historische Datenanalyse
+
+---
+
+## Beitragen
+
+Siehe [Beitragsrichtlinien](https://github.com/bundesAPI/handelsregister/blob/main/CONTRIBUTING.md) fĂŒr Informationen zum Beitragen zu diesem Projekt.
+
+### Probleme melden
+
+- Verwenden Sie [GitHub Issues](https://github.com/bundesAPI/handelsregister/issues) fĂŒr Fehlermeldungen
+- Geben Sie Python-Version, Betriebssystem und Schritte zur Reproduktion an
+- PrĂŒfen Sie bestehende Issues, bevor Sie neue erstellen
+
diff --git a/docs/changelog.md b/docs/changelog.md
new file mode 100644
index 00000000..f97fd026
--- /dev/null
+++ b/docs/changelog.md
@@ -0,0 +1,90 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+- Multilingual documentation (English and German)
+- MkDocs Material theme with improved navigation
+- API reference documentation with mkdocstrings
+- Comprehensive examples section
+- Reference tables for state codes, register types, and legal forms
+
+### Changed
+- Documentation restructured for better organization
+- Improved code examples with more context
+
+---
+
+## [1.0.0] - 2024-01-15
+
+### Added
+- Initial release
+- `search()` function for company searches
+- `get_details()` function for detailed company information
+- `HandelsRegister` class for low-level access
+- `SearchCache` class for result caching
+- Command-line interface (CLI)
+- Data models: `Company`, `CompanyDetails`, `Address`, `Representative`, `Owner`
+- Exception handling: `SearchError`, `RateLimitError`, `ConnectionError`, `ParseError`
+- JSON output format for CLI
+- State filtering support
+- Register type filtering support
+
+### Documentation
+- README with usage examples
+- Installation instructions
+- API documentation
+
+---
+
+## Version History
+
+| Version | Date | Description |
+|---------|------|-------------|
+| 1.0.0 | 2024-01-15 | Initial release |
+| 0.9.0 | 2023-12-01 | Beta release |
+| 0.1.0 | 2023-06-01 | Alpha release |
+
+---
+
+## Migration Guides
+
+### Migrating from 0.x to 1.0
+
+No breaking changes. The 1.0 release marks API stability.
+
+---
+
+## Roadmap
+
+### Planned Features
+
+- [ ] Async support (`async/await`)
+- [ ] More detailed history parsing
+- [ ] Document retrieval
+- [ ] Webhook notifications for company changes
+- [ ] Database export functionality
+
+### Under Consideration
+
+- GraphQL API wrapper
+- Company monitoring service
+- Historical data analysis tools
+
+---
+
+## Contributing
+
+See [Contributing Guidelines](https://github.com/bundesAPI/handelsregister/blob/main/CONTRIBUTING.md) for how to contribute to this project.
+
+### Reporting Issues
+
+- Use [GitHub Issues](https://github.com/bundesAPI/handelsregister/issues) for bug reports
+- Include Python version, OS, and steps to reproduce
+- Check existing issues before creating new ones
+
diff --git a/docs/examples/advanced.de.md b/docs/examples/advanced.de.md
new file mode 100644
index 00000000..633bba85
--- /dev/null
+++ b/docs/examples/advanced.de.md
@@ -0,0 +1,315 @@
+# Fortgeschrittene Beispiele
+
+Komplexere Beispiele fĂŒr fortgeschrittene AnwendungsfĂ€lle.
+
+## Stapelverarbeitung
+
+### Mehrere Suchbegriffe verarbeiten
+
+```python
+import time
+from handelsregister import search
+
+suchbegriffe = ["Bank", "Versicherung", "Immobilien", "Consulting"]
+
+alle_ergebnisse = {}
+for suchbegriff in suchbegriffe:
+ print(f"Suche: {suchbegriff}")
+ ergebnisse = search(suchbegriff, states=["BE"])
+ alle_ergebnisse[suchbegriff] = ergebnisse
+
+ # Rate-Limit beachten
+ time.sleep(60)
+
+# Zusammenfassung
+for suchbegriff, ergebnisse in alle_ergebnisse.items():
+ print(f"{suchbegriff}: {len(ergebnisse)} Unternehmen")
+```
+
+### Alle BundeslÀnder verarbeiten
+
+```python
+import time
+from handelsregister import search
+
+BUNDESLAENDER = ["BW", "BY", "BE", "BB", "HB", "HH", "HE", "MV",
+ "NI", "NW", "RP", "SL", "SN", "ST", "SH", "TH"]
+
+ergebnisse_pro_land = {}
+for land in BUNDESLAENDER:
+ print(f"Verarbeite {land}...")
+ ergebnisse = search("Bank", states=[land], register_type="HRB")
+ ergebnisse_pro_land[land] = len(ergebnisse)
+ time.sleep(60)
+
+# Nach Anzahl sortieren
+sortierte_laender = sorted(ergebnisse_pro_land.items(), key=lambda x: x[1], reverse=True)
+for land, anzahl in sortierte_laender:
+ print(f"{land}: {anzahl} Banken")
+```
+
+---
+
+## Datenanalyse
+
+### Mit pandas
+
+```python
+import pandas as pd
+from handelsregister import search
+
+# Suchen und in DataFrame konvertieren
+firmen = search("Bank", states=["BE", "HH"])
+df = pd.DataFrame(firmen)
+
+# Analyse
+print("Unternehmen nach Gericht:")
+print(df['court'].value_counts())
+
+print("\nUnternehmen nach Registerart:")
+print(df['register_type'].value_counts())
+
+print("\nUnternehmen nach Status:")
+print(df['status'].value_counts())
+```
+
+### Export nach CSV
+
+```python
+import pandas as pd
+from handelsregister import search, get_details
+
+firmen = search("Bank", states=["BE"])
+
+# Details fĂŒr jedes Unternehmen abrufen
+daten = []
+for firma in firmen[:10]: # Limit fĂŒr Demo
+ details = get_details(firma)
+ daten.append({
+ 'name': details.name,
+ 'gericht': details.court,
+ 'nummer': details.register_number,
+ 'kapital': details.capital,
+ 'stadt': details.address.city if details.address else None,
+ })
+
+df = pd.DataFrame(daten)
+df.to_csv('berliner_banken.csv', index=False)
+```
+
+---
+
+## Parallele Verarbeitung
+
+### Mit ThreadPoolExecutor
+
+```python
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from handelsregister import search
+import time
+
+suchbegriffe = ["Bank", "Versicherung", "Immobilien", "IT", "Consulting"]
+
+def suche_suchbegriff(suchbegriff):
+ """Suche mit Rate-Limit-Verzögerung."""
+ time.sleep(60) # Rate-Limit
+ return suchbegriff, search(suchbegriff, states=["BE"])
+
+# Parallel verarbeiten mit Rate-Limiting
+with ThreadPoolExecutor(max_workers=1) as executor:
+ futures = {executor.submit(suche_suchbegriff, sb): sb for sb in suchbegriffe}
+
+ for future in as_completed(futures):
+ suchbegriff, ergebnisse = future.result()
+ print(f"{suchbegriff}: {len(ergebnisse)} Unternehmen")
+```
+
+---
+
+## Benutzerdefiniertes Caching
+
+### Benutzerdefinierte TTL
+
+```python
+from handelsregister import HandelsRegister, SearchCache
+
+# Cache nur fĂŒr 1 Stunde
+cache = SearchCache(ttl_hours=1)
+hr = HandelsRegister(cache=cache)
+
+# Benutzerdefinierte Instanz verwenden
+firmen = hr.search("Bank")
+```
+
+### Benutzerdefiniertes Cache-Verzeichnis
+
+```python
+from handelsregister import SearchCache, HandelsRegister
+
+# Benutzerdefiniertes Verzeichnis verwenden
+cache = SearchCache(cache_dir="/tmp/hr-cache")
+hr = HandelsRegister(cache=cache)
+
+firmen = hr.search("Bank")
+```
+
+### Cache-Statistiken
+
+```python
+from handelsregister import SearchCache
+
+cache = SearchCache()
+
+# Statistiken abrufen
+stats = cache.get_stats()
+print(f"EintrÀge gesamt: {stats['total']}")
+print(f"GĂŒltige EintrĂ€ge: {stats['valid']}")
+print(f"Abgelaufene EintrÀge: {stats['expired']}")
+print(f"Cache-GröĂe: {stats['size_mb']:.2f} MB")
+
+# Abgelaufene EintrÀge bereinigen
+entfernt = cache.cleanup_expired()
+print(f"{entfernt} abgelaufene EintrÀge entfernt")
+```
+
+---
+
+## Berichte erstellen
+
+### Unternehmensbericht
+
+```python
+from handelsregister import search, get_details
+
+def erstelle_bericht(firmenname: str) -> str:
+ """Erstellt einen detaillierten Unternehmensbericht."""
+ firmen = search(firmenname, keyword_option="exact")
+
+ if not firmen:
+ return f"Unternehmen nicht gefunden: {firmenname}"
+
+ details = get_details(firmen[0])
+
+ bericht = []
+ bericht.append("=" * 60)
+ bericht.append(f" {details.name}")
+ bericht.append("=" * 60)
+ bericht.append("")
+
+ bericht.append("REGISTRIERUNG")
+ bericht.append(f" Gericht: {details.court}")
+ bericht.append(f" Nummer: {details.register_type} {details.register_number}")
+ bericht.append(f" Status: {details.status}")
+ bericht.append("")
+
+ if details.capital:
+ bericht.append("KAPITAL")
+ bericht.append(f" {details.capital} {details.currency}")
+ bericht.append("")
+
+ if details.address:
+ bericht.append("ADRESSE")
+ bericht.append(f" {details.address.street}")
+ bericht.append(f" {details.address.postal_code} {details.address.city}")
+ bericht.append("")
+
+ if details.representatives:
+ bericht.append("VERTRETER")
+ for v in details.representatives:
+ bericht.append(f" - {v.name}")
+ if v.role:
+ bericht.append(f" Rolle: {v.role}")
+ bericht.append("")
+
+ if details.business_purpose:
+ bericht.append("UNTERNEHMENSGEGENSTAND")
+ zweck = details.business_purpose
+ if len(zweck) > 200:
+ zweck = zweck[:200] + "..."
+ bericht.append(f" {zweck}")
+
+ return "\n".join(bericht)
+
+# Verwendung
+print(erstelle_bericht("GASAG AG"))
+```
+
+---
+
+## Rate-Limit-Behandlung
+
+### Automatische Wiederholung
+
+```python
+import time
+from handelsregister import search, RateLimitError
+
+def suche_mit_wiederholung(keywords, max_versuche=3, **kwargs):
+ """Suche mit automatischer Wiederholung bei Rate-Limit."""
+ for versuch in range(max_versuche):
+ try:
+ return search(keywords, **kwargs)
+ except RateLimitError:
+ if versuch == max_versuche - 1:
+ raise
+ wartezeit = 60 * (versuch + 1)
+ print(f"Rate-limitiert, warte {wartezeit}s...")
+ time.sleep(wartezeit)
+
+# Verwendung
+firmen = suche_mit_wiederholung("Bank", states=["BE"])
+```
+
+### Rate-Limiter-Klasse
+
+```python
+import time
+from collections import deque
+from handelsregister import search
+
+class RateLimiter:
+ """Erzwingt Rate-Limiting fĂŒr API-Aufrufe."""
+
+ def __init__(self, max_anfragen: int = 60, fenster_sekunden: int = 3600):
+ self.max_anfragen = max_anfragen
+ self.fenster_sekunden = fenster_sekunden
+ self.anfragen = deque()
+
+ def warte_falls_noetig(self):
+ """Wartet, wenn Rate-Limit ĂŒberschritten wĂŒrde."""
+ jetzt = time.time()
+
+ # Alte Anfragen entfernen
+ while self.anfragen and self.anfragen[0] < jetzt - self.fenster_sekunden:
+ self.anfragen.popleft()
+
+ if len(self.anfragen) >= self.max_anfragen:
+ wartezeit = self.anfragen[0] + self.fenster_sekunden - jetzt
+ if wartezeit > 0:
+ print(f"Rate-Limit erreicht, warte {wartezeit:.0f}s...")
+ time.sleep(wartezeit)
+
+ self.anfragen.append(jetzt)
+
+ def suche(self, *args, **kwargs):
+ """Suche mit Rate-Limiting."""
+ self.warte_falls_noetig()
+ return search(*args, **kwargs)
+
+# Verwendung
+limiter = RateLimiter()
+
+suchbegriffe = ["Bank", "Versicherung", "Consulting"]
+for suchbegriff in suchbegriffe:
+ ergebnisse = limiter.suche(suchbegriff)
+ print(f"{suchbegriff}: {len(ergebnisse)} Ergebnisse")
+```
+
+---
+
+## Siehe auch
+
+- [Einfache Beispiele](simple.md) â Grundlegende Beispiele
+- [Integrationsbeispiele](integrations.md) â Framework-Integrationen
+- [API-Referenz](../api/index.md) â Technische Dokumentation
+
diff --git a/docs/examples/advanced.md b/docs/examples/advanced.md
new file mode 100644
index 00000000..8b838659
--- /dev/null
+++ b/docs/examples/advanced.md
@@ -0,0 +1,316 @@
+# Advanced Examples
+
+More complex examples for advanced use cases.
+
+## Batch Processing
+
+### Process Multiple Keywords
+
+```python
+import time
+from handelsregister import search
+
+keywords = ["Bank", "Versicherung", "Immobilien", "Consulting"]
+
+all_results = {}
+for keyword in keywords:
+ print(f"Searching: {keyword}")
+ results = search(keyword, states=["BE"])
+ all_results[keyword] = results
+
+ # Respect rate limit
+ time.sleep(60)
+
+# Summary
+for keyword, results in all_results.items():
+ print(f"{keyword}: {len(results)} companies")
+```
+
+### Process All States
+
+```python
+import time
+from handelsregister import search
+
+STATES = ["BW", "BY", "BE", "BB", "HB", "HH", "HE", "MV",
+ "NI", "NW", "RP", "SL", "SN", "ST", "SH", "TH"]
+
+results_by_state = {}
+for state in STATES:
+ print(f"Processing {state}...")
+ results = search("Bank", states=[state], register_type="HRB")
+ results_by_state[state] = len(results)
+ time.sleep(60)
+
+# Sort by count
+sorted_states = sorted(results_by_state.items(), key=lambda x: x[1], reverse=True)
+for state, count in sorted_states:
+ print(f"{state}: {count} banks")
+```
+
+---
+
+## Data Analysis
+
+### Using pandas
+
+```python
+import pandas as pd
+from handelsregister import search
+
+# Search and convert to DataFrame
+companies = search("Bank", states=["BE", "HH"])
+# Convert Company objects to dicts for pandas
+df = pd.DataFrame([c.to_dict() for c in companies])
+
+# Analysis
+print("Companies by court:")
+print(df['court'].value_counts())
+
+print("\nCompanies by register type:")
+print(df['register_type'].value_counts())
+
+print("\nCompanies by status:")
+print(df['status'].value_counts())
+```
+
+### Export to CSV
+
+```python
+import pandas as pd
+from handelsregister import search, get_details
+
+companies = search("Bank", states=["BE"])
+
+# Get details for each
+data = []
+for company in companies[:10]: # Limit for demo
+ details = get_details(company)
+ data.append({
+ 'name': details.name,
+ 'court': details.court,
+ 'number': details.register_number,
+ 'capital': details.capital,
+ 'city': details.address.city if details.address else None,
+ })
+
+df = pd.DataFrame(data)
+df.to_csv('berlin_banks.csv', index=False)
+```
+
+---
+
+## Parallel Processing
+
+### Using ThreadPoolExecutor
+
+```python
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from handelsregister import search
+import time
+
+keywords = ["Bank", "Versicherung", "Immobilien", "IT", "Consulting"]
+
+def search_keyword(keyword):
+ """Search with rate limit delay."""
+ time.sleep(60) # Rate limit
+ return keyword, search(keyword, states=["BE"])
+
+# Process in parallel with rate limiting
+with ThreadPoolExecutor(max_workers=1) as executor:
+ futures = {executor.submit(search_keyword, kw): kw for kw in keywords}
+
+ for future in as_completed(futures):
+ keyword, results = future.result()
+ print(f"{keyword}: {len(results)} companies")
+```
+
+---
+
+## Custom Caching
+
+### Custom TTL
+
+```python
+from handelsregister import HandelsRegister, SearchCache
+
+# Cache for 1 hour only
+cache = SearchCache(ttl_hours=1)
+hr = HandelsRegister(cache=cache)
+
+# Use custom instance
+companies = hr.search("Bank")
+```
+
+### Custom Cache Directory
+
+```python
+from handelsregister import SearchCache, HandelsRegister
+
+# Use custom directory
+cache = SearchCache(cache_dir="/tmp/hr-cache")
+hr = HandelsRegister(cache=cache)
+
+companies = hr.search("Bank")
+```
+
+### Cache Statistics
+
+```python
+from handelsregister import SearchCache
+
+cache = SearchCache()
+
+# Get statistics
+stats = cache.get_stats()
+print(f"Total entries: {stats['total']}")
+print(f"Valid entries: {stats['valid']}")
+print(f"Expired entries: {stats['expired']}")
+print(f"Cache size: {stats['size_mb']:.2f} MB")
+
+# Cleanup expired entries
+removed = cache.cleanup_expired()
+print(f"Removed {removed} expired entries")
+```
+
+---
+
+## Building Reports
+
+### Company Report
+
+```python
+from handelsregister import search, get_details
+
+def generate_report(company_name: str) -> str:
+ """Generate a detailed company report."""
+ companies = search(company_name, keyword_option="exact")
+
+ if not companies:
+ return f"Company not found: {company_name}"
+
+ details = get_details(companies[0])
+
+ report = []
+ report.append("=" * 60)
+ report.append(f" {details.name}")
+ report.append("=" * 60)
+ report.append("")
+
+ report.append("REGISTRATION")
+ report.append(f" Court: {details.court}")
+ report.append(f" Number: {details.register_type} {details.register_number}")
+ report.append(f" Status: {details.status}")
+ report.append("")
+
+ if details.capital:
+ report.append("CAPITAL")
+ report.append(f" {details.capital} {details.currency}")
+ report.append("")
+
+ if details.address:
+ report.append("ADDRESS")
+ report.append(f" {details.address.street}")
+ report.append(f" {details.address.postal_code} {details.address.city}")
+ report.append("")
+
+ if details.representatives:
+ report.append("REPRESENTATIVES")
+ for rep in details.representatives:
+ report.append(f" - {rep.name}")
+ if rep.role:
+ report.append(f" Role: {rep.role}")
+ report.append("")
+
+ if details.business_purpose:
+ report.append("BUSINESS PURPOSE")
+ purpose = details.business_purpose
+ if len(purpose) > 200:
+ purpose = purpose[:200] + "..."
+ report.append(f" {purpose}")
+
+ return "\n".join(report)
+
+# Usage
+print(generate_report("GASAG AG"))
+```
+
+---
+
+## Rate Limit Handling
+
+### Automatic Retry
+
+```python
+import time
+from handelsregister import search, RateLimitError
+
+def search_with_retry(keywords, max_retries=3, **kwargs):
+ """Search with automatic retry on rate limit."""
+ for attempt in range(max_retries):
+ try:
+ return search(keywords, **kwargs)
+ except RateLimitError:
+ if attempt == max_retries - 1:
+ raise
+ wait_time = 60 * (attempt + 1)
+ print(f"Rate limited, waiting {wait_time}s...")
+ time.sleep(wait_time)
+
+# Usage
+companies = search_with_retry("Bank", states=["BE"])
+```
+
+### Rate Limiter Class
+
+```python
+import time
+from collections import deque
+from handelsregister import search
+
+class RateLimiter:
+ """Enforce rate limiting for API calls."""
+
+ def __init__(self, max_requests: int = 60, window_seconds: int = 3600):
+ self.max_requests = max_requests
+ self.window_seconds = window_seconds
+ self.requests = deque()
+
+ def wait_if_needed(self):
+ """Wait if rate limit would be exceeded."""
+ now = time.time()
+
+ # Remove old requests
+ while self.requests and self.requests[0] < now - self.window_seconds:
+ self.requests.popleft()
+
+ if len(self.requests) >= self.max_requests:
+ wait_time = self.requests[0] + self.window_seconds - now
+ if wait_time > 0:
+ print(f"Rate limit reached, waiting {wait_time:.0f}s...")
+ time.sleep(wait_time)
+
+ self.requests.append(now)
+
+ def search(self, *args, **kwargs):
+ """Search with rate limiting."""
+ self.wait_if_needed()
+ return search(*args, **kwargs)
+
+# Usage
+limiter = RateLimiter()
+
+keywords = ["Bank", "Insurance", "Consulting"]
+for keyword in keywords:
+ results = limiter.search(keyword)
+ print(f"{keyword}: {len(results)} results")
+```
+
+---
+
+## See Also
+
+- [Simple Examples](simple.md) â Basic examples
+- [Integration Examples](integrations.md) â Framework integrations
+- [API Reference](../api/index.md) â Technical documentation
+
diff --git a/docs/examples/integrations.de.md b/docs/examples/integrations.de.md
new file mode 100644
index 00000000..ff5a889b
--- /dev/null
+++ b/docs/examples/integrations.de.md
@@ -0,0 +1,382 @@
+# Integrationsbeispiele
+
+Beispiele fĂŒr die Integration von Handelsregister mit beliebten Frameworks und Tools.
+
+## FastAPI
+
+### Einfacher API-Endpunkt
+
+```python
+from fastapi import FastAPI, HTTPException
+from handelsregister import search, get_details, SearchError
+
+app = FastAPI(title="Unternehmenssuche API")
+
+@app.get("/search")
+async def suche_unternehmen(
+ q: str,
+ bundesland: str = None,
+ limit: int = 10
+):
+ """Sucht nach Unternehmen."""
+ try:
+ states = [bundesland] if bundesland else None
+ firmen = search(q, states=states)
+ return {
+ "abfrage": q,
+ "anzahl": len(firmen),
+ "ergebnisse": firmen[:limit]
+ }
+ except SearchError as e:
+ raise HTTPException(status_code=500, detail=str(e))
+
+@app.get("/unternehmen/{gericht}/{nummer}")
+async def hole_unternehmen(gericht: str, nummer: str):
+ """Ruft Unternehmensdetails nach Register ab."""
+ try:
+ firmen = search(
+ "",
+ court=gericht,
+ register_number=nummer
+ )
+ if not firmen:
+ raise HTTPException(status_code=404, detail="Unternehmen nicht gefunden")
+
+ details = get_details(firmen[0])
+ return {
+ "name": details.name,
+ "kapital": details.capital,
+ "adresse": {
+ "strasse": details.address.street if details.address else None,
+ "stadt": details.address.city if details.address else None,
+ },
+ "vertreter": [
+ {"name": v.name, "rolle": v.role}
+ for v in details.representatives
+ ]
+ }
+ except SearchError as e:
+ raise HTTPException(status_code=500, detail=str(e))
+```
+
+---
+
+## Flask
+
+### Einfache Flask-App
+
+```python
+from flask import Flask, request, jsonify
+from handelsregister import search, get_details, SearchError
+
+app = Flask(__name__)
+
+@app.route('/suche')
+def suche_unternehmen():
+ """Such-Endpunkt."""
+ abfrage = request.args.get('q', '')
+ bundesland = request.args.get('bundesland')
+
+ if not abfrage:
+ return jsonify({"fehler": "Abfrage erforderlich"}), 400
+
+ try:
+ states = [bundesland] if bundesland else None
+ firmen = search(abfrage, states=states)
+ return jsonify({
+ "anzahl": len(firmen),
+ "ergebnisse": firmen
+ })
+ except SearchError as e:
+ return jsonify({"fehler": str(e)}), 500
+
+@app.route('/unternehmen/')
+def unternehmens_details(name):
+ """Unternehmensdetails abrufen."""
+ try:
+ firmen = search(name, keyword_option="exact")
+ if not firmen:
+ return jsonify({"fehler": "Nicht gefunden"}), 404
+
+ details = get_details(firmen[0])
+ return jsonify({
+ "name": details.name,
+ "kapital": details.capital,
+ "waehrung": details.currency
+ })
+ except SearchError as e:
+ return jsonify({"fehler": str(e)}), 500
+
+if __name__ == '__main__':
+ app.run(debug=True)
+```
+
+---
+
+## Django
+
+### Django Management Command
+
+```python
+# myapp/management/commands/suche_unternehmen.py
+from django.core.management.base import BaseCommand
+from handelsregister import search
+
+class Command(BaseCommand):
+ help = 'Sucht nach Unternehmen im Handelsregister'
+
+ def add_arguments(self, parser):
+ parser.add_argument('abfrage', type=str)
+ parser.add_argument('--bundesland', type=str, default=None)
+ parser.add_argument('--limit', type=int, default=10)
+
+ def handle(self, *args, **options):
+ abfrage = options['abfrage']
+ states = [options['bundesland']] if options['bundesland'] else None
+ limit = options['limit']
+
+ firmen = search(abfrage, states=states)
+
+ self.stdout.write(f"{len(firmen)} Unternehmen gefunden\n")
+ for firma in firmen[:limit]:
+ self.stdout.write(f" - {firma.name}")
+```
+
+### Django Model Integration
+
+```python
+# models.py
+from django.db import models
+from handelsregister import search, get_details
+
+class Unternehmen(models.Model):
+ name = models.CharField(max_length=255)
+ registergericht = models.CharField(max_length=100)
+ registernummer = models.CharField(max_length=50)
+ kapital = models.DecimalField(max_digits=15, decimal_places=2, null=True)
+ aktualisiert = models.DateTimeField(auto_now=True)
+
+ @classmethod
+ def erstelle_aus_register(cls, firmenname):
+ """Erstellt Unternehmen aus Registerdaten."""
+ firmen = search(firmenname, keyword_option="exact")
+ if not firmen:
+ raise ValueError(f"Unternehmen nicht gefunden: {firmenname}")
+
+ details = get_details(firmen[0])
+
+ return cls.objects.create(
+ name=details.name,
+ registergericht=details.court,
+ registernummer=details.register_number,
+ kapital=float(details.capital) if details.capital else None
+ )
+
+ def aktualisiere_aus_register(self):
+ """Aktualisiert Unternehmensdaten aus Register."""
+ firmen = search(self.name, keyword_option="exact")
+ if firmen:
+ details = get_details(firmen[0])
+ self.kapital = float(details.capital) if details.capital else None
+ self.save()
+```
+
+---
+
+## Celery
+
+### Hintergrund-Tasks
+
+```python
+# tasks.py
+from celery import Celery
+from handelsregister import search, get_details
+import time
+
+app = Celery('tasks', broker='redis://localhost:6379/0')
+
+@app.task(bind=True, max_retries=3)
+def suche_unternehmen_task(self, abfrage, states=None):
+ """Sucht Unternehmen im Hintergrund."""
+ try:
+ return search(abfrage, states=states)
+ except Exception as e:
+ self.retry(countdown=60)
+
+@app.task
+def stapel_suche_task(suchbegriffe):
+ """Sucht mehrere Suchbegriffe mit Rate-Limiting."""
+ ergebnisse = {}
+ for suchbegriff in suchbegriffe:
+ ergebnisse[suchbegriff] = search(suchbegriff)
+ time.sleep(60) # Rate-Limit
+ return ergebnisse
+
+# Verwendung
+result = suche_unternehmen_task.delay("Bank", states=["BE"])
+firmen = result.get(timeout=30)
+```
+
+---
+
+## SQLAlchemy
+
+### Ergebnisse in Datenbank speichern
+
+```python
+from sqlalchemy import create_engine, Column, String, Float, DateTime
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+from datetime import datetime
+from handelsregister import search, get_details
+
+Base = declarative_base()
+engine = create_engine('sqlite:///unternehmen.db')
+Session = sessionmaker(bind=engine)
+
+class Unternehmen(Base):
+ __tablename__ = 'unternehmen'
+
+ id = Column(String, primary_key=True)
+ name = Column(String)
+ registergericht = Column(String)
+ registernummer = Column(String)
+ kapital = Column(Float)
+ aktualisiert = Column(DateTime, default=datetime.utcnow)
+
+Base.metadata.create_all(engine)
+
+def speichere_unternehmen(firmenname):
+ """Sucht und speichert Unternehmen in Datenbank."""
+ session = Session()
+
+ firmen = search(firmenname, keyword_option="exact")
+ if not firmen:
+ return None
+
+ details = get_details(firmen[0])
+
+ unternehmen = Unternehmen(
+ id=f"{details.court}_{details.register_number}",
+ name=details.name,
+ registergericht=details.court,
+ registernummer=details.register_number,
+ kapital=float(details.capital) if details.capital else None
+ )
+
+ session.merge(unternehmen)
+ session.commit()
+
+ return unternehmen
+```
+
+---
+
+## Jupyter Notebook
+
+### Interaktive Analyse
+
+```python
+# Zelle 1: Setup
+from handelsregister import search, get_details
+import pandas as pd
+import matplotlib.pyplot as plt
+
+# Zelle 2: Suchen und erkunden
+firmen = search("Bank", states=["BE", "HH", "BY"])
+# Company-Objekte in Dicts fĂŒr pandas konvertieren
+df = pd.DataFrame([f.to_dict() for f in firmen])
+df.head()
+
+# Zelle 3: Nach Bundesland visualisieren
+df['state'].value_counts().plot(kind='bar')
+plt.title('Banken nach Bundesland')
+plt.xlabel('Bundesland')
+plt.ylabel('Anzahl')
+plt.show()
+
+# Zelle 4: Details fĂŒr Top-Unternehmen abrufen
+# Company-Objekte direkt verwenden (nicht DataFrame-Zeilen)
+for firma in firmen[:3]:
+ details = get_details(firma)
+ print(f"{details.name}: {details.capital} {details.currency}")
+```
+
+---
+
+## CLI-Skripte
+
+### Bash-Integration
+
+```bash
+#!/bin/bash
+# suche_und_benachrichtige.sh
+
+# Nach neuen Unternehmen suchen
+ergebnisse=$(handelsregister -s "Startup" --states BE --json)
+
+# Ergebnisse zÀhlen
+anzahl=$(echo "$ergebnisse" | jq 'length')
+
+if [ "$anzahl" -gt 0 ]; then
+ echo "$anzahl neue Startups in Berlin gefunden"
+
+ # Mit Datum speichern
+ echo "$ergebnisse" > "startups_$(date +%Y%m%d).json"
+
+ # Optional: Benachrichtigung senden
+ # curl -X POST "https://slack.com/webhook" -d "{\"text\": \"$anzahl Startups gefunden\"}"
+fi
+```
+
+### Python-Skript mit Logging
+
+```python
+#!/usr/bin/env python3
+"""TĂ€gliches Unternehmenssuche-Skript mit Logging."""
+
+import logging
+import json
+from datetime import datetime
+from handelsregister import search
+
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(levelname)s - %(message)s',
+ handlers=[
+ logging.FileHandler('unternehmenssuche.log'),
+ logging.StreamHandler()
+ ]
+)
+
+def main():
+ logging.info("Starte Unternehmenssuche")
+
+ suchbegriffe = ["Bank", "FinTech", "InsurTech"]
+
+ alle_ergebnisse = {}
+ for suchbegriff in suchbegriffe:
+ logging.info(f"Suche: {suchbegriff}")
+ ergebnisse = search(suchbegriff, states=["BE"])
+ alle_ergebnisse[suchbegriff] = ergebnisse
+ logging.info(f"{len(ergebnisse)} Unternehmen gefunden")
+
+ # Ergebnisse speichern
+ dateiname = f"ergebnisse_{datetime.now():%Y%m%d_%H%M%S}.json"
+ with open(dateiname, 'w') as f:
+ json.dump(alle_ergebnisse, f, indent=2)
+
+ logging.info(f"Ergebnisse gespeichert in {dateiname}")
+
+if __name__ == '__main__':
+ main()
+```
+
+---
+
+## Siehe auch
+
+- [Einfache Beispiele](simple.md) â Grundlegende Beispiele
+- [Fortgeschrittene Beispiele](advanced.md) â Komplexe AnwendungsfĂ€lle
+- [API-Referenz](../api/index.md) â Technische Dokumentation
+
diff --git a/docs/examples/integrations.md b/docs/examples/integrations.md
new file mode 100644
index 00000000..1d72d399
--- /dev/null
+++ b/docs/examples/integrations.md
@@ -0,0 +1,381 @@
+# Integration Examples
+
+Examples of integrating Handelsregister with popular frameworks and tools.
+
+## FastAPI
+
+### Simple API Endpoint
+
+```python
+from fastapi import FastAPI, HTTPException
+from handelsregister import search, get_details, SearchError
+
+app = FastAPI(title="Company Search API")
+
+@app.get("/search")
+async def search_companies(
+ q: str,
+ state: str = None,
+ limit: int = 10
+):
+ """Search for companies."""
+ try:
+ states = [state] if state else None
+ companies = search(q, states=states)
+ return {
+ "query": q,
+ "count": len(companies),
+ "results": companies[:limit]
+ }
+ except SearchError as e:
+ raise HTTPException(status_code=500, detail=str(e))
+
+@app.get("/company/{court}/{number}")
+async def get_company(court: str, number: str):
+ """Get company details by register."""
+ try:
+ companies = search(
+ "",
+ register_number=number
+ )
+ if not companies:
+ raise HTTPException(status_code=404, detail="Company not found")
+
+ details = get_details(companies[0])
+ return {
+ "name": details.name,
+ "capital": details.capital,
+ "address": {
+ "street": details.address.street if details.address else None,
+ "city": details.address.city if details.address else None,
+ },
+ "representatives": [
+ {"name": r.name, "role": r.role}
+ for r in details.representatives
+ ]
+ }
+ except SearchError as e:
+ raise HTTPException(status_code=500, detail=str(e))
+```
+
+---
+
+## Flask
+
+### Simple Flask App
+
+```python
+from flask import Flask, request, jsonify
+from handelsregister import search, get_details, SearchError
+
+app = Flask(__name__)
+
+@app.route('/search')
+def search_companies():
+ """Search endpoint."""
+ query = request.args.get('q', '')
+ state = request.args.get('state')
+
+ if not query:
+ return jsonify({"error": "Query required"}), 400
+
+ try:
+ states = [state] if state else None
+ companies = search(query, states=states)
+ return jsonify({
+ "count": len(companies),
+ "results": companies
+ })
+ except SearchError as e:
+ return jsonify({"error": str(e)}), 500
+
+@app.route('/company/')
+def company_details(name):
+ """Get company details."""
+ try:
+ companies = search(name, keyword_option="exact")
+ if not companies:
+ return jsonify({"error": "Not found"}), 404
+
+ details = get_details(companies[0])
+ return jsonify({
+ "name": details.name,
+ "capital": details.capital,
+ "currency": details.currency
+ })
+ except SearchError as e:
+ return jsonify({"error": str(e)}), 500
+
+if __name__ == '__main__':
+ app.run(debug=True)
+```
+
+---
+
+## Django
+
+### Django Management Command
+
+```python
+# myapp/management/commands/search_companies.py
+from django.core.management.base import BaseCommand
+from handelsregister import search
+
+class Command(BaseCommand):
+ help = 'Search for companies in the commercial register'
+
+ def add_arguments(self, parser):
+ parser.add_argument('query', type=str)
+ parser.add_argument('--state', type=str, default=None)
+ parser.add_argument('--limit', type=int, default=10)
+
+ def handle(self, *args, **options):
+ query = options['query']
+ states = [options['state']] if options['state'] else None
+ limit = options['limit']
+
+ companies = search(query, states=states)
+
+ self.stdout.write(f"Found {len(companies)} companies\n")
+ for company in companies[:limit]:
+ self.stdout.write(f" - {company.name}")
+```
+
+### Django Model Integration
+
+```python
+# models.py
+from django.db import models
+from handelsregister import search, get_details
+
+class Company(models.Model):
+ name = models.CharField(max_length=255)
+ court = models.CharField(max_length=100)
+ register_number = models.CharField(max_length=50)
+ capital = models.DecimalField(max_digits=15, decimal_places=2, null=True)
+ last_updated = models.DateTimeField(auto_now=True)
+
+ @classmethod
+ def create_from_register(cls, company_name):
+ """Create company from register data."""
+ companies = search(company_name, exact=True)
+ if not companies:
+ raise ValueError(f"Company not found: {company_name}")
+
+ details = get_details(companies[0])
+
+ return cls.objects.create(
+ name=details.name,
+ court=details.court,
+ register_number=details.register_number,
+ capital=float(details.capital) if details.capital else None
+ )
+
+ def refresh_from_register(self):
+ """Update company data from register."""
+ companies = search(self.name, exact=True)
+ if companies:
+ details = get_details(companies[0])
+ self.capital = float(details.capital) if details.capital else None
+ self.save()
+```
+
+---
+
+## Celery
+
+### Background Tasks
+
+```python
+# tasks.py
+from celery import Celery
+from handelsregister import search, get_details
+import time
+
+app = Celery('tasks', broker='redis://localhost:6379/0')
+
+@app.task(bind=True, max_retries=3)
+def search_companies_task(self, query, states=None):
+ """Search companies in background."""
+ try:
+ return search(query, states=states)
+ except Exception as e:
+ self.retry(countdown=60)
+
+@app.task
+def batch_search_task(keywords):
+ """Search multiple keywords with rate limiting."""
+ results = {}
+ for keyword in keywords:
+ results[keyword] = search(keyword)
+ time.sleep(60) # Rate limit
+ return results
+
+# Usage
+result = search_companies_task.delay("Bank", states=["BE"])
+companies = result.get(timeout=30)
+```
+
+---
+
+## SQLAlchemy
+
+### Store Results in Database
+
+```python
+from sqlalchemy import create_engine, Column, String, Float, DateTime
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+from datetime import datetime
+from handelsregister import search, get_details
+
+Base = declarative_base()
+engine = create_engine('sqlite:///companies.db')
+Session = sessionmaker(bind=engine)
+
+class Company(Base):
+ __tablename__ = 'companies'
+
+ id = Column(String, primary_key=True)
+ name = Column(String)
+ court = Column(String)
+ register_number = Column(String)
+ capital = Column(Float)
+ updated_at = Column(DateTime, default=datetime.utcnow)
+
+Base.metadata.create_all(engine)
+
+def save_company(company_name):
+ """Search and save company to database."""
+ session = Session()
+
+ companies = search(company_name, exact=True)
+ if not companies:
+ return None
+
+ details = get_details(companies[0])
+
+ company = Company(
+ id=f"{details.court}_{details.register_num}",
+ name=details.name,
+ court=details.court,
+ register_num=details.register_num,
+ capital=float(details.capital) if details.capital else None
+ )
+
+ session.merge(company)
+ session.commit()
+
+ return company
+```
+
+---
+
+## Jupyter Notebook
+
+### Interactive Analysis
+
+```python
+# Cell 1: Setup
+from handelsregister import search, get_details
+import pandas as pd
+import matplotlib.pyplot as plt
+
+# Cell 2: Search and explore
+companies = search("Bank", states=["BE", "HH", "BY"])
+# Convert Company objects to dicts for pandas
+df = pd.DataFrame([c.to_dict() for c in companies])
+df.head()
+
+# Cell 3: Visualize by state
+df['state'].value_counts().plot(kind='bar')
+plt.title('Banks by State')
+plt.xlabel('State')
+plt.ylabel('Count')
+plt.show()
+
+# Cell 4: Get details for top companies
+# Use Company objects directly (not DataFrame rows)
+for company in companies[:3]:
+ details = get_details(company)
+ print(f"{details.name}: {details.capital} {details.currency}")
+```
+
+---
+
+## CLI Scripts
+
+### Bash Integration
+
+```bash
+#!/bin/bash
+# search_and_notify.sh
+
+# Search for new companies
+results=$(handelsregister -s "Startup" --states BE --json)
+
+# Count results
+count=$(echo "$results" | jq 'length')
+
+if [ "$count" -gt 0 ]; then
+ echo "Found $count new startups in Berlin"
+
+ # Save to file with date
+ echo "$results" > "startups_$(date +%Y%m%d).json"
+
+ # Optional: Send notification
+ # curl -X POST "https://slack.com/webhook" -d "{\"text\": \"Found $count startups\"}"
+fi
+```
+
+### Python Script with Logging
+
+```python
+#!/usr/bin/env python3
+"""Daily company search script with logging."""
+
+import logging
+import json
+from datetime import datetime
+from handelsregister import search
+
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(levelname)s - %(message)s',
+ handlers=[
+ logging.FileHandler('company_search.log'),
+ logging.StreamHandler()
+ ]
+)
+
+def main():
+ logging.info("Starting company search")
+
+ keywords = ["Bank", "FinTech", "InsurTech"]
+
+ all_results = {}
+ for keyword in keywords:
+ logging.info(f"Searching: {keyword}")
+ results = search(keyword, states=["BE"])
+ all_results[keyword] = results
+ logging.info(f"Found {len(results)} companies")
+
+ # Save results
+ filename = f"results_{datetime.now():%Y%m%d_%H%M%S}.json"
+ with open(filename, 'w') as f:
+ json.dump(all_results, f, indent=2)
+
+ logging.info(f"Results saved to {filename}")
+
+if __name__ == '__main__':
+ main()
+```
+
+---
+
+## See Also
+
+- [Simple Examples](simple.md) â Basic examples
+- [Advanced Examples](advanced.md) â Complex use cases
+- [API Reference](../api/index.md) â Technical documentation
+
diff --git a/docs/examples/simple.de.md b/docs/examples/simple.de.md
new file mode 100644
index 00000000..4eb5a443
--- /dev/null
+++ b/docs/examples/simple.de.md
@@ -0,0 +1,235 @@
+# Einfache Beispiele
+
+Grundlegende Beispiele zum Einstieg mit dem Handelsregister-Package.
+
+## Suchbeispiele
+
+### Einfache Suche
+
+```python
+from handelsregister import search
+
+# Suche nach Unternehmen mit "Deutsche Bahn"
+firmen = search("Deutsche Bahn")
+
+print(f"{len(firmen)} Unternehmen gefunden")
+for firma in firmen:
+ print(f" - {firma.name}")
+```
+
+### Suche mit Bundesland-Filter
+
+```python
+from handelsregister import search
+
+# Suche nach Banken in Berlin
+banken = search("Bank", states=["BE"])
+
+print(f"Banken in Berlin: {len(banken)}")
+```
+
+### Suche mit mehreren Filtern
+
+```python
+from handelsregister import search
+
+# Aktive GmbHs in Hamburg
+firmen = search(
+ keywords="Consulting",
+ states=["HH"],
+ register_type="HRB",
+ include_deleted=False
+)
+```
+
+### Exakte Namenssuche
+
+```python
+from handelsregister import search
+
+# Exakten Firmennamen suchen
+firmen = search("GASAG AG", keyword_option="exact")
+
+if firmen:
+ print(f"Gefunden: {firmen[0].name}")
+else:
+ print("Unternehmen nicht gefunden")
+```
+
+---
+
+## Mit Ergebnissen arbeiten
+
+### Auf Unternehmensdaten zugreifen
+
+```python
+from handelsregister import search
+
+firmen = search("Siemens AG", keyword_option="exact")
+
+if firmen:
+ firma = firmen[0]
+
+ print(f"Name: {firma.name}")
+ print(f"Gericht: {firma.court}")
+ print(f"Nummer: {firma.register_num}")
+ print(f"Status: {firma.status}")
+ print(f"Bundesland: {firma.state}")
+```
+
+### In Liste von Namen konvertieren
+
+```python
+from handelsregister import search
+
+firmen = search("Bank", states=["BE"])
+
+# Nur die Namen extrahieren
+namen = [f['name'] for f in firmen]
+print(namen)
+```
+
+### Ergebnisse in Python filtern
+
+```python
+from handelsregister import search
+
+firmen = search("Bank")
+
+# Nach bestimmten Kriterien filtern
+grosse_banken = [
+ f for f in firmen
+ if "AG" in f['name'] and f['status'] == 'aktuell eingetragen'
+]
+```
+
+---
+
+## Details abrufen
+
+### Grundlegende Details
+
+```python
+from handelsregister import search, get_details
+
+# Nach Unternehmen suchen
+firmen = search("GASAG AG", keyword_option="exact")
+
+if firmen:
+ # VollstÀndige Details abrufen
+ details = get_details(firmen[0])
+
+ print(f"Name: {details.name}")
+ print(f"Kapital: {details.capital} {details.currency}")
+```
+
+### Auf Adresse zugreifen
+
+```python
+from handelsregister import search, get_details
+
+firmen = search("GASAG AG", keyword_option="exact")
+details = get_details(firmen[0])
+
+if details.address:
+ print(f"StraĂe: {details.address.street}")
+ print(f"Ort: {details.address.postal_code} {details.address.city}")
+```
+
+### Vertreter auflisten
+
+```python
+from handelsregister import search, get_details
+
+firmen = search("Deutsche Bahn AG", keyword_option="exact")
+details = get_details(firmen[0])
+
+print("GeschĂ€ftsfĂŒhrung:")
+for vertreter in details.representatives:
+ print(f" - {vertreter.name}: {vertreter.role}")
+```
+
+---
+
+## CLI-Beispiele
+
+### Einfache CLI-Suche
+
+```bash
+# Einfache Suche
+handelsregister -s "Deutsche Bahn"
+
+# Suche in bestimmtem Bundesland
+handelsregister -s "Bank" --states BE
+
+# Mehrere BundeslÀnder
+handelsregister -s "Bank" --states BE,HH,BY
+```
+
+### Ausgabeformate
+
+```bash
+# Standardausgabe
+handelsregister -s "GASAG"
+
+# JSON-Ausgabe
+handelsregister -s "GASAG" --json
+
+# Kompakte Ausgabe
+handelsregister -s "GASAG" --compact
+```
+
+### Mit Details
+
+```bash
+# Unternehmensdetails abrufen
+handelsregister -s "GASAG AG" --exact --details
+```
+
+### In Datei speichern
+
+```bash
+# JSON in Datei speichern
+handelsregister -s "Bank" --states BE --json > berliner_banken.json
+
+# Ergebnisse zÀhlen
+handelsregister -s "Bank" --json | jq 'length'
+```
+
+---
+
+## Fehlerbehandlung
+
+### Einfache Fehlerbehandlung
+
+```python
+from handelsregister import search, SearchError
+
+try:
+ firmen = search("Bank")
+ print(f"{len(firmen)} Unternehmen gefunden")
+except SearchError as e:
+ print(f"Suche fehlgeschlagen: {e}")
+```
+
+### Auf leere Ergebnisse prĂŒfen
+
+```python
+from handelsregister import search
+
+firmen = search("xyz123nichtvorhanden")
+
+if not firmen:
+ print("Keine Unternehmen gefunden")
+else:
+ print(f"{len(firmen)} Unternehmen gefunden")
+```
+
+---
+
+## NĂ€chste Schritte
+
+- [Fortgeschrittene Beispiele](advanced.md) â Komplexere AnwendungsfĂ€lle
+- [Integrationsbeispiele](integrations.md) â Verwendung mit anderen Tools
+- [API-Referenz](../api/index.md) â VollstĂ€ndige Dokumentation
+
diff --git a/docs/examples/simple.md b/docs/examples/simple.md
new file mode 100644
index 00000000..979f00fc
--- /dev/null
+++ b/docs/examples/simple.md
@@ -0,0 +1,249 @@
+# Simple Examples
+
+Basic examples to get started with the Handelsregister package.
+
+## Search Examples
+
+### Basic Search
+
+```python
+from handelsregister import search
+
+# Search for companies containing "Deutsche Bahn"
+companies = search("Deutsche Bahn")
+
+print(f"Found {len(companies)} companies")
+for company in companies:
+ print(f" - {company.name}")
+```
+
+### Search with State Filter
+
+```python
+from handelsregister import search, State
+
+# Search for banks in Berlin (recommended: Enum)
+banks = search("Bank", states=[State.BE])
+
+# String-based API still works
+banks = search("Bank", states=["BE"])
+
+print(f"Banks in Berlin: {len(banks)}")
+```
+
+### Search with Multiple Filters
+
+```python
+from handelsregister import search, State, RegisterType
+
+# Active GmbHs in Hamburg (recommended: Enums)
+companies = search(
+ keywords="Consulting",
+ states=[State.HH],
+ register_type=RegisterType.HRB,
+ include_deleted=False
+)
+
+# String-based API still works
+companies = search(
+ keywords="Consulting",
+ states=["HH"],
+ register_type="HRB",
+ include_deleted=False
+)
+```
+
+### Exact Name Search
+
+```python
+from handelsregister import search, KeywordMatch
+
+# Find exact company name (recommended: Enum)
+companies = search("GASAG AG", keyword_option=KeywordMatch.EXACT)
+
+# String-based API still works
+companies = search("GASAG AG", keyword_option="exact")
+
+if companies:
+ print(f"Found: {companies[0].name}")
+else:
+ print("Company not found")
+```
+
+---
+
+## Working with Results
+
+### Accessing Company Data
+
+```python
+from handelsregister import search, KeywordMatch
+
+companies = search("Siemens AG", keyword_option=KeywordMatch.EXACT)
+
+if companies:
+ company = companies[0]
+
+ print(f"Name: {company.name}")
+ print(f"Court: {company.court}")
+ print(f"Number: {company.register_num}")
+ print(f"Status: {company.status}")
+ print(f"State: {company.state}")
+```
+
+### Converting to List of Names
+
+```python
+from handelsregister import search, State
+
+companies = search("Bank", states=[State.BE])
+
+# Extract just the names
+names = [c.name for c in companies]
+print(names)
+```
+
+### Filtering Results in Python
+
+```python
+from handelsregister import search
+
+companies = search("Bank")
+
+# Filter for specific criteria
+large_banks = [
+ c for c in companies
+ if "AG" in c.name and c.status == 'currently registered'
+]
+```
+
+---
+
+## Getting Details
+
+### Basic Details
+
+```python
+from handelsregister import search, get_details, KeywordMatch
+
+# Search for company (recommended: Enum)
+companies = search("GASAG AG", keyword_option=KeywordMatch.EXACT)
+
+if companies:
+ # Get full details
+ details = get_details(companies[0])
+
+ print(f"Name: {details.name}")
+ print(f"Capital: {details.capital} {details.currency}")
+```
+
+### Accessing Address
+
+```python
+from handelsregister import search, get_details, KeywordMatch
+
+companies = search("GASAG AG", keyword_option=KeywordMatch.EXACT)
+details = get_details(companies[0])
+
+if details.address:
+ print(f"Street: {details.address.street}")
+ print(f"City: {details.address.postal_code} {details.address.city}")
+```
+
+### Listing Representatives
+
+```python
+from handelsregister import search, get_details, KeywordMatch
+
+companies = search("Deutsche Bahn AG", keyword_option=KeywordMatch.EXACT)
+details = get_details(companies[0])
+
+print("Management:")
+for rep in details.representatives:
+ print(f" - {rep.name}: {rep.role}")
+```
+
+---
+
+## CLI Examples
+
+### Basic CLI Search
+
+```bash
+# Simple search
+handelsregister -s "Deutsche Bahn"
+
+# Search in specific state
+handelsregister -s "Bank" --states BE
+
+# Multiple states
+handelsregister -s "Bank" --states BE,HH,BY
+```
+
+### Output Formats
+
+```bash
+# Default output
+handelsregister -s "GASAG"
+
+# JSON output
+handelsregister -s "GASAG" --json
+
+# Compact output
+handelsregister -s "GASAG" --compact
+```
+
+### With Details
+
+```bash
+# Get company details
+handelsregister -s "GASAG AG" --exact --details
+```
+
+### Save to File
+
+```bash
+# Save JSON to file
+handelsregister -s "Bank" --states BE --json > berlin_banks.json
+
+# Count results
+handelsregister -s "Bank" --json | jq 'length'
+```
+
+---
+
+## Error Handling
+
+### Basic Error Handling
+
+```python
+from handelsregister import search, SearchError
+
+try:
+ companies = search("Bank")
+ print(f"Found {len(companies)} companies")
+except SearchError as e:
+ print(f"Search failed: {e}")
+```
+
+### Checking for Empty Results
+
+```python
+from handelsregister import search
+
+companies = search("xyz123nonexistent")
+
+if not companies:
+ print("No companies found")
+else:
+ print(f"Found {len(companies)} companies")
+```
+
+---
+
+## Next Steps
+
+- [Advanced Examples](advanced.md) â More complex use cases
+- [Integration Examples](integrations.md) â Using with other tools
+- [API Reference](../api/index.md) â Complete documentation
+
diff --git a/docs/guide/cache.de.md b/docs/guide/cache.de.md
new file mode 100644
index 00000000..ec873c89
--- /dev/null
+++ b/docs/guide/cache.de.md
@@ -0,0 +1,282 @@
+# Caching
+
+Das Handelsregister-Package enthÀlt ein intelligentes Caching-System, um die Last auf dem Registerportal zu reduzieren und die Leistung zu verbessern.
+
+## Ăbersicht
+
+```mermaid
+graph LR
+ A[Ihre Anwendung] --> B{Cache-PrĂŒfung}
+ B -->|Treffer| C[Gecachte Daten zurĂŒckgeben]
+ B -->|Miss| D[Vom Portal abrufen]
+ D --> E[Im Cache speichern]
+ E --> C
+```
+
+### Standardverhalten
+
+- **Cache aktiviert** standardmĂ€Ăig
+- **TTL (Time-To-Live):** 24 Stunden
+- **Speicherort:** `~/.cache/handelsregister/`
+- **Format:** JSON-Dateien
+
+---
+
+## Cache verwenden
+
+### Automatisches Caching
+
+Caching funktioniert automatisch:
+
+```python
+from handelsregister import search
+
+# Erster Aufruf: Ruft vom Portal ab, speichert im Cache
+ergebnisse1 = search("Deutsche Bank")
+
+# Zweiter Aufruf: Gibt gecachte Daten zurĂŒck (schneller)
+ergebnisse2 = search("Deutsche Bank")
+```
+
+### Cache umgehen
+
+```python
+# Cache fĂŒr diesen Aufruf ĂŒberspringen
+ergebnisse = search("Deutsche Bank", force_refresh=True)
+```
+
+### Cache löschen
+
+```python
+from handelsregister import clear_cache
+
+# Gesamten Cache löschen
+clear_cache()
+```
+
+Oder ĂŒber CLI:
+
+```bash
+handelsregister --clear-cache
+```
+
+---
+
+## Cache-Konfiguration
+
+### Benutzerdefinierte TTL
+
+```python
+from handelsregister import HandelsRegister, SearchCache
+
+# 1-Stunden-Cache
+cache = SearchCache(ttl_hours=1)
+hr = HandelsRegister(cache=cache)
+
+# 7-Tage-Cache
+cache = SearchCache(ttl_hours=168)
+hr = HandelsRegister(cache=cache)
+```
+
+### Benutzerdefiniertes Cache-Verzeichnis
+
+```python
+from handelsregister import SearchCache
+
+cache = SearchCache(
+ cache_dir="/pfad/zum/eigenen/cache",
+ ttl_hours=24
+)
+```
+
+Oder ĂŒber Umgebungsvariable:
+
+```bash
+export HANDELSREGISTER_CACHE_DIR=/tmp/hr-cache
+```
+
+### Caching deaktivieren
+
+```python
+from handelsregister import HandelsRegister
+
+# Kein Caching
+hr = HandelsRegister(cache=None)
+ergebnisse = hr.search("Bank")
+```
+
+---
+
+## Cache-Struktur
+
+Der Cache speichert Daten als JSON-Dateien:
+
+```
+~/.cache/handelsregister/
+âââ searches/
+â âââ a1b2c3d4.json # Suchergebnisse
+â âââ e5f6g7h8.json
+â âââ ...
+âââ details/
+â âââ HRB_12345_Berlin.json # Unternehmensdetails
+â âââ HRB_67890_Hamburg.json
+â âââ ...
+âââ meta.json # Cache-Metadaten
+```
+
+### Cache-Key-Generierung
+
+Cache-Keys werden aus Suchparametern generiert:
+
+```python
+# Diese erzeugen den gleichen Cache-Key:
+search("Bank", states=["BE"])
+search("Bank", states=["BE"])
+
+# Diese erzeugen unterschiedliche Cache-Keys:
+search("Bank", states=["BE"])
+search("Bank", states=["HH"])
+search("Bank", states=["BE"], include_deleted=False)
+```
+
+---
+
+## Cache-Eintrags-Format
+
+```json
+{
+ "timestamp": "2024-01-15T10:30:00Z",
+ "ttl_hours": 24,
+ "query": {
+ "keywords": "Bank",
+ "states": ["BE"],
+ "register_type": null
+ },
+ "results": [
+ {
+ "name": "Deutsche Bank AG",
+ "court": "Frankfurt am Main",
+ "register_num": "HRB 12345",
+ "status": "aktuell eingetragen"
+ }
+ ]
+}
+```
+
+---
+
+## Cache-Status prĂŒfen
+
+```python
+from handelsregister import SearchCache
+
+cache = SearchCache()
+
+# PrĂŒfen ob Eintrag existiert und gĂŒltig ist
+if cache.has_valid_entry("Bank", states=["BE"]):
+ print("Verwende gecachte Daten")
+else:
+ print("Rufe vom Portal ab")
+
+# Cache-Statistiken abrufen
+stats = cache.get_stats()
+print(f"EintrÀge gesamt: {stats['total']}")
+print(f"GĂŒltige EintrĂ€ge: {stats['valid']}")
+print(f"Abgelaufene EintrÀge: {stats['expired']}")
+print(f"Cache-GröĂe: {stats['size_mb']:.2f} MB")
+```
+
+---
+
+## Best Practices fĂŒr Caching
+
+### 1. Standard-Caching verwenden
+
+FĂŒr die meisten AnwendungsfĂ€lle ist der 24-Stunden-Cache angemessen:
+
+```python
+# Einfach search() verwenden - Caching ist automatisch
+ergebnisse = search("Bank")
+```
+
+### 2. KĂŒrzere TTL fĂŒr volatile Daten
+
+Wenn Sie aktuelle Daten benötigen (z.B. fĂŒr rechtliche Verfahren):
+
+```python
+cache = SearchCache(ttl_hours=1) # 1 Stunde
+hr = HandelsRegister(cache=cache)
+```
+
+### 3. LĂ€ngere TTL fĂŒr Analysen
+
+FĂŒr historische Analysen, wo AktualitĂ€t weniger kritisch ist:
+
+```python
+cache = SearchCache(ttl_hours=168) # 7 Tage
+hr = HandelsRegister(cache=cache)
+```
+
+### 4. Kein Cache fĂŒr Einzeloperationen
+
+```python
+# Einzelabfrage, kein Caching nötig
+ergebnisse = search("Spezifische Firma GmbH", force_refresh=True)
+```
+
+### 5. Periodische Cache-Bereinigung
+
+```python
+from handelsregister import SearchCache
+
+cache = SearchCache()
+cache.cleanup_expired() # Abgelaufene EintrÀge entfernen
+```
+
+---
+
+## Speicherplatz
+
+Der Cache kann ĂŒber die Zeit wachsen. Ăberwachen und bei Bedarf bereinigen:
+
+```python
+from handelsregister import SearchCache
+
+cache = SearchCache()
+
+# Cache-GröĂe abrufen
+stats = cache.get_stats()
+print(f"Cache-GröĂe: {stats['size_mb']:.2f} MB")
+
+# Abgelaufene EintrÀge bereinigen
+entfernt = cache.cleanup_expired()
+print(f"{entfernt} abgelaufene EintrÀge entfernt")
+
+# Oder alles löschen
+cache.clear()
+```
+
+---
+
+## Thread-Sicherheit
+
+Der Cache ist thread-sicher fĂŒr parallelen Zugriff:
+
+```python
+from concurrent.futures import ThreadPoolExecutor
+from handelsregister import search
+
+suchbegriffe = ["Bank", "Versicherung", "AG", "GmbH"]
+
+with ThreadPoolExecutor(max_workers=4) as executor:
+ ergebnisse = list(executor.map(search, suchbegriffe))
+```
+
+---
+
+## Siehe auch
+
+- [API-Referenz: SearchCache](../api/classes.md) â Technische Details
+- [Als Library verwenden](library.md) â Allgemeine Library-Verwendung
+- [CLI-Optionen](cli.md) â Cache-bezogene CLI-Optionen
+
diff --git a/docs/guide/cache.md b/docs/guide/cache.md
new file mode 100644
index 00000000..b3731c36
--- /dev/null
+++ b/docs/guide/cache.md
@@ -0,0 +1,282 @@
+# Caching
+
+The Handelsregister package includes an intelligent caching system to reduce load on the register portal and improve performance.
+
+## Overview
+
+```mermaid
+graph LR
+ A[Your Application] --> B{Cache Check}
+ B -->|Hit| C[Return Cached Data]
+ B -->|Miss| D[Fetch from Portal]
+ D --> E[Store in Cache]
+ E --> C
+```
+
+### Default Behavior
+
+- **Cache enabled** by default
+- **TTL (Time-To-Live):** 24 hours
+- **Location:** `~/.cache/handelsregister/`
+- **Format:** JSON files
+
+---
+
+## Using the Cache
+
+### Automatic Caching
+
+Caching works automatically:
+
+```python
+from handelsregister import search
+
+# First call: fetches from portal, stores in cache
+results1 = search("Deutsche Bank")
+
+# Second call: returns cached data (faster)
+results2 = search("Deutsche Bank")
+```
+
+### Bypassing the Cache
+
+```python
+# Skip cache for this call
+results = search("Deutsche Bank", force_refresh=True)
+```
+
+### Clearing the Cache
+
+```python
+from handelsregister import clear_cache
+
+# Clear entire cache
+clear_cache()
+```
+
+Or via CLI:
+
+```bash
+handelsregister --clear-cache
+```
+
+---
+
+## Cache Configuration
+
+### Custom TTL
+
+```python
+from handelsregister import HandelsRegister, SearchCache
+
+# 1-hour cache
+cache = SearchCache(ttl_hours=1)
+hr = HandelsRegister(cache=cache)
+
+# 7-day cache
+cache = SearchCache(ttl_hours=168)
+hr = HandelsRegister(cache=cache)
+```
+
+### Custom Cache Directory
+
+```python
+from handelsregister import SearchCache
+
+cache = SearchCache(
+ cache_dir="/path/to/custom/cache",
+ ttl_hours=24
+)
+```
+
+Or via environment variable:
+
+```bash
+export HANDELSREGISTER_CACHE_DIR=/tmp/hr-cache
+```
+
+### Disable Caching
+
+```python
+from handelsregister import HandelsRegister
+
+# No caching at all
+hr = HandelsRegister(cache=None)
+results = hr.search("Bank")
+```
+
+---
+
+## Cache Structure
+
+The cache stores data as JSON files:
+
+```
+~/.cache/handelsregister/
+âââ searches/
+â âââ a1b2c3d4.json # Search results
+â âââ e5f6g7h8.json
+â âââ ...
+âââ details/
+â âââ HRB_12345_Berlin.json # Company details
+â âââ HRB_67890_Hamburg.json
+â âââ ...
+âââ meta.json # Cache metadata
+```
+
+### Cache Key Generation
+
+Cache keys are generated from search parameters:
+
+```python
+# These create the same cache key:
+search("Bank", states=["BE"])
+search("Bank", states=["BE"])
+
+# These create different cache keys:
+search("Bank", states=["BE"])
+search("Bank", states=["HH"])
+search("Bank", states=["BE"], include_deleted=False)
+```
+
+---
+
+## Cache Entry Format
+
+```json
+{
+ "timestamp": "2024-01-15T10:30:00Z",
+ "ttl_hours": 24,
+ "query": {
+ "keywords": "Bank",
+ "states": ["BE"],
+ "register_type": null
+ },
+ "results": [
+ {
+ "name": "Deutsche Bank AG",
+ "court": "Frankfurt am Main",
+ "register_num": "HRB 12345",
+ "status": "currently registered"
+ }
+ ]
+}
+```
+
+---
+
+## Checking Cache Status
+
+```python
+from handelsregister import SearchCache
+
+cache = SearchCache()
+
+# Check if entry exists and is valid
+if cache.has_valid_entry("Bank", states=["BE"]):
+ print("Using cached data")
+else:
+ print("Will fetch from portal")
+
+# Get cache statistics
+stats = cache.get_stats()
+print(f"Total entries: {stats['total']}")
+print(f"Valid entries: {stats['valid']}")
+print(f"Expired entries: {stats['expired']}")
+print(f"Cache size: {stats['size_mb']:.2f} MB")
+```
+
+---
+
+## Cache Best Practices
+
+### 1. Use Default Caching
+
+For most use cases, the default 24-hour cache is appropriate:
+
+```python
+# Just use search() - caching is automatic
+results = search("Bank")
+```
+
+### 2. Shorter TTL for Volatile Data
+
+If you need current data (e.g., for legal processes):
+
+```python
+cache = SearchCache(ttl_hours=1) # 1 hour
+hr = HandelsRegister(cache=cache)
+```
+
+### 3. Longer TTL for Analysis
+
+For historical analysis where freshness is less critical:
+
+```python
+cache = SearchCache(ttl_hours=168) # 7 days
+hr = HandelsRegister(cache=cache)
+```
+
+### 4. No Cache for One-Time Operations
+
+```python
+# Single query, no need to cache
+results = search("Specific Company GmbH", force_refresh=True)
+```
+
+### 5. Periodic Cache Cleanup
+
+```python
+from handelsregister import SearchCache
+
+cache = SearchCache()
+cache.cleanup_expired() # Remove expired entries
+```
+
+---
+
+## Disk Space
+
+The cache can grow over time. Monitor and clean as needed:
+
+```python
+from handelsregister import SearchCache
+
+cache = SearchCache()
+
+# Get cache size
+stats = cache.get_stats()
+print(f"Cache size: {stats['size_mb']:.2f} MB")
+
+# Clean expired entries
+removed = cache.cleanup_expired()
+print(f"Removed {removed} expired entries")
+
+# Or clear everything
+cache.clear()
+```
+
+---
+
+## Thread Safety
+
+The cache is thread-safe for concurrent access:
+
+```python
+from concurrent.futures import ThreadPoolExecutor
+from handelsregister import search
+
+keywords = ["Bank", "Versicherung", "AG", "GmbH"]
+
+with ThreadPoolExecutor(max_workers=4) as executor:
+ results = list(executor.map(search, keywords))
+```
+
+---
+
+## See Also
+
+- [API Reference: SearchCache](../api/classes.md) â Technical details
+- [Using as Library](library.md) â General library usage
+- [CLI Options](cli.md) â Cache-related CLI options
+
diff --git a/docs/guide/cli.de.md b/docs/guide/cli.de.md
new file mode 100644
index 00000000..0448158f
--- /dev/null
+++ b/docs/guide/cli.de.md
@@ -0,0 +1,311 @@
+# Kommandozeile (CLI)
+
+Das Handelsregister-Package enthĂ€lt eine leistungsfĂ€hige Kommandozeilen-Schnittstelle fĂŒr schnelle Abfragen.
+
+## Grundlegende Verwendung
+
+```bash
+# Einfache Suche
+handelsregister -s "Deutsche Bahn"
+# Oder die kĂŒrzere Variante verwenden:
+hrg -s "Deutsche Bahn"
+
+# Mit uv
+uv run handelsregister -s "Deutsche Bahn"
+# Oder:
+uv run hrg -s "Deutsche Bahn"
+```
+
+---
+
+## Suchoptionen
+
+### `-s, --search`
+Der Suchbegriff (erforderlich):
+
+```bash
+handelsregister -s "Bank"
+handelsregister -s "Deutsche Bahn AG"
+```
+
+### `--states`
+Nach BundeslÀndern filtern (kommagetrennt):
+
+```bash
+# Ein Bundesland
+handelsregister -s "Bank" --states BE
+
+# Mehrere BundeslÀnder
+handelsregister -s "Bank" --states BE,HH,BY
+```
+
+### `--register-type`
+Nach Registerart filtern:
+
+```bash
+# Nur HRB (Kapitalgesellschaften)
+handelsregister -s "GmbH" --register-type HRB
+
+# Nur HRA (Personengesellschaften)
+handelsregister -s "KG" --register-type HRA
+```
+
+### `--exact`
+Exakte NamensĂŒbereinstimmung erfordern:
+
+```bash
+handelsregister -s "GASAG AG" --exact
+```
+
+### `--active-only`
+Nur aktuell eingetragene Unternehmen anzeigen:
+
+```bash
+handelsregister -s "Bank" --active-only
+```
+
+---
+
+## Ausgabeformate
+
+### Standardausgabe
+
+```bash
+handelsregister -s "GASAG"
+```
+
+```
+3 Unternehmen gefunden:
+
+1. GASAG AG
+ Gericht: Berlin (Charlottenburg)
+ Nummer: HRB 44343
+ Status: aktuell eingetragen
+
+2. GASAG Beteiligungs GmbH
+ Gericht: Berlin (Charlottenburg)
+ Nummer: HRB 87654
+ Status: aktuell eingetragen
+...
+```
+
+### JSON-Ausgabe
+
+```bash
+handelsregister -s "GASAG" --json
+```
+
+```json
+[
+ {
+ "name": "GASAG AG",
+ "court": "Berlin (Charlottenburg)",
+ "register_num": "HRB 44343",
+ "status": "aktuell eingetragen",
+ "state": "BE"
+ },
+ ...
+]
+```
+
+### Kompakte Ausgabe
+
+```bash
+handelsregister -s "GASAG" --compact
+```
+
+```
+GASAG AG | Berlin (Charlottenburg) | HRB 44343
+GASAG Beteiligungs GmbH | Berlin (Charlottenburg) | HRB 87654
+```
+
+---
+
+## Details abrufen
+
+### `--details`
+Erweiterte Informationen abrufen:
+
+```bash
+handelsregister -s "GASAG AG" --exact --details
+```
+
+```
+GASAG AG
+=========
+Gericht: Berlin (Charlottenburg)
+Nummer: HRB 44343
+Status: aktuell eingetragen
+
+Kapital: 306.977.800,00 EUR
+
+Adresse:
+ GASAG-Platz 1
+ 10963 Berlin
+
+Vertreter:
+ - Dr. Gerhard Holtmeier (Vorstandsvorsitzender)
+ - Stefan Michels (Vorstand)
+ - Jörg Simon (Vorstand)
+
+Unternehmensgegenstand:
+ Gegenstand des Unternehmens ist die Versorgung mit Energie...
+```
+
+### `--details --json`
+Details im JSON-Format:
+
+```bash
+handelsregister -s "GASAG AG" --exact --details --json
+```
+
+---
+
+## Caching-Optionen
+
+### `--no-cache`
+Cache ĂŒberspringen, immer frische Daten abrufen:
+
+```bash
+handelsregister -s "Bank" --no-cache
+```
+
+### `--clear-cache`
+Den gesamten Cache löschen:
+
+```bash
+handelsregister --clear-cache
+```
+
+---
+
+## Weitere Optionen
+
+### `--help`
+Hilfe anzeigen:
+
+```bash
+handelsregister --help
+```
+
+```
+usage: handelsregister [-h] [-s SEARCH] [--states STATES]
+ [--register-type TYPE] [--exact]
+ [--active-only] [--details] [--json]
+ [--compact] [--no-cache] [--clear-cache]
+
+Abfrage des deutschen Handelsregisters
+
+options:
+ -h, --help Hilfe anzeigen
+ -s, --search SEARCH Suchbegriff
+ --states STATES Nach BundeslÀndern filtern (kommagetrennt)
+ --register-type TYPE Nach Registerart filtern
+ --exact Exakte NamensĂŒbereinstimmung
+ --active-only Nur aktuell eingetragene
+ --details Unternehmensdetails abrufen
+ --json JSON-Ausgabe
+ --compact Kompakte Ausgabe
+ --no-cache Cache ĂŒberspringen
+ --clear-cache Cache löschen
+```
+
+### `--version`
+Version anzeigen:
+
+```bash
+handelsregister --version
+```
+
+---
+
+## Beispiele
+
+### Banken in Berlin suchen
+
+```bash
+handelsregister -s "Bank" --states BE --register-type HRB
+```
+
+### In JSON-Datei exportieren
+
+```bash
+handelsregister -s "Versicherung" --states BY --json > versicherungen_by.json
+```
+
+### Mit jq verarbeiten
+
+```bash
+handelsregister -s "Bank" --json | jq '.[].name'
+```
+
+### Durch BundeslÀnder iterieren
+
+```bash
+for bundesland in BE HH BY; do
+ echo "=== $bundesland ==="
+ handelsregister -s "Bank" --states $bundesland --compact
+ sleep 60 # Rate-Limit beachten
+done
+```
+
+### Details fĂŒr bestimmtes Unternehmen abrufen
+
+```bash
+handelsregister -s "Deutsche Bahn AG" --exact --details
+```
+
+---
+
+## Exit-Codes
+
+| Code | Bedeutung |
+|------|-----------|
+| 0 | Erfolg |
+| 1 | Keine Ergebnisse gefunden |
+| 2 | Verbindungsfehler |
+| 3 | Rate-Limit ĂŒberschritten |
+| 4 | UngĂŒltige Argumente |
+
+### Exit-Codes in Skripten verwenden
+
+```bash
+handelsregister -s "Bank" --states BE
+
+if [ $? -eq 0 ]; then
+ echo "Suche erfolgreich"
+elif [ $? -eq 1 ]; then
+ echo "Keine Ergebnisse gefunden"
+elif [ $? -eq 3 ]; then
+ echo "Rate-Limit - spÀter erneut versuchen"
+fi
+```
+
+---
+
+## Umgebungsvariablen
+
+| Variable | Beschreibung | Standard |
+|----------|--------------|----------|
+| `HANDELSREGISTER_CACHE_DIR` | Cache-Verzeichnis | `~/.cache/handelsregister` |
+| `HANDELSREGISTER_CACHE_TTL` | Cache-TTL in Stunden | `24` |
+| `HANDELSREGISTER_DEBUG` | Debug-Ausgabe aktivieren | `0` |
+
+```bash
+# Beispiel: Eigenes Cache-Verzeichnis
+export HANDELSREGISTER_CACHE_DIR=/tmp/hr-cache
+handelsregister -s "Bank"
+
+# Beispiel: Cache deaktivieren
+export HANDELSREGISTER_CACHE_TTL=0
+handelsregister -s "Bank"
+```
+
+---
+
+## Siehe auch
+
+- [Als Library verwenden](library.md) â Python-Integration
+- [Referenztabellen](../reference/states.md) â BundeslĂ€nder-Codes, Registerarten
+- [Beispiele](../examples/simple.md) â Weitere Beispiele
+
diff --git a/docs/guide/cli.md b/docs/guide/cli.md
new file mode 100644
index 00000000..c47159bc
--- /dev/null
+++ b/docs/guide/cli.md
@@ -0,0 +1,311 @@
+# Command Line (CLI)
+
+The Handelsregister package includes a powerful command-line interface for quick queries.
+
+## Basic Usage
+
+```bash
+# Simple search
+handelsregister -s "Deutsche Bahn"
+# Or use the shorter alias:
+hrg -s "Deutsche Bahn"
+
+# With uv
+uv run handelsregister -s "Deutsche Bahn"
+# Or:
+uv run hrg -s "Deutsche Bahn"
+```
+
+---
+
+## Search Options
+
+### `-s, --search`
+The search term (required):
+
+```bash
+handelsregister -s "Bank"
+handelsregister -s "Deutsche Bahn AG"
+```
+
+### `--states`
+Filter by states (comma-separated):
+
+```bash
+# Single state
+handelsregister -s "Bank" --states BE
+
+# Multiple states
+handelsregister -s "Bank" --states BE,HH,BY
+```
+
+### `--register-type`
+Filter by register type:
+
+```bash
+# Only HRB (corporations)
+handelsregister -s "GmbH" --register-type HRB
+
+# Only HRA (partnerships)
+handelsregister -s "KG" --register-type HRA
+```
+
+### `--exact`
+Require exact name match:
+
+```bash
+handelsregister -s "GASAG AG" --exact
+```
+
+### `--active-only`
+Only show currently registered companies:
+
+```bash
+handelsregister -s "Bank" --active-only
+```
+
+---
+
+## Output Formats
+
+### Default Output
+
+```bash
+handelsregister -s "GASAG"
+```
+
+```
+Found 3 companies:
+
+1. GASAG AG
+ Court: Berlin (Charlottenburg)
+ Number: HRB 44343
+ Status: currently registered
+
+2. GASAG Beteiligungs GmbH
+ Court: Berlin (Charlottenburg)
+ Number: HRB 87654
+ Status: currently registered
+...
+```
+
+### JSON Output
+
+```bash
+handelsregister -s "GASAG" --json
+```
+
+```json
+[
+ {
+ "name": "GASAG AG",
+ "register_court": "Berlin (Charlottenburg)",
+ "register_num": "HRB 44343",
+ "status": "currently registered",
+ "state": "BE"
+ },
+ ...
+]
+```
+
+### Compact Output
+
+```bash
+handelsregister -s "GASAG" --compact
+```
+
+```
+GASAG AG | Berlin (Charlottenburg) | HRB 44343
+GASAG Beteiligungs GmbH | Berlin (Charlottenburg) | HRB 87654
+```
+
+---
+
+## Fetching Details
+
+### `--details`
+Fetch extended information:
+
+```bash
+handelsregister -s "GASAG AG" --exact --details
+```
+
+```
+GASAG AG
+=========
+Court: Berlin (Charlottenburg)
+Number: HRB 44343
+Status: currently registered
+
+Capital: 306,977,800.00 EUR
+
+Address:
+ GASAG-Platz 1
+ 10963 Berlin
+
+Representatives:
+ - Dr. Gerhard Holtmeier (Vorstandsvorsitzender)
+ - Stefan Michels (Vorstand)
+ - Jörg Simon (Vorstand)
+
+Business Purpose:
+ Gegenstand des Unternehmens ist die Versorgung mit Energie...
+```
+
+### `--details --json`
+Details in JSON format:
+
+```bash
+handelsregister -s "GASAG AG" --exact --details --json
+```
+
+---
+
+## Caching Options
+
+### `--no-cache`
+Skip cache, always fetch fresh data:
+
+```bash
+handelsregister -s "Bank" --no-cache
+```
+
+### `--clear-cache`
+Clear the entire cache:
+
+```bash
+handelsregister --clear-cache
+```
+
+---
+
+## Other Options
+
+### `--help`
+Show help message:
+
+```bash
+handelsregister --help
+```
+
+```
+usage: handelsregister [-h] [-s SEARCH] [--states STATES]
+ [--register-type TYPE] [--exact]
+ [--active-only] [--details] [--json]
+ [--compact] [--no-cache] [--clear-cache]
+
+Query the German commercial register
+
+options:
+ -h, --help show this help message and exit
+ -s, --search SEARCH Search term
+ --states STATES Filter by states (comma-separated)
+ --register-type TYPE Filter by register type
+ --exact Exact name match
+ --active-only Only currently registered
+ --details Fetch company details
+ --json JSON output
+ --compact Compact output
+ --no-cache Skip cache
+ --clear-cache Clear cache
+```
+
+### `--version`
+Show version:
+
+```bash
+handelsregister --version
+```
+
+---
+
+## Examples
+
+### Search for Banks in Berlin
+
+```bash
+handelsregister -s "Bank" --states BE --register-type HRB
+```
+
+### Export to JSON File
+
+```bash
+handelsregister -s "Versicherung" --states BY --json > insurance_by.json
+```
+
+### Pipe to jq for Processing
+
+```bash
+handelsregister -s "Bank" --json | jq '.[].name'
+```
+
+### Loop Through States
+
+```bash
+for state in BE HH BY; do
+ echo "=== $state ==="
+ handelsregister -s "Bank" --states $state --compact
+ sleep 60 # Respect rate limit
+done
+```
+
+### Get Details for Specific Company
+
+```bash
+handelsregister -s "Deutsche Bahn AG" --exact --details
+```
+
+---
+
+## Exit Codes
+
+| Code | Meaning |
+|------|---------|
+| 0 | Success |
+| 1 | No results found |
+| 2 | Connection error |
+| 3 | Rate limit exceeded |
+| 4 | Invalid arguments |
+
+### Using Exit Codes in Scripts
+
+```bash
+handelsregister -s "Bank" --states BE
+
+if [ $? -eq 0 ]; then
+ echo "Search successful"
+elif [ $? -eq 1 ]; then
+ echo "No results found"
+elif [ $? -eq 3 ]; then
+ echo "Rate limit - try again later"
+fi
+```
+
+---
+
+## Environment Variables
+
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `HANDELSREGISTER_CACHE_DIR` | Cache directory | `~/.cache/handelsregister` |
+| `HANDELSREGISTER_CACHE_TTL` | Cache TTL in hours | `24` |
+| `HANDELSREGISTER_DEBUG` | Enable debug output | `0` |
+
+```bash
+# Example: Custom cache directory
+export HANDELSREGISTER_CACHE_DIR=/tmp/hr-cache
+handelsregister -s "Bank"
+
+# Example: Disable cache
+export HANDELSREGISTER_CACHE_TTL=0
+handelsregister -s "Bank"
+```
+
+---
+
+## See Also
+
+- [Using as Library](library.md) â Python integration
+- [Reference Tables](../reference/states.md) â State codes, register types
+- [Examples](../examples/simple.md) â More examples
+
diff --git a/docs/guide/details.de.md b/docs/guide/details.de.md
new file mode 100644
index 00000000..1c7f61d5
--- /dev/null
+++ b/docs/guide/details.de.md
@@ -0,0 +1,278 @@
+# Details abrufen
+
+Lernen Sie, wie Sie erweiterte Unternehmensinformationen ĂŒber die einfachen Suchergebnisse hinaus abrufen.
+
+## Ăbersicht
+
+Die einfache Suche liefert begrenzte Informationen. FĂŒr vollstĂ€ndige Details verwenden Sie die `get_details()`-Funktion:
+
+| Suchergebnis | Details |
+|--------------|---------|
+| Firmenname | â Plus historische Namen |
+| Registergericht | â |
+| Registernummer | â |
+| Status | â Plus Eintragungsdaten |
+| | **ZusÀtzlich:** |
+| | Kapital (Stammkapital/Grundkapital) |
+| | GeschÀftsadresse |
+| | Vertreter (GeschĂ€ftsfĂŒhrer, Vorstand) |
+| | Unternehmensgegenstand |
+| | Gesellschafter (bei Personengesellschaften) |
+| | VollstÀndige Historie |
+
+---
+
+## Grundlegende Verwendung
+
+```python
+from handelsregister import search, get_details
+
+# Zuerst nach dem Unternehmen suchen
+firmen = search("GASAG AG", keyword_option="exact")
+
+if firmen:
+ # Dann Details abrufen
+ details = get_details(firmen[0])
+
+ print(f"Firma: {details.name}")
+ print(f"Kapital: {details.capital} {details.currency}")
+```
+
+---
+
+## Das CompanyDetails-Objekt
+
+Die `get_details()`-Funktion gibt eine `CompanyDetails`-Dataclass zurĂŒck:
+
+### Grundinformationen
+
+```python
+details = get_details(firma)
+
+# Grundinfo
+print(details.name) # "GASAG AG"
+print(details.court) # "Berlin (Charlottenburg)"
+print(details.register_number) # "HRB 44343"
+print(details.register_type) # "HRB"
+print(details.status) # "aktuell eingetragen"
+```
+
+### Kapital
+
+```python
+# Stammkapital/Grundkapital
+print(details.capital) # "306977800.00"
+print(details.currency) # "EUR"
+
+# Formatierte Ausgabe
+if details.capital:
+ betrag = float(details.capital)
+ print(f"Kapital: {betrag:,.2f} {details.currency}")
+ # Ausgabe: Kapital: 306,977,800.00 EUR
+```
+
+### Adresse
+
+Die Adresse wird als `Address`-Objekt zurĂŒckgegeben:
+
+```python
+adresse = details.address
+
+print(adresse.street) # "GASAG-Platz 1"
+print(adresse.postal_code) # "10963"
+print(adresse.city) # "Berlin"
+print(adresse.country) # "Deutschland"
+
+# VollstÀndige Adresse
+print(adresse)
+# GASAG-Platz 1
+# 10963 Berlin
+# Deutschland
+```
+
+### Vertreter
+
+Vertreter (GeschĂ€ftsfĂŒhrer, Vorstandsmitglieder) werden als Liste zurĂŒckgegeben:
+
+```python
+for vertreter in details.representatives:
+ print(f"Name: {vertreter.name}")
+ print(f"Rolle: {vertreter.role}")
+ print(f"Geburtsdatum: {vertreter.birth_date}")
+ print(f"Ort: {vertreter.location}")
+ print("---")
+```
+
+**Ausgabe:**
+
+```
+Name: Dr. Gerhard Holtmeier
+Rolle: Vorstandsvorsitzender
+Geburtsdatum: 1960-05-15
+Ort: Berlin
+---
+Name: Stefan Michels
+Rolle: Vorstand
+Geburtsdatum: 1972-03-22
+Ort: Potsdam
+---
+```
+
+### Gesellschafter (Personengesellschaften)
+
+FĂŒr Personengesellschaften (KG, OHG, GbR) sind Gesellschafterinformationen verfĂŒgbar:
+
+```python
+if details.owners:
+ for gesellschafter in details.owners:
+ print(f"Name: {gesellschafter.name}")
+ print(f"Typ: {gesellschafter.owner_type}")
+ print(f"Anteil: {gesellschafter.share}")
+ print(f"Haftung: {gesellschafter.liability}")
+```
+
+### Unternehmensgegenstand
+
+```python
+print("Unternehmensgegenstand:")
+print(details.business_purpose)
+```
+
+### Historie
+
+Die vollstÀndige Historie der Registereintragungen:
+
+```python
+for eintrag in details.history:
+ print(f"Datum: {eintrag.date}")
+ print(f"Typ: {eintrag.entry_type}")
+ print(f"Inhalt: {eintrag.content[:100]}...")
+ print("---")
+```
+
+---
+
+## VollstÀndiges Beispiel
+
+```python
+from handelsregister import search, get_details
+
+def zeige_firmendetails(name: str):
+ """Zeigt vollstĂ€ndige Details fĂŒr ein Unternehmen an."""
+
+ # Suchen
+ firmen = search(name, keyword_option="exact")
+
+ if not firmen:
+ print(f"Kein Unternehmen gefunden: {name}")
+ return
+
+ # Details abrufen
+ details = get_details(firmen[0])
+
+ # Kopfzeile
+ print("=" * 60)
+ print(f" {details.name}")
+ print("=" * 60)
+
+ # Registrierung
+ print(f"\nRegister: {details.court}")
+ print(f"Nummer: {details.register_type} {details.register_number}")
+ print(f"Status: {details.status}")
+
+ # Kapital
+ if details.capital:
+ betrag = float(details.capital)
+ print(f"\nKapital: {betrag:,.2f} {details.currency}")
+
+ # Adresse
+ if details.address:
+ print(f"\nAdresse:")
+ print(f" {details.address.street}")
+ print(f" {details.address.postal_code} {details.address.city}")
+
+ # Vertreter
+ if details.representatives:
+ print(f"\nVertreter ({len(details.representatives)}):")
+ for v in details.representatives:
+ rolle = f" ({v.role})" if v.role else ""
+ print(f" âą {v.name}{rolle}")
+
+ # Unternehmensgegenstand
+ if details.business_purpose:
+ print(f"\nUnternehmensgegenstand:")
+ # KĂŒrzen wenn zu lang
+ zweck = details.business_purpose
+ if len(zweck) > 200:
+ zweck = zweck[:200] + "..."
+ print(f" {zweck}")
+
+# Verwendung
+zeige_firmendetails("GASAG AG")
+```
+
+---
+
+## Details-Caching
+
+Details werden separat von Suchergebnissen gecacht:
+
+```python
+# Erster Aufruf: Ruft vom Portal ab
+details1 = get_details(firma)
+
+# Zweiter Aufruf: Nutzt Cache
+details2 = get_details(firma)
+
+# Frischen Abruf erzwingen
+details3 = get_details(firma, force_refresh=True)
+```
+
+---
+
+## Stapelverarbeitung
+
+FĂŒr mehrere Unternehmen sequentiell mit Verzögerungen verarbeiten:
+
+```python
+import time
+from handelsregister import search, get_details
+
+firmen = search("Bank", states=["BE"])
+
+alle_details = []
+for i, firma in enumerate(firmen[:10]): # Limit zur Sicherheit
+ print(f"Rufe ab {i+1}/{len(firmen)}: {firma.name}")
+
+ details = get_details(firma)
+ alle_details.append(details)
+
+ # Rate-Limit beachten: 60/Stunde = 1/Minute
+ time.sleep(60)
+
+print(f"\nDetails fĂŒr {len(alle_details)} Unternehmen abgerufen")
+```
+
+---
+
+## Fehlerbehandlung
+
+```python
+from handelsregister import get_details, SearchError
+
+try:
+ details = get_details(firma)
+except SearchError as e:
+ print(f"Details konnten nicht abgerufen werden: {e}")
+ # Fallback auf Grundinfo aus Suchergebnis
+ print(f"Firma: {firma.name}")
+```
+
+---
+
+## Siehe auch
+
+- [API-Referenz: get_details()](../api/functions.md) â Technische Dokumentation
+- [Datenmodelle](../api/models.md) â CompanyDetails, Address, Representative
+- [Caching](cache.md) â Wie Caching funktioniert
+
diff --git a/docs/guide/details.md b/docs/guide/details.md
new file mode 100644
index 00000000..8bc37855
--- /dev/null
+++ b/docs/guide/details.md
@@ -0,0 +1,278 @@
+# Fetching Details
+
+Learn how to retrieve extended company information beyond basic search results.
+
+## Overview
+
+The basic search returns limited information. For complete details, use the `get_details()` function:
+
+| Search Result | Details |
+|---------------|---------|
+| Company name | â Plus historical names |
+| Register court | â |
+| Register number | â |
+| Status | â Plus registration dates |
+| | **Additional:** |
+| | Capital (Stammkapital/Grundkapital) |
+| | Business address |
+| | Representatives (directors, board) |
+| | Business purpose |
+| | Owners (for partnerships) |
+| | Complete history |
+
+---
+
+## Basic Usage
+
+```python
+from handelsregister import search, get_details
+
+# First, search for the company
+companies = search("GASAG AG", keyword_option="exact")
+
+if companies:
+ # Then fetch details
+ details = get_details(companies[0])
+
+ print(f"Company: {details.name}")
+ print(f"Capital: {details.capital} {details.currency}")
+```
+
+---
+
+## The CompanyDetails Object
+
+The `get_details()` function returns a `CompanyDetails` dataclass:
+
+### Basic Information
+
+```python
+details = get_details(company)
+
+# Basic info
+print(details.name) # "GASAG AG"
+print(details.court) # "Berlin (Charlottenburg)"
+print(details.register_num) # "HRB 44343 B"
+print(details.state) # "Berlin"
+print(details.status) # "currently registered"
+```
+
+### Capital
+
+```python
+# Share capital
+print(details.capital) # "306977800.00"
+print(details.currency) # "EUR"
+
+# Formatted output
+if details.capital:
+ amount = float(details.capital)
+ print(f"Capital: {amount:,.2f} {details.currency}")
+ # Output: Capital: 306,977,800.00 EUR
+```
+
+### Address
+
+The address is returned as an `Address` object:
+
+```python
+address = details.address
+
+print(address.street) # "GASAG-Platz 1"
+print(address.postal_code) # "10963"
+print(address.city) # "Berlin"
+print(address.country) # "Deutschland"
+
+# Full address
+print(address)
+# GASAG-Platz 1
+# 10963 Berlin
+# Deutschland
+```
+
+### Representatives
+
+Representatives (directors, board members) are returned as a list:
+
+```python
+for rep in details.representatives:
+ print(f"Name: {rep.name}")
+ print(f"Role: {rep.role}")
+ print(f"Birth date: {rep.birth_date}")
+ print(f"Location: {rep.location}")
+ print("---")
+```
+
+**Output:**
+
+```
+Name: Dr. Gerhard Holtmeier
+Role: Vorstandsvorsitzender
+Birth date: 1960-05-15
+Location: Berlin
+---
+Name: Stefan Michels
+Role: Vorstand
+Birth date: 1972-03-22
+Location: Potsdam
+---
+```
+
+### Owners (Partnerships)
+
+For partnerships (KG, OHG, GbR), owner information is available:
+
+```python
+if details.owners:
+ for owner in details.owners:
+ print(f"Name: {owner.name}")
+ print(f"Type: {owner.owner_type}")
+ print(f"Share: {owner.share}")
+ print(f"Liability: {owner.liability}")
+```
+
+### Business Purpose
+
+```python
+print("Business Purpose:")
+print(details.business_purpose)
+```
+
+### History
+
+The complete history of register entries:
+
+```python
+for entry in details.history:
+ print(f"Date: {entry.date}")
+ print(f"Type: {entry.entry_type}")
+ print(f"Content: {entry.content[:100]}...")
+ print("---")
+```
+
+---
+
+## Complete Example
+
+```python
+from handelsregister import search, get_details
+
+def show_company_details(name: str):
+ """Display complete details for a company."""
+
+ # Search
+ companies = search(name, keyword_option="exact")
+
+ if not companies:
+ print(f"No company found: {name}")
+ return
+
+ # Get details
+ details = get_details(companies[0])
+
+ # Header
+ print("=" * 60)
+ print(f" {details.name}")
+ print("=" * 60)
+
+ # Registration
+ print(f"\nRegister: {details.court}")
+ print(f"Number: {details.register_num}")
+ print(f"Status: {details.status}")
+
+ # Capital
+ if details.capital:
+ amount = float(details.capital)
+ print(f"\nCapital: {amount:,.2f} {details.currency}")
+
+ # Address
+ if details.address:
+ print(f"\nAddress:")
+ print(f" {details.address.street}")
+ print(f" {details.address.postal_code} {details.address.city}")
+
+ # Representatives
+ if details.representatives:
+ print(f"\nRepresentatives ({len(details.representatives)}):")
+ for rep in details.representatives:
+ role = f" ({rep.role})" if rep.role else ""
+ print(f" âą {rep.name}{role}")
+
+ # Business purpose
+ if details.purpose:
+ print(f"\nBusiness Purpose:")
+ # Truncate if too long
+ purpose = details.purpose
+ if len(purpose) > 200:
+ purpose = purpose[:200] + "..."
+ print(f" {purpose}")
+
+# Usage
+show_company_details("GASAG AG")
+```
+
+---
+
+## Caching Details
+
+Details are cached separately from search results:
+
+```python
+# First call: fetches from portal
+details1 = get_details(company)
+
+# Second call: uses cache
+details2 = get_details(company)
+
+# Force fresh fetch
+details3 = get_details(company, force_refresh=True)
+```
+
+---
+
+## Batch Processing
+
+For multiple companies, process sequentially with delays:
+
+```python
+import time
+from handelsregister import search, get_details
+
+companies = search("Bank", states=["BE"])
+
+all_details = []
+for i, company in enumerate(companies[:10]): # Limit for safety
+ print(f"Fetching {i+1}/{len(companies)}: {company.name}")
+
+ details = get_details(company)
+ all_details.append(details)
+
+ # Respect rate limit: 60/hour = 1/minute
+ time.sleep(60)
+
+print(f"\nFetched details for {len(all_details)} companies")
+```
+
+---
+
+## Error Handling
+
+```python
+from handelsregister import get_details, SearchError
+
+try:
+ details = get_details(company)
+except SearchError as e:
+ print(f"Could not fetch details: {e}")
+ # Fallback to basic info from search result
+ print(f"Company: {company.name}")
+```
+
+---
+
+## See Also
+
+- [API Reference: get_details()](../api/functions.md) â Technical documentation
+- [Data Models](../api/models.md) â CompanyDetails, Address, Representative
+- [Caching](cache.md) â How caching works
+
diff --git a/docs/guide/index.de.md b/docs/guide/index.de.md
new file mode 100644
index 00000000..270feb11
--- /dev/null
+++ b/docs/guide/index.de.md
@@ -0,0 +1,149 @@
+# Benutzerhandbuch
+
+Willkommen zum umfassenden Benutzerhandbuch fĂŒr das Handelsregister-Package. Dieses Handbuch behandelt alle Funktionen im Detail.
+
+## Ăbersicht
+
+Das Handelsregister-Package bietet zwei Hauptwege zur Abfrage des deutschen Handelsregisters:
+
+1. **Als Python-Library** â FĂŒr die Integration in Ihre Anwendungen
+2. **Als CLI-Tool** â FĂŒr schnelle Kommandozeilen-Abfragen
+
+---
+
+## Kapitel
+
+
+
+- :material-code-braces:{ .lg .middle } __Als Library verwenden__
+
+ ---
+
+ Lernen Sie, wie Sie Handelsregister als Python-Library in Ihren Anwendungen verwenden.
+
+ [:octicons-arrow-right-24: Library-Anleitung](library.md)
+
+- :material-console:{ .lg .middle } __Kommandozeile (CLI)__
+
+ ---
+
+ Nutzen Sie die Kommandozeilen-Schnittstelle fĂŒr schnelle Abfragen und Scripting.
+
+ [:octicons-arrow-right-24: CLI-Anleitung](cli.md)
+
+- :material-file-document-multiple:{ .lg .middle } __Details abrufen__
+
+ ---
+
+ Wie Sie erweiterte Unternehmensinformationen wie Kapital, Vertreter und mehr abrufen.
+
+ [:octicons-arrow-right-24: Details-Anleitung](details.md)
+
+- :material-cached:{ .lg .middle } __Caching__
+
+ ---
+
+ Verstehen und konfigurieren Sie den Caching-Mechanismus.
+
+ [:octicons-arrow-right-24: Caching-Anleitung](cache.md)
+
+
+
+---
+
+## Kernkonzepte
+
+### Datenstrukturen
+
+Das Package verwendet mehrere Datenstrukturen:
+
+| Struktur | Beschreibung |
+|----------|--------------|
+| `Company` | Grundlegende Unternehmensinformationen aus Suchergebnissen |
+| `CompanyDetails` | Erweiterte Informationen (Kapital, Vertreter, etc.) |
+| `Address` | Strukturierte Adressdaten |
+| `Representative` | GeschĂ€ftsfĂŒhrer, Vorstandsmitglieder, etc. |
+| `Owner` | Gesellschafter (bei Personengesellschaften) |
+
+### Suchablauf
+
+```mermaid
+sequenceDiagram
+ participant App as Ihre Anwendung
+ participant HR as HandelsRegister
+ participant Cache as SearchCache
+ participant Portal as handelsregister.de
+
+ App->>HR: search("Deutsche Bahn")
+ HR->>Cache: Cache prĂŒfen
+ alt Cache-Treffer
+ Cache-->>HR: Gecachte Ergebnisse
+ else Cache-Miss
+ HR->>Portal: HTTP-Anfrage
+ Portal-->>HR: HTML-Antwort
+ HR->>Cache: Ergebnisse speichern
+ end
+ HR-->>App: List[Company]
+```
+
+### Rate Limiting
+
+!!! warning "Wichtig"
+ Das Registerportal erlaubt maximal **60 Anfragen pro Stunde**. Das Package erzwingt dieses Limit nicht automatisch, daher sind Sie selbst dafĂŒr verantwortlich, innerhalb dieser Grenzen zu bleiben.
+
+---
+
+## Schnellreferenz
+
+### HĂ€ufigste Operationen
+
+=== "Suche"
+
+ ```python
+ from handelsregister import search
+
+ # Einfache Suche
+ firmen = search("Deutsche Bahn")
+
+ # Mit Filtern
+ firmen = search(
+ keywords="Bank",
+ states=["BE", "HH"],
+ register_type="HRB",
+ include_deleted=False
+ )
+ ```
+
+=== "Details abrufen"
+
+ ```python
+ from handelsregister import search, get_details
+
+ firmen = search("GASAG AG", keyword_option="exact")
+ if firmen:
+ details = get_details(firmen[0])
+ print(details.capital)
+ print(details.representatives)
+ ```
+
+=== "CLI"
+
+ ```bash
+ # Suche
+ handelsregister -s "Deutsche Bahn"
+
+ # Mit Filtern und JSON-Ausgabe
+ handelsregister -s "Bank" --states BE,HH --json
+
+ # Mit Details
+ handelsregister -s "GASAG AG" --exact --details
+ ```
+
+---
+
+## Siehe auch
+
+- [API-Referenz](../api/index.md) â Technische Dokumentation
+- [Beispiele](../examples/simple.md) â Praktische Code-Beispiele
+- [Referenztabellen](../reference/states.md) â BundeslĂ€nder-Codes, Registerarten, etc.
+
diff --git a/docs/guide/index.md b/docs/guide/index.md
new file mode 100644
index 00000000..7e2dbe71
--- /dev/null
+++ b/docs/guide/index.md
@@ -0,0 +1,149 @@
+# User Guide
+
+Welcome to the comprehensive user guide for the Handelsregister package. This guide covers all functionality in detail.
+
+## Overview
+
+The Handelsregister package provides two main ways to query the German commercial register:
+
+1. **As a Python Library** â For integration into your applications
+2. **As a CLI Tool** â For quick command-line queries
+
+---
+
+## Chapters
+
+
+
+- :material-code-braces:{ .lg .middle } __Using as Library__
+
+ ---
+
+ Learn how to use Handelsregister as a Python library in your applications.
+
+ [:octicons-arrow-right-24: Library Guide](library.md)
+
+- :material-console:{ .lg .middle } __Command Line (CLI)__
+
+ ---
+
+ Use the command-line interface for quick queries and scripting.
+
+ [:octicons-arrow-right-24: CLI Guide](cli.md)
+
+- :material-file-document-multiple:{ .lg .middle } __Fetching Details__
+
+ ---
+
+ How to retrieve extended company information like capital, representatives, and more.
+
+ [:octicons-arrow-right-24: Details Guide](details.md)
+
+- :material-cached:{ .lg .middle } __Caching__
+
+ ---
+
+ Understand and configure the caching mechanism.
+
+ [:octicons-arrow-right-24: Caching Guide](cache.md)
+
+
+
+---
+
+## Core Concepts
+
+### Data Structures
+
+The package uses several data structures:
+
+| Structure | Description |
+|-----------|-------------|
+| `Company` | Basic company information from search results |
+| `CompanyDetails` | Extended information (capital, representatives, etc.) |
+| `Address` | Structured address data |
+| `Representative` | Managing directors, board members, etc. |
+| `Owner` | Shareholders (for partnerships) |
+
+### Search Flow
+
+```mermaid
+sequenceDiagram
+ participant App as Your Application
+ participant HR as HandelsRegister
+ participant Cache as SearchCache
+ participant Portal as handelsregister.de
+
+ App->>HR: search("Deutsche Bahn")
+ HR->>Cache: Check cache
+ alt Cache hit
+ Cache-->>HR: Cached results
+ else Cache miss
+ HR->>Portal: HTTP request
+ Portal-->>HR: HTML response
+ HR->>Cache: Store results
+ end
+ HR-->>App: List[Company]
+```
+
+### Rate Limiting
+
+!!! warning "Important"
+ The register portal allows a maximum of **60 requests per hour**. The package does not automatically enforce this limit, so you are responsible for staying within these bounds.
+
+---
+
+## Quick Reference
+
+### Most Common Operations
+
+=== "Search"
+
+ ```python
+ from handelsregister import search
+
+ # Simple search
+ companies = search("Deutsche Bahn")
+
+ # With filters
+ companies = search(
+ keywords="Bank",
+ states=["BE", "HH"],
+ register_type="HRB",
+ include_deleted=False
+ )
+ ```
+
+=== "Get Details"
+
+ ```python
+ from handelsregister import search, get_details
+
+ companies = search("GASAG AG", keyword_option="exact")
+ if companies:
+ details = get_details(companies[0])
+ print(details.capital)
+ print(details.representatives)
+ ```
+
+=== "CLI"
+
+ ```bash
+ # Search
+ handelsregister -s "Deutsche Bahn"
+
+ # With filters and JSON output
+ handelsregister -s "Bank" --states BE,HH --json
+
+ # With details
+ handelsregister -s "GASAG AG" --exact --details
+ ```
+
+---
+
+## See Also
+
+- [API Reference](../api/index.md) â Technical documentation
+- [Examples](../examples/simple.md) â Practical code examples
+- [Reference Tables](../reference/states.md) â State codes, register types, etc.
+
diff --git a/docs/guide/library.de.md b/docs/guide/library.de.md
new file mode 100644
index 00000000..f123b04d
--- /dev/null
+++ b/docs/guide/library.de.md
@@ -0,0 +1,322 @@
+# Als Library verwenden
+
+Dieses Kapitel erklÀrt, wie Sie Handelsregister als Python-Library in Ihren Anwendungen verwenden.
+
+## Grundlegende Verwendung
+
+### Die `search()`-Funktion
+
+Die `search()`-Funktion ist der Haupteinstiegspunkt fĂŒr Unternehmenssuchen:
+
+```python
+from handelsregister import search
+
+# Einfache Suche
+firmen = search("Deutsche Bahn")
+
+# Ergebnisse verarbeiten
+for firma in firmen:
+ print(f"Name: {firma.name}")
+ print(f"Gericht: {firma.court}")
+ print(f"Nummer: {firma.register_num}")
+ print(f"Status: {firma.status}")
+ print("---")
+```
+
+### RĂŒckgabewert
+
+Die Funktion gibt eine Liste von `Company`-Objekten mit folgenden Attributen zurĂŒck:
+
+| Attribut | Typ | Beschreibung |
+|----------|-----|--------------|
+| `name` | `str` | Firmenname |
+| `court` | `str` | Registergericht |
+| `register_num` | `str \| None` | Registernummer (z.B. "HRB 12345 B") |
+| `status` | `str` | Registrierungsstatus |
+| `state` | `str` | Bundesland (z.B. "Berlin") |
+| `status_normalized` | `str` | Normalisierter Status (z.B. "CURRENTLY_REGISTERED") |
+| `documents` | `str` | VerfĂŒgbare Dokumenttypen |
+| `history` | `List[HistoryEntry]` | Liste historischer EintrÀge |
+
+---
+
+## Suchparameter
+
+### Alle Parameter
+
+```python
+firmen = search(
+ keywords="Bank", # Suchbegriff (erforderlich)
+ states=["BE", "HH"], # Nach BundeslÀndern filtern
+ register_type="HRB", # Nach Registerart filtern
+ court="Berlin", # Spezifisches Registergericht
+ register_number="12345", # Spezifische Registernummer
+ include_deleted=False, # Nur aktuell eingetragene
+ exact=False, # Exakte NamensĂŒbereinstimmung
+ force_refresh=False, # Caching verwenden
+ similar_sounding=False, # Ăhnlich klingende Namen einschlieĂen
+)
+```
+
+### Parameter im Detail
+
+#### `keywords` (erforderlich)
+Der Suchbegriff fĂŒr Firmennamen:
+
+```python
+# TeilĂŒbereinstimmung
+search("Deutsche") # Findet "Deutsche Bahn", "Deutsche Bank", etc.
+
+# Mehrere Wörter
+search("Deutsche Bank AG")
+```
+
+#### `states`
+Nach deutschen BundeslÀndern filtern mit ISO-Codes:
+
+```python
+# Ein Bundesland
+search("Bank", states=["BE"])
+
+# Mehrere BundeslÀnder
+search("Bank", states=["BE", "HH", "BY"])
+```
+
+Siehe [BundeslĂ€nder-Codes](../reference/states.md) fĂŒr alle Codes.
+
+#### `register_type`
+Nach Registerart filtern:
+
+```python
+# Nur HRB (Kapitalgesellschaften)
+search("GmbH", register_type="HRB")
+
+# Nur HRA (Einzelunternehmen, Personengesellschaften)
+search("KG", register_type="HRA")
+```
+
+Siehe [Registerarten](../reference/registers.md) fĂŒr alle Arten.
+
+#### `only_active`
+Nach aktuell eingetragenen Unternehmen filtern:
+
+```python
+# Nur aktive Unternehmen
+search("Bank", include_deleted=False)
+
+# Gelöschte/fusionierte einschlieĂen
+search("Bank", include_deleted=True)
+```
+
+#### `exact`
+Exakte NamensĂŒbereinstimmung erfordern:
+
+```python
+# Nur exakte Ăbereinstimmung
+search("GASAG AG", keyword_option="exact")
+
+# TeilĂŒbereinstimmungen erlaubt (Standard)
+search("GASAG", exact=False)
+```
+
+---
+
+## Mit Ergebnissen arbeiten
+
+### Ergebnisse durchlaufen
+
+```python
+firmen = search("Deutsche Bahn")
+
+# Als Liste
+for firma in firmen:
+ verarbeite(firma)
+
+# Mit Index
+for i, firma in enumerate(firmen):
+ print(f"{i+1}. {firma.name}")
+
+# In Python filtern
+berliner_firmen = [
+ f for f in firmen
+ if f['state'] == 'BE'
+]
+```
+
+### Auf Ergebnisse prĂŒfen
+
+```python
+firmen = search("xyz123nichtvorhanden")
+
+if not firmen:
+ print("Keine Unternehmen gefunden")
+else:
+ print(f"{len(firmen)} Unternehmen gefunden")
+```
+
+### In DataFrame konvertieren
+
+```python
+import pandas as pd
+from handelsregister import search
+
+firmen = search("Bank", states=["BE"])
+
+# In DataFrame konvertieren
+df = pd.DataFrame(firmen)
+
+# Analysieren
+print(df.groupby('court').size())
+```
+
+---
+
+## Fortgeschrittene Verwendung
+
+### Die HandelsRegister-Klasse verwenden
+
+FĂŒr mehr Kontrolle verwenden Sie die `HandelsRegister`-Klasse direkt:
+
+```python
+from handelsregister import HandelsRegister
+
+# Instanz erstellen
+hr = HandelsRegister()
+
+# Suche mit voller Kontrolle
+ergebnisse = hr.search(
+ keywords="Bank",
+ register_type="HRB",
+ states=["BE"]
+)
+
+# Details abrufen
+if ergebnisse:
+ details = hr.get_details(ergebnisse[0])
+```
+
+### Benutzerdefinierte Cache-Konfiguration
+
+```python
+from handelsregister import HandelsRegister, SearchCache
+
+# Benutzerdefinierter Cache mit 1-Stunden-TTL
+cache = SearchCache(ttl_hours=1)
+
+hr = HandelsRegister(cache=cache)
+ergebnisse = hr.search("Bank")
+```
+
+### Ohne Caching
+
+```python
+# Cache fĂŒr diese Suche deaktivieren
+firmen = search("Bank", force_refresh=True)
+
+# Oder global
+hr = HandelsRegister(cache=None)
+```
+
+---
+
+## Fehlerbehandlung
+
+```python
+from handelsregister import (
+ search,
+ SearchError,
+ RateLimitError,
+ ConnectionError,
+ ParseError
+)
+
+try:
+ firmen = search("Bank")
+except RateLimitError:
+ print("Rate-Limit ĂŒberschritten (max 60/Stunde)")
+ # Warten und erneut versuchen
+except ConnectionError:
+ print("Verbindung zum Registerportal nicht möglich")
+except ParseError:
+ print("Fehler beim Parsen der Antwort")
+except SearchError as e:
+ print(f"Allgemeiner Suchfehler: {e}")
+```
+
+### Wiederholungslogik
+
+```python
+import time
+from handelsregister import search, RateLimitError
+
+def suche_mit_wiederholung(keywords, max_versuche=3):
+ for versuch in range(max_versuche):
+ try:
+ return search(keywords)
+ except RateLimitError:
+ if versuch < max_versuche - 1:
+ wartezeit = (versuch + 1) * 60 # 1, 2, 3 Minuten
+ print(f"Rate-limitiert, warte {wartezeit}s...")
+ time.sleep(wartezeit)
+ else:
+ raise
+```
+
+---
+
+## Best Practices
+
+### 1. Rate-Limits respektieren
+
+```python
+import time
+
+suchbegriffe = ["Bank", "Versicherung", "AG", ...]
+
+for keywords in suchbegriffe:
+ ergebnisse = search(keywords)
+ verarbeite(ergebnisse)
+ time.sleep(60) # 1 Minute zwischen Suchen warten
+```
+
+### 2. Caching nutzen
+
+```python
+# Cache ist standardmĂ€Ăig aktiviert
+# Ergebnisse werden 24 Stunden wiederverwendet
+
+firmen = search("Bank") # Erster Aufruf: Portal-Anfrage
+firmen = search("Bank") # Zweiter Aufruf: aus Cache
+```
+
+### 3. Server-seitig filtern
+
+```python
+# Gut: Auf dem Server filtern
+firmen = search("Bank", states=["BE"], register_type="HRB")
+
+# Weniger effizient: Client-seitig filtern
+firmen = search("Bank")
+berlin_hrb = [f for f in firmen if f['state'] == 'BE']
+```
+
+### 4. Leere Ergebnisse behandeln
+
+```python
+firmen = search(keywords)
+
+if not firmen:
+ logger.info(f"Keine Ergebnisse fĂŒr '{keywords}'")
+ return []
+
+# Verarbeitung fortsetzen
+```
+
+---
+
+## Siehe auch
+
+- [API-Referenz: search()](../api/functions.md) â Technische Details
+- [Details abrufen](details.md) â Erweiterte Informationen abrufen
+- [Beispiele](../examples/simple.md) â Code-Beispiele
+
diff --git a/docs/guide/library.md b/docs/guide/library.md
new file mode 100644
index 00000000..b42f4303
--- /dev/null
+++ b/docs/guide/library.md
@@ -0,0 +1,347 @@
+# Using as Library
+
+This chapter explains how to use Handelsregister as a Python library in your applications.
+
+## Basic Usage
+
+### The `search()` Function
+
+The `search()` function is the main entry point for company searches:
+
+```python
+from handelsregister import search
+
+# Simple search
+companies = search("Deutsche Bahn")
+
+# Process results
+for company in companies:
+ print(f"Name: {company.name}")
+ print(f"Court: {company.court}")
+ print(f"Number: {company.register_num}")
+ print(f"Status: {company.status}")
+ print("---")
+```
+
+### Return Value
+
+The function returns a list of `Company` objects with the following attributes:
+
+| Attribute | Type | Description |
+|-----------|------|-------------|
+| `name` | `str` | Company name |
+| `court` | `str` | Register court |
+| `register_num` | `str \| None` | Register number (e.g., "HRB 12345 B") |
+| `state` | `str` | State name (e.g., "Berlin") |
+| `status` | `str` | Registration status |
+| `status_normalized` | `str` | Normalized status (e.g., "CURRENTLY_REGISTERED") |
+| `documents` | `str` | Available document types |
+| `history` | `List[HistoryEntry]` | List of historical entries (name, location) |
+
+---
+
+## Search Parameters
+
+### All Parameters
+
+```python
+companies = search(
+ keywords="Bank", # Search term (required)
+ keyword_option="all", # How to match: "all", "min", or "exact"
+ states=["BE", "HH"], # Filter by states (can use State enum)
+ register_type="HRB", # Filter by register type (can use RegisterType enum)
+ register_number="12345", # Specific register number
+ include_deleted=False, # Only currently registered
+ similar_sounding=False, # Include similar-sounding names
+ results_per_page=100, # Results per page
+ force_refresh=False, # Use caching
+ debug=False, # Debug logging
+)
+```
+
+### Parameter Details
+
+#### `keywords` (required)
+The search term for company names:
+
+```python
+# Partial match
+search("Deutsche") # Finds "Deutsche Bahn", "Deutsche Bank", etc.
+
+# Multiple words
+search("Deutsche Bank AG")
+```
+
+#### `states`
+Filter by German federal states using ISO codes:
+
+```python
+from handelsregister import search, State
+
+# Single state (recommended: Enum)
+search("Bank", states=[State.BE])
+
+# Multiple states (recommended: Enums)
+search("Bank", states=[State.BE, State.HH, State.BY])
+
+# String-based API still works
+search("Bank", states=["BE"])
+search("Bank", states=["BE", "HH", "BY"])
+```
+
+See [State Codes](../reference/states.md) for all codes.
+
+#### `register_type`
+Filter by register type:
+
+```python
+from handelsregister import search, RegisterType
+
+# Only HRB (corporations) - recommended: Enum
+search("GmbH", register_type=RegisterType.HRB)
+
+# Only HRA (sole proprietors, partnerships)
+search("KG", register_type=RegisterType.HRA)
+
+# String-based API still works
+search("GmbH", register_type="HRB")
+```
+
+See [Register Types](../reference/registers.md) for all types.
+
+#### `keyword_option`
+How to match keywords:
+
+```python
+from handelsregister import search, KeywordMatch
+
+# All keywords must match (default) - recommended: Enum
+search("Deutsche Bank", keyword_option=KeywordMatch.ALL)
+
+# At least one keyword must match
+search("Deutsche Bank", keyword_option=KeywordMatch.MIN)
+
+# Exact name match
+search("GASAG AG", keyword_option=KeywordMatch.EXACT)
+
+# String-based API still works
+search("Deutsche Bank", keyword_option="all")
+search("GASAG AG", keyword_option="exact")
+```
+
+#### `include_deleted`
+Include deleted/historical entries:
+
+```python
+# Only currently registered (default)
+search("Bank", include_deleted=False)
+
+# Include deleted/merged companies
+search("Bank", include_deleted=True)
+```
+
+---
+
+## Working with Results
+
+### Iterating Results
+
+```python
+companies = search("Deutsche Bahn")
+
+# As list
+for company in companies:
+ process(company)
+
+# With index
+for i, company in enumerate(companies):
+ print(f"{i+1}. {company.name}")
+
+# Filter in Python
+berlin_companies = [
+ c for c in companies
+ if c.state == 'Berlin'
+]
+```
+
+### Checking for Results
+
+```python
+companies = search("xyz123nonexistent")
+
+if not companies:
+ print("No companies found")
+else:
+ print(f"Found {len(companies)} companies")
+```
+
+### Converting to DataFrame
+
+```python
+import pandas as pd
+from handelsregister import search
+
+from handelsregister import search, State
+
+companies = search("Bank", states=[State.BE])
+
+# Convert Company objects to dicts for pandas
+df = pd.DataFrame([c.to_dict() for c in companies])
+
+# Analyze
+print(df.groupby('court').size())
+```
+
+---
+
+## Advanced Usage
+
+### Using the HandelsRegister Class
+
+For more control, use the `HandelsRegister` class directly:
+
+```python
+from handelsregister import HandelsRegister, State, RegisterType
+
+# Create instance
+hr = HandelsRegister()
+
+# Search with full control (recommended: Enums)
+results = hr.search(
+ keywords="Bank",
+ register_type=RegisterType.HRB,
+ states=[State.BE]
+)
+
+# Get details
+if results:
+ details = hr.get_details(results[0])
+```
+
+### Custom Cache Configuration
+
+```python
+from handelsregister import HandelsRegister, SearchCache
+
+# Custom cache with 1-hour TTL
+cache = SearchCache(ttl_hours=1)
+
+hr = HandelsRegister(cache=cache)
+results = hr.search("Bank")
+```
+
+### Without Caching
+
+```python
+# Disable cache for this search
+companies = search("Bank", force_refresh=True)
+
+# Or globally
+hr = HandelsRegister(cache=None)
+```
+
+---
+
+## Error Handling
+
+```python
+from handelsregister import (
+ search,
+ SearchError,
+ RateLimitError,
+ ConnectionError,
+ ParseError
+)
+
+try:
+ companies = search("Bank")
+except RateLimitError:
+ print("Rate limit exceeded (max 60/hour)")
+ # Wait and retry
+except ConnectionError:
+ print("Could not connect to register portal")
+except ParseError:
+ print("Error parsing response")
+except SearchError as e:
+ print(f"General search error: {e}")
+```
+
+### Retry Logic
+
+```python
+import time
+from handelsregister import search, RateLimitError
+
+def search_with_retry(keywords, max_retries=3):
+ for attempt in range(max_retries):
+ try:
+ return search(keywords)
+ except RateLimitError:
+ if attempt < max_retries - 1:
+ wait_time = (attempt + 1) * 60 # 1, 2, 3 minutes
+ print(f"Rate limited, waiting {wait_time}s...")
+ time.sleep(wait_time)
+ else:
+ raise
+```
+
+---
+
+## Best Practices
+
+### 1. Respect Rate Limits
+
+```python
+import time
+
+keywords_list = ["Bank", "Versicherung", "AG", ...]
+
+for keywords in keywords_list:
+ results = search(keywords)
+ process(results)
+ time.sleep(60) # Wait 1 minute between searches
+```
+
+### 2. Use Caching
+
+```python
+# Cache is enabled by default
+# Results are reused for 24 hours
+
+companies = search("Bank") # First call: hits portal
+companies = search("Bank") # Second call: from cache
+```
+
+### 3. Filter Server-Side
+
+```python
+# Good: Filter on the server
+from handelsregister import search, State, RegisterType
+
+companies = search("Bank", states=[State.BE], register_type=RegisterType.HRB)
+
+# Less efficient: Filter client-side
+companies = search("Bank")
+berlin_hrb = [c for c in companies if c.state == 'Berlin']
+```
+
+### 4. Handle Empty Results
+
+```python
+companies = search(keywords)
+
+if not companies:
+ logger.info(f"No results for '{keywords}'")
+ return []
+
+# Continue processing
+```
+
+---
+
+## See Also
+
+- [API Reference: search()](../api/functions.md) â Technical details
+- [Fetching Details](details.md) â How to get extended information
+- [Examples](../examples/simple.md) â Code examples
+
diff --git a/docs/index.de.md b/docs/index.de.md
new file mode 100644
index 00000000..389821f7
--- /dev/null
+++ b/docs/index.de.md
@@ -0,0 +1,191 @@
+# Handelsregister
+
+
+
+- :material-rocket-launch:{ .lg .middle } __Schneller Einstieg__
+
+ ---
+
+ Installieren Sie das Package und starten Sie in wenigen Minuten mit der Abfrage des deutschen Handelsregisters.
+
+ [:octicons-arrow-right-24: Installation](installation.md)
+
+- :material-code-braces:{ .lg .middle } __Als Library__
+
+ ---
+
+ Integrieren Sie die Handelsregister-Abfrage in Ihre Python-Anwendungen mit einer einfachen API.
+
+ [:octicons-arrow-right-24: Library-Dokumentation](guide/library.md)
+
+- :material-console:{ .lg .middle } __Kommandozeile__
+
+ ---
+
+ Nutzen Sie das CLI-Tool fĂŒr schnelle Abfragen direkt aus dem Terminal.
+
+ [:octicons-arrow-right-24: CLI-Dokumentation](guide/cli.md)
+
+- :material-api:{ .lg .middle } __API-Referenz__
+
+ ---
+
+ VollstÀndige technische Dokumentation aller Klassen, Funktionen und Datenmodelle.
+
+ [:octicons-arrow-right-24: API-Referenz](api/index.md)
+
+
+
+---
+
+## Was ist Handelsregister?
+
+**Handelsregister** ist ein Python-Package fĂŒr das gemeinsame Registerportal der deutschen BundeslĂ€nder. Es ermöglicht die programmatische Abfrage des Handelsregisters â sowohl als **Kommandozeilen-Tool** als auch als **Library** in eigenen Anwendungen.
+
+```python
+from handelsregister import search
+
+# Unternehmen suchen
+unternehmen = search("Deutsche Bahn")
+
+for firma in unternehmen:
+ print(f"{firma.name} - {firma.register_num}")
+```
+
+### Funktionsumfang
+
+- :material-magnify: **Unternehmenssuche** â Suche nach Firmennamen, Registernummer oder Ort
+- :material-filter: **Flexible Filter** â Nach Bundesland, Registerart und Status filtern
+- :material-file-document: **Detailabruf** â Erweiterte Unternehmensinformationen abrufen
+- :material-cached: **Intelligentes Caching** â Automatische Zwischenspeicherung von Ergebnissen
+- :material-console: **CLI-Tool** â Kommandozeilen-Interface fĂŒr schnelle Abfragen
+- :material-code-json: **JSON-Export** â Maschinenlesbare Ausgabe fĂŒr Weiterverarbeitung
+
+---
+
+## Schnellbeispiel
+
+=== "Python"
+
+ ```python
+ from handelsregister import search, get_details
+
+ # Suche nach Banken in Berlin und Hamburg
+ banken = search(
+ keywords="Bank",
+ states=["BE", "HH"],
+ register_type="HRB"
+ )
+
+ print(f"Gefunden: {len(banken)} Unternehmen")
+
+ # Details zum ersten Ergebnis abrufen
+ if banken:
+ details = get_details(banken[0])
+ print(f"Firma: {details.name}")
+ print(f"Kapital: {details.capital} {details.currency}")
+ ```
+
+=== "Kommandozeile"
+
+ ```bash
+ # Einfache Suche
+ handelsregister -s "Deutsche Bahn"
+
+ # Mit JSON-Ausgabe
+ handelsregister -s "GASAG AG" --exact --json
+
+ # Mit Filtern
+ handelsregister -s "Bank" --states BE,HH --register-type HRB
+ ```
+
+---
+
+## Installation
+
+Die schnellste Methode zur Installation ist mit [uv](https://docs.astral.sh/uv/):
+
+```bash
+# Klonen und installieren
+git clone https://github.com/bundesAPI/handelsregister.git
+cd handelsregister
+uv sync
+```
+
+Oder direkt mit pip:
+
+```bash
+pip install git+https://github.com/bundesAPI/handelsregister.git
+```
+
+:material-arrow-right: [VollstÀndige Installationsanleitung](installation.md)
+
+---
+
+## Architektur
+
+Das Package besteht aus mehreren Schichten:
+
+```mermaid
+graph TB
+ A[CLI / Anwendung] --> B[Public API]
+ B --> C[HandelsRegister]
+ C --> D[SearchCache]
+ C --> E[ResultParser]
+ C --> F[DetailsParser]
+ D --> G[Dateisystem]
+ C --> H[mechanize Browser]
+ H --> I[handelsregister.de]
+```
+
+| Komponente | Beschreibung |
+|------------|--------------|
+| `search()` | Einfache API fĂŒr Unternehmenssuche |
+| `get_details()` | API fĂŒr Detailabruf |
+| `HandelsRegister` | Browser-Automatisierung |
+| `SearchCache` | TTL-basiertes Caching |
+| `ResultParser` | HTML-Parsing der Suchergebnisse |
+| `DetailsParser` | Parsing der Detailansichten |
+
+---
+
+## Rechtliche Hinweise
+
+!!! warning "NutzungsbeschrÀnkungen"
+
+ Es ist unzulĂ€ssig, mehr als **60 Abrufe pro Stunde** zu tĂ€tigen. Das Registerportal ist das Ziel automatisierter Massenabfragen, deren Frequenz hĂ€ufig die StraftatbestĂ€nde der **§§ 303a, b StGB** erfĂŒllt.
+
+Die Einsichtnahme in das Handelsregister ist gemÀà **§ 9 Abs. 1 HGB** jeder Person zu Informationszwecken gestattet.
+
+:material-arrow-right: [VollstÀndige rechtliche Hinweise](legal.md)
+
+---
+
+## UnterstĂŒtzung
+
+
+
+- :fontawesome-brands-github:{ .lg .middle } __GitHub Issues__
+
+ ---
+
+ Bugs melden und Features anfragen
+
+ [:octicons-arrow-right-24: Issues öffnen](https://github.com/bundesAPI/handelsregister/issues)
+
+- :material-source-pull:{ .lg .middle } __Beitragen__
+
+ ---
+
+ Pull Requests sind willkommen!
+
+ [:octicons-arrow-right-24: Repository](https://github.com/bundesAPI/handelsregister)
+
+
+
+---
+
+## Lizenz
+
+Dieses Projekt ist Teil der [bundesAPI](https://github.com/bundesAPI) Initiative und steht unter der MIT-Lizenz.
+
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..c10b5320
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,191 @@
+# Handelsregister
+
+
+
+- :material-rocket-launch:{ .lg .middle } __Quick Start__
+
+ ---
+
+ Install the package and start querying the German commercial register in minutes.
+
+ [:octicons-arrow-right-24: Installation](installation.md)
+
+- :material-code-braces:{ .lg .middle } __As Library__
+
+ ---
+
+ Integrate commercial register queries into your Python applications with a simple API.
+
+ [:octicons-arrow-right-24: Library Documentation](guide/library.md)
+
+- :material-console:{ .lg .middle } __Command Line__
+
+ ---
+
+ Use the CLI tool for quick queries directly from your terminal.
+
+ [:octicons-arrow-right-24: CLI Documentation](guide/cli.md)
+
+- :material-api:{ .lg .middle } __API Reference__
+
+ ---
+
+ Complete technical documentation of all classes, functions, and data models.
+
+ [:octicons-arrow-right-24: API Reference](api/index.md)
+
+
+
+---
+
+## What is Handelsregister?
+
+**Handelsregister** is a Python package for the shared commercial register portal of the German federal states. It enables programmatic access to the German commercial register â both as a **command-line tool** and as a **library** in your own applications.
+
+```python
+from handelsregister import search
+
+# Search for companies
+companies = search("Deutsche Bahn")
+
+for company in companies:
+ print(f"{company.name} - {company.register_num}")
+```
+
+### Features
+
+- :material-magnify: **Company Search** â Search by company name, register number, or location
+- :material-filter: **Flexible Filters** â Filter by state, register type, and status
+- :material-file-document: **Detail Fetching** â Retrieve extended company information
+- :material-cached: **Intelligent Caching** â Automatic caching of results
+- :material-console: **CLI Tool** â Command-line interface for quick queries
+- :material-code-json: **JSON Export** â Machine-readable output for further processing
+
+---
+
+## Quick Example
+
+=== "Python"
+
+ ```python
+ from handelsregister import search, get_details
+
+ # Search for banks in Berlin and Hamburg
+ banks = search(
+ keywords="Bank",
+ states=["BE", "HH"],
+ register_type="HRB"
+ )
+
+ print(f"Found: {len(banks)} companies")
+
+ # Get details for the first result
+ if banks:
+ details = get_details(banks[0])
+ print(f"Company: {details.name}")
+ print(f"Capital: {details.capital} {details.currency}")
+ ```
+
+=== "Command Line"
+
+ ```bash
+ # Simple search
+ handelsregister -s "Deutsche Bahn"
+
+ # With JSON output
+ handelsregister -s "GASAG AG" --exact --json
+
+ # With filters
+ handelsregister -s "Bank" --states BE,HH --register-type HRB
+ ```
+
+---
+
+## Installation
+
+The fastest installation method is with [uv](https://docs.astral.sh/uv/):
+
+```bash
+# Clone and install
+git clone https://github.com/bundesAPI/handelsregister.git
+cd handelsregister
+uv sync
+```
+
+Or directly with pip:
+
+```bash
+pip install git+https://github.com/bundesAPI/handelsregister.git
+```
+
+:material-arrow-right: [Complete Installation Guide](installation.md)
+
+---
+
+## Architecture
+
+The package consists of several layers:
+
+```mermaid
+graph TB
+ A[CLI / Application] --> B[Public API]
+ B --> C[HandelsRegister]
+ C --> D[SearchCache]
+ C --> E[ResultParser]
+ C --> F[DetailsParser]
+ D --> G[File System]
+ C --> H[mechanize Browser]
+ H --> I[handelsregister.de]
+```
+
+| Component | Description |
+|-----------|-------------|
+| `search()` | Simple API for company search |
+| `get_details()` | API for fetching details |
+| `HandelsRegister` | Browser automation |
+| `SearchCache` | TTL-based caching |
+| `ResultParser` | HTML parsing of search results |
+| `DetailsParser` | Parsing of detail views |
+
+---
+
+## Legal Notice
+
+!!! warning "Usage Restrictions"
+
+ It is not permitted to make more than **60 requests per hour**. The register portal is frequently targeted by automated mass queries, which often constitute criminal offenses under **§§ 303a, b StGB** (German Criminal Code).
+
+Access to the commercial register is permitted for informational purposes according to **§ 9 Abs. 1 HGB** (German Commercial Code).
+
+:material-arrow-right: [Complete Legal Notice](legal.md)
+
+---
+
+## Support
+
+
+
+- :fontawesome-brands-github:{ .lg .middle } __GitHub Issues__
+
+ ---
+
+ Report bugs and request features
+
+ [:octicons-arrow-right-24: Open Issues](https://github.com/bundesAPI/handelsregister/issues)
+
+- :material-source-pull:{ .lg .middle } __Contribute__
+
+ ---
+
+ Pull requests are welcome!
+
+ [:octicons-arrow-right-24: Repository](https://github.com/bundesAPI/handelsregister)
+
+
+
+---
+
+## License
+
+This project is part of the [bundesAPI](https://github.com/bundesAPI) initiative and is licensed under the MIT License.
+
diff --git a/docs/installation.de.md b/docs/installation.de.md
new file mode 100644
index 00000000..fb5ef3e5
--- /dev/null
+++ b/docs/installation.de.md
@@ -0,0 +1,219 @@
+# Installation
+
+Diese Anleitung beschreibt die Installation des Handelsregister-Packages auf verschiedenen Systemen.
+
+## Voraussetzungen
+
+- **Python 3.9** oder höher
+- **pip** oder **uv** als Paketmanager
+
+## Installation mit uv (Empfohlen)
+
+[uv](https://docs.astral.sh/uv/) ist ein moderner, schneller Python-Paketmanager. Er ist die empfohlene Methode zur Installation:
+
+```bash
+# Repository klonen
+git clone https://github.com/bundesAPI/handelsregister.git
+cd handelsregister
+
+# AbhÀngigkeiten installieren
+uv sync
+```
+
+Das Kommandozeilen-Tool ist dann verfĂŒgbar:
+
+```bash
+uv run handelsregister -s "Deutsche Bahn"
+```
+
+### Entwicklungsumgebung
+
+FĂŒr die Entwicklung mit zusĂ€tzlichen Werkzeugen:
+
+```bash
+# Mit EntwicklungsabhÀngigkeiten
+uv sync --extra dev
+
+# Tests ausfĂŒhren
+uv run pytest
+```
+
+### Dokumentation lokal bauen
+
+```bash
+# Mit DokumentationsabhÀngigkeiten
+uv sync --extra docs
+
+# Dokumentation starten
+uv run mkdocs serve
+```
+
+---
+
+## Installation mit pip
+
+### Direkt von GitHub
+
+```bash
+pip install git+https://github.com/bundesAPI/handelsregister.git
+```
+
+### In einem Virtual Environment
+
+```bash
+# Virtual Environment erstellen
+python -m venv venv
+
+# Aktivieren (Linux/macOS)
+source venv/bin/activate
+
+# Aktivieren (Windows)
+venv\Scripts\activate
+
+# Installieren
+pip install git+https://github.com/bundesAPI/handelsregister.git
+```
+
+### Aus lokalem Klon
+
+```bash
+# Repository klonen
+git clone https://github.com/bundesAPI/handelsregister.git
+cd handelsregister
+
+# Als editierbares Package installieren
+pip install -e .
+
+# Mit EntwicklungsabhÀngigkeiten
+pip install -e ".[dev]"
+```
+
+---
+
+## AbhÀngigkeiten
+
+Das Package hat folgende AbhÀngigkeiten:
+
+| Package | Version | Beschreibung |
+|---------|---------|--------------|
+| `mechanize` | â„0.4.8 | Browser-Automatisierung |
+| `beautifulsoup4` | â„4.11.0 | HTML-Parsing |
+
+### Optionale AbhÀngigkeiten
+
+=== "Entwicklung"
+
+ ```
+ black>=22.6.0 # Code-Formatierung
+ pytest>=7.0.0 # Testing
+ ```
+
+=== "Dokumentation"
+
+ ```
+ mkdocs>=1.5.0 # Dokumentationsgenerator
+ mkdocs-material>=9.5.0 # Material Theme
+ mkdocstrings[python]>=0.24.0 # API-Dokumentation
+ mkdocs-static-i18n>=1.2.0 # Internationalisierung
+ ```
+
+---
+
+## Systemspezifische Hinweise
+
+### macOS
+
+Auf macOS ist Python 3 in der Regel vorinstalliert. Falls nicht:
+
+```bash
+# Mit Homebrew
+brew install python
+
+# uv installieren
+curl -LsSf https://astral.sh/uv/install.sh | sh
+```
+
+### Linux (Debian/Ubuntu)
+
+```bash
+# Python und pip installieren
+sudo apt update
+sudo apt install python3 python3-pip python3-venv
+
+# uv installieren
+curl -LsSf https://astral.sh/uv/install.sh | sh
+```
+
+### Windows
+
+1. Python von [python.org](https://www.python.org/downloads/) herunterladen
+2. Bei der Installation "Add Python to PATH" aktivieren
+3. uv installieren:
+
+```powershell
+powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
+```
+
+---
+
+## ĂberprĂŒfung der Installation
+
+Nach der Installation können Sie die Installation ĂŒberprĂŒfen:
+
+=== "Als Modul"
+
+ ```bash
+ python -c "import handelsregister; print('Installation erfolgreich!')"
+ ```
+
+=== "CLI"
+
+ ```bash
+ handelsregister --help
+ ```
+
+=== "Mit uv"
+
+ ```bash
+ uv run python -c "import handelsregister; print('Installation erfolgreich!')"
+ ```
+
+---
+
+## HĂ€ufige Probleme
+
+### ModuleNotFoundError: No module named 'mechanize'
+
+Die AbhĂ€ngigkeiten wurden nicht korrekt installiert. FĂŒhren Sie erneut aus:
+
+```bash
+pip install mechanize beautifulsoup4
+```
+
+### SSL-Zertifikatsfehler
+
+Auf manchen Systemen gibt es Probleme mit SSL-Zertifikaten:
+
+```bash
+# macOS: Zertifikate installieren
+/Applications/Python\ 3.x/Install\ Certificates.command
+```
+
+### Permission Denied bei globaler Installation
+
+Verwenden Sie `--user` oder ein Virtual Environment:
+
+```bash
+pip install --user git+https://github.com/bundesAPI/handelsregister.git
+```
+
+---
+
+## NĂ€chste Schritte
+
+Nach erfolgreicher Installation:
+
+- :material-rocket-launch: [Schnellstart](quickstart.md) â Erste Schritte mit dem Package
+- :material-code-braces: [Library-Dokumentation](guide/library.md) â Integration in Python-Anwendungen
+- :material-console: [CLI-Dokumentation](guide/cli.md) â Nutzung der Kommandozeile
+
diff --git a/docs/installation.md b/docs/installation.md
new file mode 100644
index 00000000..186e1436
--- /dev/null
+++ b/docs/installation.md
@@ -0,0 +1,219 @@
+# Installation
+
+This guide describes how to install the Handelsregister package on various systems.
+
+## Requirements
+
+- **Python 3.9** or higher
+- **pip** or **uv** as package manager
+
+## Installation with uv (Recommended)
+
+[uv](https://docs.astral.sh/uv/) is a modern, fast Python package manager. It's the recommended installation method:
+
+```bash
+# Clone the repository
+git clone https://github.com/bundesAPI/handelsregister.git
+cd handelsregister
+
+# Install dependencies
+uv sync
+```
+
+The command-line tool is then available:
+
+```bash
+uv run handelsregister -s "Deutsche Bahn"
+```
+
+### Development Environment
+
+For development with additional tools:
+
+```bash
+# With development dependencies
+uv sync --extra dev
+
+# Run tests
+uv run pytest
+```
+
+### Build Documentation Locally
+
+```bash
+# With documentation dependencies
+uv sync --extra docs
+
+# Start documentation server
+uv run mkdocs serve
+```
+
+---
+
+## Installation with pip
+
+### Directly from GitHub
+
+```bash
+pip install git+https://github.com/bundesAPI/handelsregister.git
+```
+
+### In a Virtual Environment
+
+```bash
+# Create virtual environment
+python -m venv venv
+
+# Activate (Linux/macOS)
+source venv/bin/activate
+
+# Activate (Windows)
+venv\Scripts\activate
+
+# Install
+pip install git+https://github.com/bundesAPI/handelsregister.git
+```
+
+### From Local Clone
+
+```bash
+# Clone repository
+git clone https://github.com/bundesAPI/handelsregister.git
+cd handelsregister
+
+# Install as editable package
+pip install -e .
+
+# With development dependencies
+pip install -e ".[dev]"
+```
+
+---
+
+## Dependencies
+
+The package has the following dependencies:
+
+| Package | Version | Description |
+|---------|---------|-------------|
+| `mechanize` | â„0.4.8 | Browser automation |
+| `beautifulsoup4` | â„4.11.0 | HTML parsing |
+
+### Optional Dependencies
+
+=== "Development"
+
+ ```
+ black>=22.6.0 # Code formatting
+ pytest>=7.0.0 # Testing
+ ```
+
+=== "Documentation"
+
+ ```
+ mkdocs>=1.5.0 # Documentation generator
+ mkdocs-material>=9.5.0 # Material theme
+ mkdocstrings[python]>=0.24.0 # API documentation
+ mkdocs-static-i18n>=1.2.0 # Internationalization
+ ```
+
+---
+
+## System-Specific Notes
+
+### macOS
+
+Python 3 is usually pre-installed on macOS. If not:
+
+```bash
+# With Homebrew
+brew install python
+
+# Install uv
+curl -LsSf https://astral.sh/uv/install.sh | sh
+```
+
+### Linux (Debian/Ubuntu)
+
+```bash
+# Install Python and pip
+sudo apt update
+sudo apt install python3 python3-pip python3-venv
+
+# Install uv
+curl -LsSf https://astral.sh/uv/install.sh | sh
+```
+
+### Windows
+
+1. Download Python from [python.org](https://www.python.org/downloads/)
+2. Enable "Add Python to PATH" during installation
+3. Install uv:
+
+```powershell
+powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
+```
+
+---
+
+## Verify Installation
+
+After installation, you can verify it:
+
+=== "As Module"
+
+ ```bash
+ python -c "import handelsregister; print('Installation successful!')"
+ ```
+
+=== "CLI"
+
+ ```bash
+ handelsregister --help
+ ```
+
+=== "With uv"
+
+ ```bash
+ uv run python -c "import handelsregister; print('Installation successful!')"
+ ```
+
+---
+
+## Common Issues
+
+### ModuleNotFoundError: No module named 'mechanize'
+
+Dependencies were not installed correctly. Run again:
+
+```bash
+pip install mechanize beautifulsoup4
+```
+
+### SSL Certificate Errors
+
+Some systems have issues with SSL certificates:
+
+```bash
+# macOS: Install certificates
+/Applications/Python\ 3.x/Install\ Certificates.command
+```
+
+### Permission Denied on Global Installation
+
+Use `--user` or a virtual environment:
+
+```bash
+pip install --user git+https://github.com/bundesAPI/handelsregister.git
+```
+
+---
+
+## Next Steps
+
+After successful installation:
+
+- :material-rocket-launch: [Quickstart](quickstart.md) â First steps with the package
+- :material-code-braces: [Library Documentation](guide/library.md) â Integration into Python applications
+- :material-console: [CLI Documentation](guide/cli.md) â Using the command line
+
diff --git a/docs/legal.de.md b/docs/legal.de.md
new file mode 100644
index 00000000..05b62e36
--- /dev/null
+++ b/docs/legal.de.md
@@ -0,0 +1,156 @@
+# Rechtliche Hinweise
+
+Diese Seite enthÀlt wichtige rechtliche Informationen zur Nutzung des Handelsregister-Packages.
+
+## NutzungsbeschrÀnkungen
+
+!!! danger "Wichtig: Rate-Limits"
+
+ Es ist **unzulÀssig**, mehr als **60 Abrufe pro Stunde** beim Handelsregisterportal zu tÀtigen.
+
+ Das Registerportal ist das Ziel automatisierter Massenabfragen, deren Frequenz hĂ€ufig die StraftatbestĂ€nde der **§§ 303a, b StGB** (Computersabotage) erfĂŒllt.
+
+## Rechtsgrundlage
+
+### Einsichtnahme (§ 9 Abs. 1 HGB)
+
+GemÀà **§ 9 Abs. 1 HGB** (Handelsgesetzbuch) ist die Einsichtnahme in das Handelsregister jeder Person zu Informationszwecken gestattet.
+
+> "Die Einsichtnahme in das Handelsregister sowie in die zum Handelsregister eingereichten Dokumente ist jedem zu Informationszwecken gestattet."
+
+### Datenschutz (DSGVO)
+
+Die aus dem Handelsregister erhaltenen Daten können personenbezogene Daten enthalten. Bei der Verwendung dieser Daten mĂŒssen Sie die **Datenschutz-Grundverordnung (DSGVO)** und das **Bundesdatenschutzgesetz (BDSG)** einhalten.
+
+Insbesondere:
+
+- Personenbezogene Daten dĂŒrfen nur fĂŒr legitime Zwecke verarbeitet werden
+- GrundsÀtze der Datenminimierung gelten
+- Betroffene haben Rechte bezĂŒglich ihrer Daten
+
+---
+
+## Nutzungsbedingungen von handelsregister.de
+
+Das Handelsregisterportal (handelsregister.de) hat eigene Nutzungsbedingungen, die zu beachten sind:
+
+1. **Keine Massenabfragen** - Automatisierter Abruf groĂer Datenmengen ist untersagt
+2. **Kein kommerzieller Weiterverkauf** - Daten dĂŒrfen nicht ohne Genehmigung kommerziell weiterverkauft oder verbreitet werden
+3. **Persönliche Haftung** - Nutzer haften persönlich fĂŒr ihre Nutzung des Portals
+
+---
+
+## Haftungsausschluss
+
+### Keine Garantie
+
+Dieses Package wird "wie besehen" ohne jegliche Garantie bereitgestellt. Die Autoren und Mitwirkenden:
+
+- Garantieren nicht die Richtigkeit, VollstÀndigkeit oder AktualitÀt der Daten
+- Haften nicht fĂŒr SchĂ€den, die aus der Nutzung dieses Packages entstehen
+- Garantieren nicht die VerfĂŒgbarkeit oder FunktionsfĂ€higkeit des Packages
+
+### Nutzerverantwortung
+
+Nutzer dieses Packages sind verantwortlich fĂŒr:
+
+- Einhaltung der Rate-Limits
+- Beachtung geltender Gesetze und Vorschriften
+- OrdnungsgemĂ€Ăen Umgang mit personenbezogenen Daten
+- Alle Konsequenzen ihrer Nutzung
+
+---
+
+## Verbotene Nutzungen
+
+Dieses Package darf **nicht** verwendet werden fĂŒr:
+
+1. **Massenhafte Datensammlung** - Systematische Erfassung aller oder groĂer Teile der Registerdaten
+2. **Denial of Service** - Aktionen, die die VerfĂŒgbarkeit des Portals beeintrĂ€chtigen könnten
+3. **Kommerzieller Datenweiterverkauf** - Verkauf von Registerdaten ohne Genehmigung
+4. **Stalking oder BelĂ€stigung** - Nutzung von Unternehmens- oder Personendaten fĂŒr schĂ€dliche Zwecke
+5. **Betrug** - Jegliche betrĂŒgerische oder tĂ€uschende Zwecke
+
+---
+
+## Best Practices
+
+FĂŒr eine verantwortungsvolle Nutzung dieses Packages:
+
+### Rate-Limits respektieren
+
+```python
+import time
+
+# Zwischen Anfragen warten
+for suchbegriff in suchbegriffe:
+ ergebnisse = search(suchbegriff)
+ time.sleep(60) # Max 60 Anfragen pro Stunde
+```
+
+### Caching nutzen
+
+```python
+# Caching ist standardmĂ€Ăig aktiviert
+ergebnisse = search("Bank") # Einmal abgerufen, 24h gecacht
+```
+
+### Anfragen minimieren
+
+```python
+# Gut: Server-seitig filtern
+firmen = search("Bank", states=["BE"], register_type="HRB")
+
+# Schlecht: Alles abrufen, lokal filtern
+alle_firmen = search("Bank") # Viel gröĂere Antwort
+gefiltert = [f for f in alle_firmen if f['state'] == 'BE']
+```
+
+---
+
+## Probleme melden
+
+Wenn Sie Probleme mit diesem Package entdecken, die zu Missbrauch fĂŒhren könnten, melden Sie diese bitte verantwortungsvoll:
+
+1. Ăffnen Sie ein GitHub-Issue (fĂŒr nicht-sicherheitsrelevante Probleme)
+2. Kontaktieren Sie die Maintainer direkt (fĂŒr Sicherheitsprobleme)
+
+---
+
+## Lizenz
+
+Dieses Package ist unter der **MIT-Lizenz** lizenziert.
+
+```
+MIT License
+
+Copyright (c) 2024 BundesAPI
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
+
+---
+
+## Kontakt
+
+FĂŒr Fragen zu rechtlichen Angelegenheiten:
+
+- **GitHub Issues**: [github.com/bundesAPI/handelsregister/issues](https://github.com/bundesAPI/handelsregister/issues)
+- **BundesAPI**: [github.com/bundesAPI](https://github.com/bundesAPI)
+
diff --git a/docs/legal.md b/docs/legal.md
new file mode 100644
index 00000000..cfa90d33
--- /dev/null
+++ b/docs/legal.md
@@ -0,0 +1,158 @@
+# Legal Notice
+
+This page contains important legal information regarding the use of the Handelsregister package.
+
+## Usage Restrictions
+
+!!! danger "Important: Rate Limits"
+
+ It is **not permitted** to make more than **60 requests per hour** to the commercial register portal.
+
+ The register portal is frequently targeted by automated mass queries, which often constitute criminal offenses under **§§ 303a, b StGB** (German Criminal Code - Computer Sabotage).
+
+## Legal Basis
+
+### Access Rights (§ 9 Abs. 1 HGB)
+
+According to **§ 9 Abs. 1 HGB** (German Commercial Code), access to the commercial register is permitted to any person for informational purposes.
+
+> "Die Einsichtnahme in das Handelsregister sowie in die zum Handelsregister eingereichten Dokumente ist jedem zu Informationszwecken gestattet."
+>
+> *Translation: Access to the commercial register and documents filed with it is permitted to everyone for informational purposes.*
+
+### Data Protection (GDPR)
+
+The data obtained from the commercial register may contain personal data. When using this data, you must comply with the **General Data Protection Regulation (GDPR)** and the **Bundesdatenschutzgesetz (BDSG)**.
+
+In particular:
+
+- Personal data may only be processed for legitimate purposes
+- Data minimization principles apply
+- Data subjects have rights regarding their data
+
+---
+
+## Terms of Use of handelsregister.de
+
+The commercial register portal (handelsregister.de) has its own terms of use that must be observed:
+
+1. **No mass queries** - Automated retrieval of large amounts of data is prohibited
+2. **No commercial redistribution** - Data may not be resold or redistributed commercially without authorization
+3. **Personal liability** - Users are personally responsible for their use of the portal
+
+---
+
+## Liability Disclaimer
+
+### No Warranty
+
+This package is provided "as is" without warranty of any kind. The authors and contributors:
+
+- Do not guarantee the accuracy, completeness, or timeliness of data
+- Are not liable for any damages resulting from use of this package
+- Do not guarantee the availability or functionality of the package
+
+### User Responsibility
+
+Users of this package are responsible for:
+
+- Complying with rate limits
+- Observing applicable laws and regulations
+- Proper handling of personal data
+- Any consequences of their use
+
+---
+
+## Prohibited Uses
+
+This package may **not** be used for:
+
+1. **Mass data harvesting** - Systematic collection of all or large portions of register data
+2. **Denial of Service** - Actions that could impair the portal's availability
+3. **Commercial data resale** - Selling register data without authorization
+4. **Stalking or harassment** - Using company or person data for harmful purposes
+5. **Fraud** - Any fraudulent or deceptive purposes
+
+---
+
+## Best Practices
+
+To use this package responsibly:
+
+### Respect Rate Limits
+
+```python
+import time
+
+# Wait between requests
+for keyword in keywords:
+ results = search(keyword)
+ time.sleep(60) # Max 60 requests per hour
+```
+
+### Use Caching
+
+```python
+# Caching is enabled by default
+results = search("Bank") # Fetched once, cached for 24h
+```
+
+### Minimize Requests
+
+```python
+# Good: Use server-side filtering
+companies = search("Bank", states=["BE"], register_type="HRB")
+
+# Bad: Fetch everything, filter locally
+all_companies = search("Bank") # Much larger response
+filtered = [c for c in all_companies if c['state'] == 'BE']
+```
+
+---
+
+## Reporting Issues
+
+If you discover any issues with this package that could lead to misuse, please report them responsibly:
+
+1. Open a GitHub issue (for non-security issues)
+2. Contact the maintainers directly (for security issues)
+
+---
+
+## License
+
+This package is licensed under the **MIT License**.
+
+```
+MIT License
+
+Copyright (c) 2024 BundesAPI
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
+
+---
+
+## Contact
+
+For questions regarding legal matters:
+
+- **GitHub Issues**: [github.com/bundesAPI/handelsregister/issues](https://github.com/bundesAPI/handelsregister/issues)
+- **BundesAPI**: [github.com/bundesAPI](https://github.com/bundesAPI)
+
diff --git a/docs/quickstart.de.md b/docs/quickstart.de.md
new file mode 100644
index 00000000..0c2458ae
--- /dev/null
+++ b/docs/quickstart.de.md
@@ -0,0 +1,190 @@
+# Schnellstart
+
+Starten Sie mit Handelsregister in nur wenigen Minuten.
+
+## Installation
+
+```bash
+git clone https://github.com/bundesAPI/handelsregister.git
+cd handelsregister
+uv sync
+```
+
+---
+
+## Erste Suche
+
+### Mit Python
+
+```python
+from handelsregister import search
+
+# Unternehmen suchen
+ergebnisse = search("Deutsche Bahn")
+
+# Ergebnisse ausgeben
+for firma in ergebnisse:
+ print(f"{firma.name}")
+ print(f" Register: {firma.court} {firma.register_num}")
+ print(f" Status: {firma.status}")
+ print()
+```
+
+**Ausgabe:**
+
+```
+Deutsche Bahn Aktiengesellschaft
+ Register: Berlin (Charlottenburg) HRB 50000
+ Status: aktuell eingetragen
+
+DB Fernverkehr Aktiengesellschaft
+ Register: Frankfurt am Main HRB 12345
+ Status: aktuell eingetragen
+...
+```
+
+### Mit CLI
+
+```bash
+# Suche nach "Deutsche Bahn"
+handelsregister -s "Deutsche Bahn"
+# Oder die kĂŒrzere Variante verwenden:
+hrg -s "Deutsche Bahn"
+
+# Als JSON-Ausgabe
+handelsregister -s "Deutsche Bahn" --json
+```
+
+---
+
+## Ergebnisse filtern
+
+### Nach Bundesland
+
+```python
+from handelsregister import search
+
+# Nur Berliner Unternehmen
+ergebnisse = search("Bank", states=["BE"])
+```
+
+```bash
+# CLI: Nur Berlin
+handelsregister -s "Bank" --states BE
+```
+
+### Nach Registerart
+
+```python
+# Nur Kapitalgesellschaften (HRB)
+ergebnisse = search("GmbH", register_type="HRB")
+```
+
+```bash
+# CLI
+handelsregister -s "GmbH" --register-type HRB
+```
+
+### Kombinierte Filter
+
+```python
+# Banken in Berlin oder Hamburg, nur HRB
+ergebnisse = search(
+ keywords="Bank",
+ states=["BE", "HH"],
+ register_type="HRB",
+ include_deleted=False
+)
+```
+
+---
+
+## Details abrufen
+
+```python
+from handelsregister import search, get_details
+
+# Suchen
+firmen = search("GASAG AG", keyword_option="exact")
+
+if firmen:
+ # Detailinformationen abrufen
+ details = get_details(firmen[0])
+
+ print(f"Firma: {details.name}")
+ print(f"Kapital: {details.capital} {details.currency}")
+ print(f"Adresse: {details.address}")
+
+ print("Vertreter:")
+ for vertreter in details.representatives:
+ print(f" - {vertreter.name} ({vertreter.role})")
+```
+
+---
+
+## Caching
+
+Das Package cached Ergebnisse automatisch:
+
+```python
+from handelsregister import search
+
+# Erste Suche: Fragt das Registerportal an
+ergebnisse1 = search("Deutsche Bank")
+
+# Zweite Suche: Nutzt Cache (schneller)
+ergebnisse2 = search("Deutsche Bank")
+
+# Frische Suche erzwingen (Cache umgehen)
+ergebnisse3 = search("Deutsche Bank", force_refresh=True)
+```
+
+Standard Cache-Dauer: **24 Stunden**
+
+---
+
+## Fehlerbehandlung
+
+```python
+from handelsregister import search, SearchError, RateLimitError
+
+try:
+ ergebnisse = search("Deutsche Bahn")
+except RateLimitError:
+ print("Zu viele Anfragen! Maximal 60 pro Stunde erlaubt.")
+except SearchError as e:
+ print(f"Suchfehler: {e}")
+```
+
+---
+
+## NĂ€chste Schritte
+
+
+
+- :material-book-open-variant:{ .lg .middle } __Benutzerhandbuch__
+
+ ---
+
+ Detaillierte Dokumentation aller Funktionen
+
+ [:octicons-arrow-right-24: Benutzerhandbuch](guide/index.md)
+
+- :material-api:{ .lg .middle } __API-Referenz__
+
+ ---
+
+ VollstÀndige technische Referenz
+
+ [:octicons-arrow-right-24: API-Referenz](api/index.md)
+
+- :material-code-braces:{ .lg .middle } __Beispiele__
+
+ ---
+
+ Praktische Code-Beispiele
+
+ [:octicons-arrow-right-24: Beispiele](examples/simple.md)
+
+
+
diff --git a/docs/quickstart.md b/docs/quickstart.md
new file mode 100644
index 00000000..81c7e1ed
--- /dev/null
+++ b/docs/quickstart.md
@@ -0,0 +1,213 @@
+# Quickstart
+
+Get started with Handelsregister in just a few minutes.
+
+## Installation
+
+```bash
+git clone https://github.com/bundesAPI/handelsregister.git
+cd handelsregister
+uv sync
+```
+
+---
+
+## First Search
+
+### Using Python
+
+```python
+from handelsregister import search
+
+# Search for companies
+results = search("Deutsche Bahn")
+
+# Display results
+for company in results:
+ print(f"{company.name}")
+ print(f" Register: {company.court} {company.register_num}")
+ print(f" Status: {company.status}")
+ print()
+```
+
+**Output:**
+
+```
+Deutsche Bahn Aktiengesellschaft
+ Register: Berlin (Charlottenburg) HRB 50000
+ Status: currently registered
+
+DB Fernverkehr Aktiengesellschaft
+ Register: Frankfurt am Main HRB 12345
+ Status: currently registered
+...
+```
+
+### Using CLI
+
+```bash
+# Search for "Deutsche Bahn"
+handelsregister -s "Deutsche Bahn"
+# Or use the shorter alias:
+hrg -s "Deutsche Bahn"
+
+# As JSON output
+handelsregister -s "Deutsche Bahn" --json
+```
+
+---
+
+## Filtering Results
+
+### By State
+
+```python
+from handelsregister import search, State
+
+# Only Berlin companies (recommended: Enum for autocomplete)
+results = search("Bank", states=[State.BE])
+
+# String-based API still works
+results = search("Bank", states=["BE"])
+```
+
+```bash
+# CLI: Berlin only
+handelsregister -s "Bank" --states BE
+```
+
+### By Register Type
+
+```python
+from handelsregister import search, RegisterType
+
+# Only corporations (HRB) - recommended: Enum
+results = search("GmbH", register_type=RegisterType.HRB)
+
+# String-based API still works
+results = search("GmbH", register_type="HRB")
+```
+
+```bash
+# CLI
+handelsregister -s "GmbH" --register-type HRB
+```
+
+### Combined Filters
+
+```python
+from handelsregister import search, State, RegisterType
+
+# Banks in Berlin or Hamburg, only HRB, exclude deleted entries
+# Recommended: Enums for better IDE support
+results = search(
+ keywords="Bank",
+ states=[State.BE, State.HH],
+ register_type=RegisterType.HRB,
+ include_deleted=False
+)
+
+# String-based API still works
+results = search(
+ keywords="Bank",
+ states=["BE", "HH"],
+ register_type="HRB",
+ include_deleted=False
+)
+```
+
+---
+
+## Fetching Details
+
+```python
+from handelsregister import search, get_details, KeywordMatch
+
+# Search (recommended: Enum for autocomplete)
+companies = search("GASAG AG", keyword_option=KeywordMatch.EXACT)
+
+# String-based API still works
+companies = search("GASAG AG", keyword_option="exact")
+
+if companies:
+ # Fetch detailed information
+ details = get_details(companies[0])
+
+ print(f"Company: {details.name}")
+ print(f"Capital: {details.capital} {details.currency}")
+ print(f"Address: {details.address}")
+
+ print("Representatives:")
+ for rep in details.representatives:
+ print(f" - {rep.name} ({rep.role})")
+```
+
+---
+
+## Caching
+
+The package automatically caches results:
+
+```python
+from handelsregister import search
+
+# First search: requests the register portal
+results1 = search("Deutsche Bank")
+
+# Second search: uses cache (faster)
+results2 = search("Deutsche Bank")
+
+# Force fresh search (bypass cache)
+results3 = search("Deutsche Bank", force_refresh=True)
+```
+
+Default cache duration: **24 hours**
+
+---
+
+## Error Handling
+
+```python
+from handelsregister import search, NetworkError, FormError, ParseError
+
+try:
+ results = search("Deutsche Bahn")
+except NetworkError as e:
+ print(f"Network error: {e}")
+except FormError as e:
+ print(f"Form error: {e}")
+except ParseError as e:
+ print(f"Parse error: {e}")
+```
+
+---
+
+## Next Steps
+
+
+
+- :material-book-open-variant:{ .lg .middle } __User Guide__
+
+ ---
+
+ Detailed documentation for all features
+
+ [:octicons-arrow-right-24: User Guide](guide/index.md)
+
+- :material-api:{ .lg .middle } __API Reference__
+
+ ---
+
+ Complete technical reference
+
+ [:octicons-arrow-right-24: API Reference](api/index.md)
+
+- :material-code-braces:{ .lg .middle } __Examples__
+
+ ---
+
+ Practical code examples
+
+ [:octicons-arrow-right-24: Examples](examples/simple.md)
+
+
diff --git a/docs/reference/legal-forms.de.md b/docs/reference/legal-forms.de.md
new file mode 100644
index 00000000..622626fa
--- /dev/null
+++ b/docs/reference/legal-forms.de.md
@@ -0,0 +1,154 @@
+# Rechtsformen
+
+Diese Tabelle zeigt die gÀngigen deutschen Rechtsformen und ihre Eigenschaften.
+
+## Rechtsformen-Referenz
+
+### Kapitalgesellschaften (HRB)
+
+| AbkĂŒrzung | Name | Mindestkapital |
+|-----------|------|----------------|
+| GmbH | Gesellschaft mit beschrÀnkter Haftung | 25.000 ⏠|
+| UG | Unternehmergesellschaft (haftungsbeschrÀnkt) | 1 ⏠|
+| AG | Aktiengesellschaft | 50.000 ⏠|
+| SE | Societas Europaea | 120.000 ⏠|
+| KGaA | Kommanditgesellschaft auf Aktien | 50.000 ⏠|
+
+### Personengesellschaften (HRA)
+
+| AbkĂŒrzung | Name | Haftung |
+|-----------|------|---------|
+| e.K. | Eingetragener Kaufmann | Persönlich |
+| OHG | Offene Handelsgesellschaft | Persönlich |
+| KG | Kommanditgesellschaft | Gemischt |
+| GmbH & Co. KG | GmbH & Co. Kommanditgesellschaft | BeschrÀnkt |
+
+### Weitere Formen
+
+| AbkĂŒrzung | Name | Register |
+|-----------|------|----------|
+| eG | Eingetragene Genossenschaft | GnR |
+| PartG | Partnerschaftsgesellschaft | PR |
+| PartG mbB | Partnerschaftsgesellschaft mbB | PR |
+| e.V. | Eingetragener Verein | VR |
+
+---
+
+## Detaillierte Beschreibungen
+
+### GmbH (Gesellschaft mit beschrÀnkter Haftung)
+
+Die hÀufigste Kapitalgesellschaftsform in Deutschland.
+
+- **Mindestkapital:** 25.000 âŹ
+- **Haftung:** Auf Gesellschaftsvermögen beschrÀnkt
+- **FĂŒhrung:** GeschĂ€ftsfĂŒhrer
+- **Anteile:** GeschÀftsanteile (nicht börsenhandelbar)
+
+```python
+# Suche nach GmbHs
+firmen = search("Consulting GmbH", register_type="HRB")
+```
+
+### UG (Unternehmergesellschaft)
+
+Eine Sondervariante der GmbH fĂŒr GrĂŒnder mit wenig Kapital.
+
+- **Mindestkapital:** 1 âŹ
+- **Thesaurierungspflicht:** 25% des Jahresgewinns bis 25.000 ⏠erreicht
+- **Danach:** Umwandlung in regulÀre GmbH möglich
+
+```python
+firmen = search("UG", register_type="HRB")
+```
+
+### AG (Aktiengesellschaft)
+
+FĂŒr gröĂere Unternehmen, besonders solche, die Börsenkapital suchen.
+
+- **Mindestkapital:** 50.000 âŹ
+- **FĂŒhrung:** Vorstand + Aufsichtsrat
+- **Anteile:** Aktien (können börsengehandelt werden)
+
+```python
+firmen = search("AG", register_type="HRB")
+```
+
+### KG (Kommanditgesellschaft)
+
+Personengesellschaft mit persönlich haftenden und beschrÀnkt haftenden Gesellschaftern.
+
+- **KomplementÀr:** Persönlich haftender Gesellschafter
+- **Kommanditist:** BeschrÀnkt haftender Gesellschafter
+
+```python
+firmen = search("KG", register_type="HRA")
+```
+
+### GmbH & Co. KG
+
+Besondere Kommanditgesellschaft, bei der der KomplementÀr eine GmbH ist.
+
+- Kombiniert beschrÀnkte Haftung mit Personengesellschafts-Besteuerung
+- Sehr verbreitet in Deutschland
+
+```python
+firmen = search("GmbH & Co. KG", register_type="HRA")
+```
+
+---
+
+## Suchmuster
+
+### Nach Rechtsform
+
+```python
+from handelsregister import search
+
+# Nur GmbHs
+gmbhs = search("Suchbegriff GmbH", register_type="HRB")
+
+# Nur AGs
+ags = search("Suchbegriff AG", register_type="HRB")
+
+# Alle Kommanditgesellschaften
+kgs = search("KG", register_type="HRA")
+```
+
+### Mehrere Formen
+
+```python
+# Breit suchen, dann filtern
+alle_firmen = search("Mustermann")
+
+# Nach Suffix filtern
+gmbhs = [f for f in alle_firmen if "GmbH" in f['name']]
+ags = [f for f in alle_firmen if f['name'].endswith(" AG")]
+```
+
+---
+
+## Rechtsform-Statistiken
+
+UngefÀhre Anzahl aktiver Unternehmen in Deutschland:
+
+| Rechtsform | Anzahl | Anteil |
+|------------|--------|--------|
+| GmbH | ~1.200.000 | 48% |
+| UG | ~150.000 | 6% |
+| GmbH & Co. KG | ~200.000 | 8% |
+| KG | ~50.000 | 2% |
+| AG | ~15.000 | <1% |
+| e.K. | ~450.000 | 18% |
+| OHG | ~10.000 | <1% |
+| eG | ~20.000 | <1% |
+| e.V. | ~600.000 | - |
+
+---
+
+## Siehe auch
+
+- [Registerarten](registers.md) â HRA, HRB, etc.
+- [BundeslĂ€nder-Codes](states.md) â Deutsche BundeslĂ€nder-Codes
+- [API-Parameter](parameters.md) â Alle Suchparameter
+
diff --git a/docs/reference/legal-forms.md b/docs/reference/legal-forms.md
new file mode 100644
index 00000000..fdfe017a
--- /dev/null
+++ b/docs/reference/legal-forms.md
@@ -0,0 +1,154 @@
+# Legal Forms
+
+This table shows the common German legal forms (Rechtsformen) and their characteristics.
+
+## Legal Form Reference
+
+### Corporations (HRB)
+
+| Abbreviation | German Name | English Name | Min. Capital |
+|--------------|-------------|--------------|--------------|
+| GmbH | Gesellschaft mit beschrĂ€nkter Haftung | Limited liability company | âŹ25,000 |
+| UG | Unternehmergesellschaft (haftungsbeschrĂ€nkt) | Mini-GmbH | âŹ1 |
+| AG | Aktiengesellschaft | Stock corporation | âŹ50,000 |
+| SE | Societas Europaea | European company | âŹ120,000 |
+| KGaA | Kommanditgesellschaft auf Aktien | Partnership limited by shares | âŹ50,000 |
+
+### Partnerships (HRA)
+
+| Abbreviation | German Name | English Name | Liability |
+|--------------|-------------|--------------|-----------|
+| e.K. | Eingetragener Kaufmann | Sole proprietor | Personal |
+| OHG | Offene Handelsgesellschaft | General partnership | Personal |
+| KG | Kommanditgesellschaft | Limited partnership | Mixed |
+| GmbH & Co. KG | GmbH & Co. Kommanditgesellschaft | Special limited partnership | Limited |
+
+### Other Forms
+
+| Abbreviation | German Name | English Name | Register |
+|--------------|-------------|--------------|----------|
+| eG | Eingetragene Genossenschaft | Cooperative | GnR |
+| PartG | Partnerschaftsgesellschaft | Professional partnership | PR |
+| PartG mbB | Partnerschaftsgesellschaft mbB | Professional partnership (limited) | PR |
+| e.V. | Eingetragener Verein | Registered association | VR |
+
+---
+
+## Detailed Descriptions
+
+### GmbH (Limited Liability Company)
+
+The most common corporate form in Germany.
+
+- **Minimum capital:** âŹ25,000
+- **Liability:** Limited to company assets
+- **Governance:** GeschĂ€ftsfĂŒhrer (managing directors)
+- **Shares:** Anteile (not publicly tradable)
+
+```python
+# Search for GmbHs
+companies = search("Consulting GmbH", register_type="HRB")
+```
+
+### UG (Mini-GmbH)
+
+A special variant of the GmbH for founders with little capital.
+
+- **Minimum capital:** âŹ1
+- **Must retain:** 25% of annual profit until âŹ25,000 reached
+- **Then:** Can convert to regular GmbH
+
+```python
+companies = search("UG", register_type="HRB")
+```
+
+### AG (Stock Corporation)
+
+Used for larger companies, especially those seeking public capital.
+
+- **Minimum capital:** âŹ50,000
+- **Governance:** Vorstand (board) + Aufsichtsrat (supervisory board)
+- **Shares:** Aktien (can be publicly traded)
+
+```python
+companies = search("AG", register_type="HRB")
+```
+
+### KG (Limited Partnership)
+
+Partnership with general and limited partners.
+
+- **KomplementÀr:** General partner (unlimited liability)
+- **Kommanditist:** Limited partner (liability limited to contribution)
+
+```python
+companies = search("KG", register_type="HRA")
+```
+
+### GmbH & Co. KG
+
+Special limited partnership where the general partner is a GmbH.
+
+- Combines limited liability with partnership taxation
+- Very common in Germany
+
+```python
+companies = search("GmbH & Co. KG", register_type="HRA")
+```
+
+---
+
+## Search Patterns
+
+### By Legal Form
+
+```python
+from handelsregister import search
+
+# GmbHs only
+gmbhs = search("keyword GmbH", register_type="HRB")
+
+# AGs only
+ags = search("keyword AG", register_type="HRB")
+
+# All limited partnerships
+kgs = search("KG", register_type="HRA")
+```
+
+### Multiple Forms
+
+```python
+# Search broadly, then filter
+all_companies = search("Mustermann")
+
+# Filter by suffix
+gmbhs = [c for c in all_companies if "GmbH" in c['name']]
+ags = [c for c in all_companies if c['name'].endswith(" AG")]
+```
+
+---
+
+## Legal Form Statistics
+
+Approximate number of active companies in Germany:
+
+| Legal Form | Count | Percentage |
+|------------|-------|------------|
+| GmbH | ~1,200,000 | 48% |
+| UG | ~150,000 | 6% |
+| GmbH & Co. KG | ~200,000 | 8% |
+| KG | ~50,000 | 2% |
+| AG | ~15,000 | <1% |
+| e.K. | ~450,000 | 18% |
+| OHG | ~10,000 | <1% |
+| eG | ~20,000 | <1% |
+| e.V. | ~600,000 | - |
+
+---
+
+## See Also
+
+- [Register Types](registers.md) â HRA, HRB, etc.
+- [State Codes](states.md) â German state codes
+- [API Parameters](parameters.md) â All search parameters
+
diff --git a/docs/reference/parameters.de.md b/docs/reference/parameters.de.md
new file mode 100644
index 00000000..6ae6c101
--- /dev/null
+++ b/docs/reference/parameters.de.md
@@ -0,0 +1,244 @@
+# API-Parameter
+
+VollstĂ€ndige Referenz aller Parameter fĂŒr die `search()`-Funktion.
+
+## ParameterĂŒbersicht
+
+| Parameter | Typ | Standard | Beschreibung |
+|-----------|-----|----------|--------------|
+| `keywords` | `str` | Erforderlich | Suchbegriff fĂŒr Firmennamen |
+| `states` | `List[str]` | `None` | Nach BundeslÀndern filtern |
+| `register_type` | `str` | `None` | Nach Registerart filtern |
+| `court` | `str` | `None` | Spezifisches Registergericht |
+| `register_number` | `str` | `None` | Spezifische Registernummer |
+| `only_active` | `bool` | `False` | Nur aktuell eingetragene |
+| `exact` | `bool` | `False` | Exakte NamensĂŒbereinstimmung |
+| `similar_sounding` | `bool` | `False` | Ăhnlich klingende Namen einschlieĂen |
+| `use_cache` | `bool` | `True` | Gecachte Ergebnisse verwenden |
+
+---
+
+## Parameter im Detail
+
+### keywords (erforderlich)
+
+Der Hauptsuchbegriff. Sucht in Firmennamen.
+
+```python
+# Ein Wort
+search("Bank")
+
+# Mehrere Wörter
+search("Deutsche Bank AG")
+
+# Teilname
+search("Deutsche") # Findet "Deutsche Bahn", "Deutsche Bank", etc.
+```
+
+**Tipps:**
+
+- Verwenden Sie markante Wörter fĂŒr bessere Ergebnisse
+- Vermeiden Sie sehr hÀufige Wörter wie "GmbH" allein
+- FĂŒgen Sie die Rechtsform fĂŒr spezifischere Ergebnisse hinzu: "Mustermann GmbH"
+
+---
+
+### states
+
+Liste von BundeslÀnder-Codes zum Filtern der Ergebnisse. Siehe [BundeslÀnder-Codes](states.md).
+
+```python
+# Ein Bundesland
+search("Bank", states=["BE"])
+
+# Mehrere BundeslÀnder
+search("Bank", states=["BE", "HH", "BY"])
+
+# Alle BundeslÀnder (Standard - nicht angeben)
+search("Bank")
+```
+
+**Typ:** `List[str]` oder `None`
+
+**GĂŒltige Werte:** `BW`, `BY`, `BE`, `BB`, `HB`, `HH`, `HE`, `MV`, `NI`, `NW`, `RP`, `SL`, `SN`, `ST`, `SH`, `TH`
+
+---
+
+### register_type
+
+Nach Registerart filtern. Siehe [Registerarten](registers.md).
+
+```python
+# Nur Kapitalgesellschaften (GmbH, AG)
+search("Bank", register_type="HRB")
+
+# Nur Personengesellschaften (KG, OHG)
+search("Consulting", register_type="HRA")
+
+# Genossenschaften
+search("Wohnungsbau", register_type="GnR")
+```
+
+**Typ:** `str` oder `None`
+
+**GĂŒltige Werte:** `HRA`, `HRB`, `GnR`, `PR`, `VR`
+
+---
+
+### court
+
+Nach spezifischem Registergericht filtern.
+
+```python
+# Nur Berlin Charlottenburg
+search("Bank", court="Berlin (Charlottenburg)")
+
+# Nur MĂŒnchen
+search("Bank", court="MĂŒnchen")
+```
+
+**Typ:** `str` oder `None`
+
+**Hinweis:** Gerichtsnamen mĂŒssen exakt ĂŒbereinstimmen, wie sie im Register erscheinen.
+
+---
+
+### register_number
+
+Nach spezifischer Registernummer suchen.
+
+```python
+# Nach Registernummer suchen
+search("", register_number="HRB 12345")
+
+# Kombiniert mit Gericht
+search("",
+ court="Berlin (Charlottenburg)",
+ register_number="HRB 44343")
+```
+
+**Typ:** `str` oder `None`
+
+---
+
+### only_active
+
+Nur nach aktuell eingetragenen Unternehmen filtern.
+
+```python
+# Nur aktive Unternehmen
+search("Bank", include_deleted=False)
+
+# Gelöschte/fusionierte einschlieĂen (Standard)
+search("Bank", include_deleted=True)
+```
+
+**Typ:** `bool`
+
+**Standard:** `False`
+
+---
+
+### exact
+
+Exakte NamensĂŒbereinstimmung statt TeilĂŒbereinstimmung erfordern.
+
+```python
+# Exakte Ăbereinstimmung - findet nur "GASAG AG"
+search("GASAG AG", keyword_option="exact")
+
+# TeilĂŒbereinstimmung - findet "GASAG AG", "GASAG Beteiligungs GmbH", etc.
+search("GASAG", exact=False)
+```
+
+**Typ:** `bool`
+
+**Standard:** `False`
+
+---
+
+### similar_sounding
+
+Unternehmen mit Ă€hnlich klingenden Namen einschlieĂen (phonetische Suche).
+
+```python
+# Ăhnliche Namen einschlieĂen (Meyer, Meier, Mayer, etc.)
+search("MĂŒller", similar_sounding=True)
+```
+
+**Typ:** `bool`
+
+**Standard:** `False`
+
+**Hinweis:** Dies kann die Anzahl der Ergebnisse erheblich erhöhen.
+
+---
+
+### use_cache
+
+Ob gecachte Ergebnisse verwendet werden sollen.
+
+```python
+# Cache verwenden (Standard)
+search("Bank", force_refresh=False)
+
+# Immer frische Daten abrufen
+search("Bank", force_refresh=True)
+```
+
+**Typ:** `bool`
+
+**Standard:** `True`
+
+---
+
+## VollstÀndiges Beispiel
+
+```python
+from handelsregister import search
+
+# VollstÀndiges Beispiel mit allen Parametern
+firmen = search(
+ keywords="Bank", # Suche nach "Bank"
+ states=["BE", "HH"], # In Berlin und Hamburg
+ register_type="HRB", # Nur Kapitalgesellschaften
+ court=None, # Beliebiges Gericht
+ register_number=None, # Beliebige Nummer
+ include_deleted=False, # Nur aktive Unternehmen
+ exact=False, # TeilĂŒbereinstimmung OK
+ similar_sounding=False, # Keine phonetische Suche
+ force_refresh=False, # Cache verwenden
+)
+
+print(f"Gefunden: {len(firmen)} Unternehmen")
+```
+
+---
+
+## CLI-Entsprechung
+
+| Python-Parameter | CLI-Option |
+|------------------|------------|
+| `keywords` | `-s, --search` |
+| `states` | `--states` |
+| `register_type` | `--register-type` |
+| `only_active` | `--active-only` |
+| `exact` | `--exact` |
+| `force_refresh=True` | `--no-cache` |
+
+```bash
+handelsregister \
+ -s "Bank" \
+ --states BE,HH \
+ --register-type HRB \
+ --active-only
+```
+
+---
+
+## Siehe auch
+
+- [BundeslĂ€nder-Codes](states.md) â GĂŒltige BundeslĂ€nder-Codes
+- [Registerarten](registers.md) â GĂŒltige Registerarten
+- [Als Library verwenden](../guide/library.md) â Weitere Beispiele
+
diff --git a/docs/reference/parameters.md b/docs/reference/parameters.md
new file mode 100644
index 00000000..0bb237ed
--- /dev/null
+++ b/docs/reference/parameters.md
@@ -0,0 +1,307 @@
+# API Parameters
+
+Complete reference of all parameters for the `search()` function.
+
+## Parameter Overview
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `keywords` | `str` | Required | Search term for company names |
+| `keyword_option` | `KeywordMatch` or `str` | `"all"` | How to match keywords: "all", "min", or "exact" |
+| `states` | `List[State]` or `List[str]` | `None` | Filter by federal states |
+| `register_type` | `RegisterType` or `str` | `None` | Filter by register type |
+| `register_number` | `str` | `None` | Specific register number |
+| `include_deleted` | `bool` | `False` | Include deleted/historical entries |
+| `similar_sounding` | `bool` | `False` | Include similar-sounding names |
+| `results_per_page` | `int` | `100` | Number of results per page (10, 25, 50, 100) |
+| `force_refresh` | `bool` | `False` | Bypass cache and fetch fresh data |
+| `debug` | `bool` | `False` | Enable debug logging |
+
+---
+
+## Parameter Details
+
+### keywords (required)
+
+The main search term. Searches in company names.
+
+```python
+# Single word
+search("Bank")
+
+# Multiple words
+search("Deutsche Bank AG")
+
+# Partial name
+search("Deutsche") # Finds "Deutsche Bahn", "Deutsche Bank", etc.
+```
+
+**Tips:**
+
+- Use distinctive words for better results
+- Avoid very common words like "GmbH" alone
+- Add legal form for more specific results: "Mustermann GmbH"
+
+---
+
+### states
+
+List of state codes to filter results. See [State Codes](states.md).
+
+```python
+from handelsregister import search, State
+
+# Single state (recommended: Enum)
+search("Bank", states=[State.BE])
+
+# Multiple states (recommended: Enums)
+search("Bank", states=[State.BE, State.HH, State.BY])
+
+# String-based API still works
+search("Bank", states=["BE"])
+search("Bank", states=["BE", "HH", "BY"])
+
+# All states (default - don't specify)
+search("Bank")
+```
+
+**Type:** `List[State]` or `List[str]` or `None`
+
+**Valid values:** `BW`, `BY`, `BE`, `BB`, `HB`, `HH`, `HE`, `MV`, `NI`, `NW`, `RP`, `SL`, `SN`, `ST`, `SH`, `TH`
+
+**Recommended:** Use `State` enum for IDE autocomplete: `State.BE`, `State.HH`, etc.
+
+---
+
+### register_type
+
+Filter by register type. See [Register Types](registers.md).
+
+```python
+from handelsregister import search, RegisterType
+
+# Only corporations (GmbH, AG) - recommended: Enum
+search("Bank", register_type=RegisterType.HRB)
+
+# Only partnerships (KG, OHG)
+search("Consulting", register_type=RegisterType.HRA)
+
+# Cooperatives
+search("Wohnungsbau", register_type=RegisterType.GnR)
+
+# String-based API still works
+search("Bank", register_type="HRB")
+```
+
+**Type:** `RegisterType` or `str` or `None`
+
+**Valid values:** `HRA`, `HRB`, `GnR`, `PR`, `VR`
+
+**Recommended:** Use `RegisterType` enum for IDE autocomplete: `RegisterType.HRB`, `RegisterType.HRA`, etc.
+
+---
+
+### keyword_option
+
+How to match keywords in the search.
+
+```python
+from handelsregister import search, KeywordMatch
+
+# All keywords must match (default) - recommended: Enum
+search("Deutsche Bank", keyword_option=KeywordMatch.ALL)
+
+# At least one keyword must match
+search("Deutsche Bank", keyword_option=KeywordMatch.MIN)
+
+# Exact name match
+search("GASAG AG", keyword_option=KeywordMatch.EXACT)
+
+# String-based API still works
+search("Deutsche Bank", keyword_option="all")
+search("GASAG AG", keyword_option="exact")
+```
+
+**Type:** `KeywordMatch` or `str`
+
+**Default:** `"all"` or `KeywordMatch.ALL`
+
+**Valid values:** `"all"` / `KeywordMatch.ALL`, `"min"` / `KeywordMatch.MIN`, `"exact"` / `KeywordMatch.EXACT`
+
+**Recommended:** Use `KeywordMatch` enum for IDE autocomplete: `KeywordMatch.ALL`, `KeywordMatch.EXACT`, etc.
+
+---
+
+### register_number
+
+Search for a specific register number.
+
+```python
+# Find by register number
+search("", register_number="HRB 12345")
+
+# Combined with keywords
+search("GASAG", register_number="HRB 44343")
+```
+
+**Type:** `str` or `None`
+
+---
+
+### include_deleted
+
+Include deleted/historical entries in results.
+
+```python
+# Include deleted entries
+search("Bank", include_deleted=True)
+
+# Only currently registered (default)
+search("Bank", include_deleted=False)
+```
+
+**Type:** `bool`
+
+**Default:** `False`
+
+---
+
+### similar_sounding
+
+Include companies with similar-sounding names (phonetic search).
+
+```python
+# Include similar names (Meyer, Meier, Mayer, etc.)
+search("MĂŒller", similar_sounding=True)
+```
+
+**Type:** `bool`
+
+**Default:** `False`
+
+**Note:** This can significantly increase the number of results.
+
+---
+
+### results_per_page
+
+Number of results to return per page.
+
+```python
+# Get 50 results per page
+search("Bank", results_per_page=50)
+
+# Get maximum results (100)
+search("Bank", results_per_page=100)
+```
+
+**Type:** `int`
+
+**Default:** `100`
+
+**Valid values:** `10`, `25`, `50`, `100`
+
+---
+
+### force_refresh
+
+Bypass cache and fetch fresh data from the website.
+
+```python
+# Use cache (default)
+search("Bank", force_refresh=False)
+
+# Always fetch fresh data
+search("Bank", force_refresh=True)
+```
+
+**Type:** `bool`
+
+**Default:** `False`
+
+---
+
+### debug
+
+Enable debug logging for troubleshooting.
+
+```python
+# Enable debug output
+search("Bank", debug=True)
+```
+
+**Type:** `bool`
+
+**Default:** `False`
+
+---
+
+## Complete Example
+
+```python
+from handelsregister import search, State, KeywordMatch, RegisterType
+
+# Full example with all parameters (recommended: Enums)
+companies = search(
+ keywords="Bank", # Search for "Bank"
+ keyword_option=KeywordMatch.ALL, # All keywords must match
+ states=[State.BE, State.HH], # In Berlin and Hamburg
+ register_type=RegisterType.HRB, # Only corporations
+ register_number=None, # Any number
+ include_deleted=False, # Only active companies
+ similar_sounding=False, # No phonetic search
+ results_per_page=100, # Maximum results
+ force_refresh=False, # Use cache
+ debug=False, # No debug output
+)
+
+# String-based API still works
+companies = search(
+ keywords="Bank",
+ keyword_option="all",
+ states=["BE", "HH"],
+ register_type="HRB",
+ register_number=None,
+ include_deleted=False,
+ similar_sounding=False,
+ results_per_page=100,
+ force_refresh=False,
+ debug=False,
+)
+
+print(f"Found: {len(companies)} companies")
+```
+
+---
+
+## CLI Equivalent
+
+| Python Parameter | CLI Option |
+|-----------------|------------|
+| `keywords` | `-s, --schlagwoerter` |
+| `keyword_option` | `-so, --schlagwortOptionen` |
+| `states` | `--states` |
+| `register_type` | `--register-type` |
+| `register_number` | `--register-number` |
+| `include_deleted` | `--include-deleted` |
+| `similar_sounding` | `--similar-sounding` |
+| `results_per_page` | `--results-per-page` |
+| `force_refresh=True` | `-f, --force` |
+| `debug=True` | `-d, --debug` |
+
+```bash
+handelsregister \
+ -s "Bank" \
+ --states BE,HH \
+ --register-type HRB \
+ --schlagwortOptionen all
+```
+
+---
+
+## See Also
+
+- [State Codes](states.md) â Valid state codes
+- [Register Types](registers.md) â Valid register types
+- [Using as Library](../guide/library.md) â More examples
+
diff --git a/docs/reference/registers.de.md b/docs/reference/registers.de.md
new file mode 100644
index 00000000..9e41c396
--- /dev/null
+++ b/docs/reference/registers.de.md
@@ -0,0 +1,140 @@
+# Registerarten
+
+Diese Tabelle zeigt die verschiedenen Registerarten im deutschen Handelsregistersystem.
+
+## Registerarten-Referenz
+
+| Code | Name | Typische Rechtsformen |
+|------|------|----------------------|
+| `HRA` | Handelsregister Abteilung A | Einzelkaufleute, Personengesellschaften (OHG, KG) |
+| `HRB` | Handelsregister Abteilung B | Kapitalgesellschaften (GmbH, AG, SE, KGaA) |
+| `GnR` | Genossenschaftsregister | Genossenschaften (eG) |
+| `PR` | Partnerschaftsregister | Partnerschaftsgesellschaften (PartG) |
+| `VR` | Vereinsregister | Eingetragene Vereine (e.V.) |
+
+---
+
+## Detaillierte Beschreibungen
+
+### HRA (Abteilung A)
+
+**Handelsregister Abteilung A** enthÀlt:
+
+- **Einzelkaufleute** (e.K., e.Kfm., e.Kfr.)
+- **OHG** (Offene Handelsgesellschaften)
+- **KG** (Kommanditgesellschaften)
+- **GmbH & Co. KG** (Besondere Kommanditgesellschaften)
+- **EWIV** (EuropÀische wirtschaftliche Interessenvereinigungen)
+
+```python
+# Suche nach Personengesellschaften
+firmen = search("KG", register_type="HRA")
+```
+
+### HRB (Abteilung B)
+
+**Handelsregister Abteilung B** enthÀlt:
+
+- **GmbH** (Gesellschaften mit beschrÀnkter Haftung)
+- **AG** (Aktiengesellschaften)
+- **SE** (EuropÀische Gesellschaften)
+- **KGaA** (Kommanditgesellschaften auf Aktien)
+- **UG (haftungsbeschrÀnkt)** (Unternehmergesellschaften)
+
+```python
+# Suche nach Kapitalgesellschaften
+firmen = search("GmbH", register_type="HRB")
+```
+
+### GnR (Genossenschaftsregister)
+
+**Genossenschaftsregister** enthÀlt:
+
+- **eG** (Eingetragene Genossenschaften)
+- **SCE** (EuropÀische Genossenschaften)
+
+```python
+# Suche nach Genossenschaften
+firmen = search("Genossenschaft", register_type="GnR")
+```
+
+### PR (Partnerschaftsregister)
+
+**Partnerschaftsregister** enthÀlt:
+
+- **PartG** (Partnerschaftsgesellschaften)
+- **PartG mbB** (Partnerschaftsgesellschaften mit beschrÀnkter Berufshaftung)
+
+Ăblich fĂŒr RechtsanwĂ€lte, Steuerberater, Ărzte, Architekten.
+
+```python
+# Suche nach Partnerschaftsgesellschaften
+firmen = search("RechtsanwÀlte", register_type="PR")
+```
+
+### VR (Vereinsregister)
+
+**Vereinsregister** enthÀlt:
+
+- **e.V.** (Eingetragene Vereine)
+
+```python
+# Suche nach Vereinen
+firmen = search("Verein", register_type="VR")
+```
+
+---
+
+## Verwendungsbeispiele
+
+### Python
+
+```python
+from handelsregister import search
+
+# Nur HRB (Kapitalgesellschaften)
+kapitalgesellschaften = search("Bank", register_type="HRB")
+
+# Nur HRA (Personengesellschaften)
+personengesellschaften = search("Consulting", register_type="HRA")
+
+# Suche ĂŒber alle Registerarten
+# (register_type nicht angeben)
+alle_arten = search("Mustermann")
+```
+
+### CLI
+
+```bash
+# Nur HRB
+handelsregister -s "Bank" --register-type HRB
+
+# Nur HRA
+handelsregister -s "KG" --register-type HRA
+
+# Genossenschaften
+handelsregister -s "Wohnungsbau" --register-type GnR
+```
+
+---
+
+## Statistiken
+
+UngefÀhre Verteilung der EintrÀge in Deutschland:
+
+| Register | UngefÀhre Anzahl | Anteil |
+|----------|------------------|--------|
+| HRB | ~1.500.000 | ~60% |
+| HRA | ~700.000 | ~28% |
+| GnR | ~20.000 | ~1% |
+| PR | ~15.000 | ~1% |
+| VR | ~250.000 | ~10% |
+
+---
+
+## Siehe auch
+
+- [BundeslĂ€nder-Codes](states.md) â Deutsche BundeslĂ€nder-Codes
+- [Rechtsformen](legal-forms.md) â GmbH, AG, etc.
+- [API-Parameter](parameters.md) â Alle Suchparameter
+
diff --git a/docs/reference/registers.md b/docs/reference/registers.md
new file mode 100644
index 00000000..fdec7433
--- /dev/null
+++ b/docs/reference/registers.md
@@ -0,0 +1,140 @@
+# Register Types
+
+This table shows the different register types available in the German commercial register system.
+
+## Register Type Reference
+
+| Code | German Name | English Name | Typical Legal Forms |
+|------|-------------|--------------|---------------------|
+| `HRA` | Handelsregister Abteilung A | Commercial Register Section A | Sole proprietors, partnerships (OHG, KG) |
+| `HRB` | Handelsregister Abteilung B | Commercial Register Section B | Corporations (GmbH, AG, SE, KGaA) |
+| `GnR` | Genossenschaftsregister | Cooperative Register | Cooperatives (eG) |
+| `PR` | Partnerschaftsregister | Partnership Register | Professional partnerships (PartG) |
+| `VR` | Vereinsregister | Association Register | Registered associations (e.V.) |
+
+---
+
+## Detailed Descriptions
+
+### HRA (Section A)
+
+**Commercial Register Section A** contains:
+
+- **Einzelkaufleute** (Sole proprietors)
+- **OHG** (General partnerships)
+- **KG** (Limited partnerships)
+- **GmbH & Co. KG** (Special limited partnerships)
+- **EWIV** (European Economic Interest Groupings)
+
+```python
+# Search for partnerships
+companies = search("KG", register_type="HRA")
+```
+
+### HRB (Section B)
+
+**Commercial Register Section B** contains:
+
+- **GmbH** (Limited liability companies)
+- **AG** (Stock corporations)
+- **SE** (European companies)
+- **KGaA** (Partnerships limited by shares)
+- **UG (haftungsbeschrÀnkt)** (Mini-GmbH)
+
+```python
+# Search for corporations
+companies = search("GmbH", register_type="HRB")
+```
+
+### GnR (Cooperative Register)
+
+**Cooperative Register** contains:
+
+- **eG** (Registered cooperatives)
+- **SCE** (European cooperatives)
+
+```python
+# Search for cooperatives
+companies = search("Genossenschaft", register_type="GnR")
+```
+
+### PR (Partnership Register)
+
+**Partnership Register** contains:
+
+- **PartG** (Professional partnerships)
+- **PartG mbB** (Professional partnerships with limited liability)
+
+Common for lawyers, accountants, doctors, architects.
+
+```python
+# Search for professional partnerships
+companies = search("RechtsanwÀlte", register_type="PR")
+```
+
+### VR (Association Register)
+
+**Association Register** contains:
+
+- **e.V.** (Registered associations)
+
+```python
+# Search for associations
+companies = search("Verein", register_type="VR")
+```
+
+---
+
+## Usage Examples
+
+### Python
+
+```python
+from handelsregister import search
+
+# Only HRB (corporations)
+corporations = search("Bank", register_type="HRB")
+
+# Only HRA (partnerships)
+partnerships = search("Consulting", register_type="HRA")
+
+# Search across register types
+# (don't specify register_type)
+all_types = search("Mustermann")
+```
+
+### CLI
+
+```bash
+# Only HRB
+handelsregister -s "Bank" --register-type HRB
+
+# Only HRA
+handelsregister -s "KG" --register-type HRA
+
+# Cooperatives
+handelsregister -s "Wohnungsbau" --register-type GnR
+```
+
+---
+
+## Statistics
+
+Approximate distribution of entries in Germany:
+
+| Register | Approximate Entries | Percentage |
+|----------|---------------------|------------|
+| HRB | ~1,500,000 | ~60% |
+| HRA | ~700,000 | ~28% |
+| GnR | ~20,000 | ~1% |
+| PR | ~15,000 | ~1% |
+| VR | ~250,000 | ~10% |
+
+---
+
+## See Also
+
+- [State Codes](states.md) â German state codes
+- [Legal Forms](legal-forms.md) â GmbH, AG, etc.
+- [API Parameters](parameters.md) â All search parameters
+
diff --git a/docs/reference/states.de.md b/docs/reference/states.de.md
new file mode 100644
index 00000000..f58b53ef
--- /dev/null
+++ b/docs/reference/states.de.md
@@ -0,0 +1,114 @@
+# BundeslÀnder-Codes
+
+Diese Tabelle zeigt die ISO 3166-2:DE Codes fĂŒr deutsche BundeslĂ€nder, die im `states`-Parameter verwendet werden.
+
+## BundeslÀnder-Referenz
+
+| Code | Bundesland | Hauptstadt |
+|------|------------|------------|
+| `BW` | Baden-WĂŒrttemberg | Stuttgart |
+| `BY` | Bayern | MĂŒnchen |
+| `BE` | Berlin | Berlin |
+| `BB` | Brandenburg | Potsdam |
+| `HB` | Bremen | Bremen |
+| `HH` | Hamburg | Hamburg |
+| `HE` | Hessen | Wiesbaden |
+| `MV` | Mecklenburg-Vorpommern | Schwerin |
+| `NI` | Niedersachsen | Hannover |
+| `NW` | Nordrhein-Westfalen | DĂŒsseldorf |
+| `RP` | Rheinland-Pfalz | Mainz |
+| `SL` | Saarland | SaarbrĂŒcken |
+| `SN` | Sachsen | Dresden |
+| `ST` | Sachsen-Anhalt | Magdeburg |
+| `SH` | Schleswig-Holstein | Kiel |
+| `TH` | ThĂŒringen | Erfurt |
+
+---
+
+## Verwendungsbeispiele
+
+### Python
+
+```python
+from handelsregister import search
+
+# Suche in Berlin
+firmen = search("Bank", states=["BE"])
+
+# Suche in mehreren BundeslÀndern
+firmen = search("Bank", states=["BE", "HH", "BY"])
+
+# Alle GroĂstĂ€dte
+grossstaedte = ["BE", "HH", "BY", "NW", "HE"]
+firmen = search("Bank", states=grossstaedte)
+```
+
+### CLI
+
+```bash
+# Ein Bundesland
+handelsregister -s "Bank" --states BE
+
+# Mehrere BundeslÀnder
+handelsregister -s "Bank" --states BE,HH,BY
+```
+
+---
+
+## Bundesland-Gruppen
+
+### Stadtstaaten
+
+```python
+STADTSTAATEN = ["BE", "HH", "HB"]
+```
+
+### Ostdeutschland (ehemalige DDR)
+
+```python
+OSTDEUTSCHE_LAENDER = ["BE", "BB", "MV", "SN", "ST", "TH"]
+```
+
+### Westdeutschland
+
+```python
+WESTDEUTSCHE_LAENDER = ["BW", "BY", "HB", "HH", "HE", "NI", "NW", "RP", "SL", "SH"]
+```
+
+### SĂŒddeutschland
+
+```python
+SUEDDEUTSCHE_LAENDER = ["BW", "BY"]
+```
+
+### Norddeutschland
+
+```python
+NORDDEUTSCHE_LAENDER = ["HB", "HH", "MV", "NI", "SH"]
+```
+
+---
+
+## Karte
+
+```
+ SH
+ HH
+HB MV
+ NI BB BE
+ ST
+ NW SN
+ TH
+ HE
+ RP BY
+SL BW
+```
+
+---
+
+## Siehe auch
+
+- [Registerarten](registers.md) â HRA, HRB, etc.
+- [Rechtsformen](legal-forms.md) â GmbH, AG, etc.
+- [API-Parameter](parameters.md) â Alle Suchparameter
+
diff --git a/docs/reference/states.md b/docs/reference/states.md
new file mode 100644
index 00000000..da6f4fb3
--- /dev/null
+++ b/docs/reference/states.md
@@ -0,0 +1,114 @@
+# State Codes
+
+This table shows the ISO 3166-2:DE codes for German federal states used in the `states` parameter.
+
+## State Code Reference
+
+| Code | State (German) | State (English) |
+|------|----------------|-----------------|
+| `BW` | Baden-WĂŒrttemberg | Baden-WĂŒrttemberg |
+| `BY` | Bayern | Bavaria |
+| `BE` | Berlin | Berlin |
+| `BB` | Brandenburg | Brandenburg |
+| `HB` | Bremen | Bremen |
+| `HH` | Hamburg | Hamburg |
+| `HE` | Hessen | Hesse |
+| `MV` | Mecklenburg-Vorpommern | Mecklenburg-Western Pomerania |
+| `NI` | Niedersachsen | Lower Saxony |
+| `NW` | Nordrhein-Westfalen | North Rhine-Westphalia |
+| `RP` | Rheinland-Pfalz | Rhineland-Palatinate |
+| `SL` | Saarland | Saarland |
+| `SN` | Sachsen | Saxony |
+| `ST` | Sachsen-Anhalt | Saxony-Anhalt |
+| `SH` | Schleswig-Holstein | Schleswig-Holstein |
+| `TH` | ThĂŒringen | Thuringia |
+
+---
+
+## Usage Examples
+
+### Python
+
+```python
+from handelsregister import search
+
+# Search in Berlin
+companies = search("Bank", states=["BE"])
+
+# Search in multiple states
+companies = search("Bank", states=["BE", "HH", "BY"])
+
+# All major cities
+major_cities = ["BE", "HH", "BY", "NW", "HE"]
+companies = search("Bank", states=major_cities)
+```
+
+### CLI
+
+```bash
+# Single state
+handelsregister -s "Bank" --states BE
+
+# Multiple states
+handelsregister -s "Bank" --states BE,HH,BY
+```
+
+---
+
+## State Groups
+
+### City-States
+
+```python
+CITY_STATES = ["BE", "HH", "HB"]
+```
+
+### Eastern Germany (former GDR)
+
+```python
+EASTERN_STATES = ["BE", "BB", "MV", "SN", "ST", "TH"]
+```
+
+### Western Germany
+
+```python
+WESTERN_STATES = ["BW", "BY", "HB", "HH", "HE", "NI", "NW", "RP", "SL", "SH"]
+```
+
+### Southern Germany
+
+```python
+SOUTHERN_STATES = ["BW", "BY"]
+```
+
+### Northern Germany
+
+```python
+NORTHERN_STATES = ["HB", "HH", "MV", "NI", "SH"]
+```
+
+---
+
+## Map
+
+```
+ SH
+ HH
+HB MV
+ NI BB BE
+ ST
+ NW SN
+ TH
+ HE
+ RP BY
+SL BW
+```
+
+---
+
+## See Also
+
+- [Register Types](registers.md) â HRA, HRB, etc.
+- [Legal Forms](legal-forms.md) â GmbH, AG, etc.
+- [API Parameters](parameters.md) â All search parameters
+
diff --git a/handelsregister.py b/handelsregister.py
index 03ccc1af..eb658b61 100755
--- a/handelsregister.py
+++ b/handelsregister.py
@@ -1,221 +1,27 @@
#!/usr/bin/env python3
"""
-bundesAPI/handelsregister is the command-line interface for the shared register of companies portal for the German federal states.
-You can query, download, automate and much more, without using a web browser.
-"""
-
-import argparse
-import tempfile
-import mechanize
-import re
-import pathlib
-import sys
-from bs4 import BeautifulSoup
-import urllib.parse
-
-# Dictionaries to map arguments to values
-schlagwortOptionen = {
- "all": 1,
- "min": 2,
- "exact": 3
-}
-
-class HandelsRegister:
- def __init__(self, args):
- self.args = args
- self.browser = mechanize.Browser()
-
- self.browser.set_debug_http(args.debug)
- self.browser.set_debug_responses(args.debug)
- # self.browser.set_debug_redirects(True)
-
- self.browser.set_handle_robots(False)
- self.browser.set_handle_equiv(True)
- self.browser.set_handle_gzip(True)
- self.browser.set_handle_refresh(False)
- self.browser.set_handle_redirect(True)
- self.browser.set_handle_referer(True)
-
- self.browser.addheaders = [
- (
- "User-Agent",
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15",
- ),
- ( "Accept-Language", "en-GB,en;q=0.9" ),
- ( "Accept-Encoding", "gzip, deflate, br" ),
- (
- "Accept",
- "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
- ),
- ( "Connection", "keep-alive" ),
- ]
-
- self.cachedir = pathlib.Path(tempfile.gettempdir()) / "handelsregister_cache"
- self.cachedir.mkdir(parents=True, exist_ok=True)
-
- def open_startpage(self):
- self.browser.open("https://www.handelsregister.de", timeout=10)
-
- def companyname2cachename(self, companyname):
- return self.cachedir / companyname
-
- def search_company(self):
- cachename = self.companyname2cachename(self.args.schlagwoerter)
- if self.args.force==False and cachename.exists():
- with open(cachename, "r") as f:
- html = f.read()
- if not self.args.json:
- print("return cached content for %s" % self.args.schlagwoerter)
- else:
- # TODO implement token bucket to abide by rate limit
- # Use an atomic counter: https://gist.github.com/benhoyt/8c8a8d62debe8e5aa5340373f9c509c7
- self.browser.select_form(name="naviForm")
- self.browser.form.new_control('hidden', 'naviForm:erweiterteSucheLink', {'value': 'naviForm:erweiterteSucheLink'})
- self.browser.form.new_control('hidden', 'target', {'value': 'erweiterteSucheLink'})
- response_search = self.browser.submit()
-
- if self.args.debug == True:
- print(self.browser.title())
-
- self.browser.select_form(name="form")
-
- self.browser["form:schlagwoerter"] = self.args.schlagwoerter
- so_id = schlagwortOptionen.get(self.args.schlagwortOptionen)
-
- self.browser["form:schlagwortOptionen"] = [str(so_id)]
+Compatibility shim for backward compatibility.
- response_result = self.browser.submit()
+This file maintains backward compatibility with the old single-file structure.
+All imports are re-exported from the new package structure.
- if self.args.debug == True:
- print(self.browser.title())
-
- html = response_result.read().decode("utf-8")
- with open(cachename, "w") as f:
- f.write(html)
-
- # TODO catch the situation if there's more than one company?
- # TODO get all documents attached to the exact company
- # TODO parse useful information out of the PDFs
- return get_companies_in_searchresults(html)
-
-
-
-def parse_result(result):
- cells = []
- for cellnum, cell in enumerate(result.find_all('td')):
- cells.append(cell.text.strip())
- d = {}
- d['court'] = cells[1]
-
- # Extract register number: HRB, HRA, VR, GnR followed by numbers (e.g. HRB 12345, VR 6789)
- # Also capture suffix letter if present (e.g. HRB 12345 B), but avoid matching start of words (e.g. " Formerly")
- reg_match = re.search(r'(HRA|HRB|GnR|VR|PR)\s*\d+(\s+[A-Z])?(?!\w)', d['court'])
- d['register_num'] = reg_match.group(0) if reg_match else None
-
- d['name'] = cells[2]
- d['state'] = cells[3]
- d['status'] = cells[4].strip() # Original value for backward compatibility
- d['statusCurrent'] = cells[4].strip().upper().replace(' ', '_') # Transformed value
-
- # Ensure consistent register number suffixes (e.g. ' B' for Berlin HRB, ' HB' for Bremen) which might be implicit
- if d['register_num']:
- suffix_map = {
- 'Berlin': {'HRB': ' B'},
- 'Bremen': {'HRA': ' HB', 'HRB': ' HB', 'GnR': ' HB', 'VR': ' HB', 'PR': ' HB'}
- }
- reg_type = d['register_num'].split()[0]
- suffix = suffix_map.get(d['state'], {}).get(reg_type)
- if suffix and not d['register_num'].endswith(suffix):
- d['register_num'] += suffix
- d['documents'] = cells[5] # todo: get the document links
- d['history'] = []
- hist_start = 8
-
- for i in range(hist_start, len(cells), 3):
- if i + 1 >= len(cells):
- break
- if "Branches" in cells[i] or "Niederlassungen" in cells[i]:
- break
- d['history'].append((cells[i], cells[i+1])) # (name, location)
-
- return d
-
-def pr_company_info(c):
- for tag in ('name', 'court', 'register_num', 'district', 'state', 'statusCurrent'):
- print('%s: %s' % (tag, c.get(tag, '-')))
- print('history:')
- for name, loc in c.get('history'):
- print(name, loc)
-
-def get_companies_in_searchresults(html):
- soup = BeautifulSoup(html, 'html.parser')
- grid = soup.find('table', role='grid')
-
- results = []
- for result in grid.find_all('tr'):
- a = result.get('data-ri')
- if a is not None:
- index = int(a)
-
- d = parse_result(result)
- results.append(d)
- return results
-
-def parse_args():
- parser = argparse.ArgumentParser(description='A handelsregister CLI')
- parser.add_argument(
- "-d",
- "--debug",
- help="Enable debug mode and activate logging",
- action="store_true"
- )
- parser.add_argument(
- "-f",
- "--force",
- help="Force a fresh pull and skip the cache",
- action="store_true"
- )
- parser.add_argument(
- "-s",
- "--schlagwoerter",
- help="Search for the provided keywords",
- required=True,
- default="Gasag AG" # TODO replace default with a generic search term
- )
- parser.add_argument(
- "-so",
- "--schlagwortOptionen",
- help="Keyword options: all=contain all keywords; min=contain at least one keyword; exact=contain the exact company name.",
- choices=["all", "min", "exact"],
- default="all"
- )
- parser.add_argument(
- "-j",
- "--json",
- help="Return response as JSON",
- action="store_true"
- )
- args = parser.parse_args()
+DEPRECATED: This file will be removed in a future version.
+Please update your imports to use the new package structure:
+ from handelsregister import search, HandelsRegister, SearchOptions
+"""
+from __future__ import annotations
- # Enable debugging if wanted
- if args.debug == True:
- import logging
- logger = logging.getLogger("mechanize")
- logger.addHandler(logging.StreamHandler(sys.stdout))
- logger.setLevel(logging.DEBUG)
+import warnings
- return args
+# Issue deprecation warning
+warnings.warn(
+ "Importing from handelsregister.py directly is deprecated. "
+ "Please use 'from handelsregister import ...' instead. "
+ "The old single-file structure will be removed in a future version.",
+ DeprecationWarning,
+ stacklevel=2,
+)
-if __name__ == "__main__":
- import json
- args = parse_args()
- h = HandelsRegister(args)
- h.open_startpage()
- companies = h.search_company()
- if companies is not None:
- if args.json:
- print(json.dumps(companies))
- else:
- for c in companies:
- pr_company_info(c)
+# Re-export everything from the new package structure
+from handelsregister import * # noqa: F403, E402
diff --git a/handelsregister/__init__.py b/handelsregister/__init__.py
new file mode 100644
index 00000000..8f7f9973
--- /dev/null
+++ b/handelsregister/__init__.py
@@ -0,0 +1,116 @@
+"""Python client for the German Handelsregister (commercial register).
+
+This package provides both a CLI tool and a library interface to search the
+Handelsregister portal without using a browser. Built as part of the bundesAPI
+initiative to make German government data more accessible.
+"""
+
+from __future__ import annotations
+
+# Import all public API components for backward compatibility
+from .cache import SearchCache
+
+# Import public API functions
+from .cli import get_details, pr_company_details, pr_company_info, search, search_batch
+from .client import HandelsRegister
+from .constants import (
+ KEYWORD_OPTIONS,
+ REGISTER_TYPES,
+ RESULTS_PER_PAGE_OPTIONS,
+ STATE_CODES,
+ SUFFIX_MAP,
+ KeywordMatch,
+ RegisterType,
+ State,
+ build_url,
+ schlagwortOptionen,
+)
+from .exceptions import (
+ CacheError,
+ FormError,
+ HandelsregisterError,
+ NetworkError,
+ ParseError,
+ PartialResultError,
+)
+from .models import (
+ Address,
+ CacheEntry,
+ Company,
+ CompanyDetails,
+ HistoryEntry,
+ Owner,
+ Representative,
+ SearchOptions,
+)
+from .parser import (
+ DetailsParser,
+ ResultParser,
+ get_companies_in_searchresults,
+ parse_result,
+)
+from .settings import (
+ BASE_URL,
+ DEFAULT_CACHE_TTL_SECONDS,
+ DETAILS_CACHE_TTL_SECONDS,
+ MAX_RETRIES,
+ RATE_LIMIT_CALLS,
+ RATE_LIMIT_PERIOD,
+ REQUEST_TIMEOUT,
+ RETRY_WAIT_MAX,
+ RETRY_WAIT_MIN,
+ Settings,
+ settings,
+)
+
+# Package metadata
+__version__ = "0.3.0"
+__all__ = [
+ "BASE_URL",
+ "DEFAULT_CACHE_TTL_SECONDS",
+ "DETAILS_CACHE_TTL_SECONDS",
+ "KEYWORD_OPTIONS",
+ "KeywordMatch",
+ "MAX_RETRIES",
+ "RATE_LIMIT_CALLS",
+ "RATE_LIMIT_PERIOD",
+ "RegisterType",
+ "REGISTER_TYPES",
+ "REQUEST_TIMEOUT",
+ "RESULTS_PER_PAGE_OPTIONS",
+ "RETRY_WAIT_MAX",
+ "RETRY_WAIT_MIN",
+ "State",
+ "STATE_CODES",
+ "SUFFIX_MAP",
+ # Main classes
+ "Address",
+ "CacheEntry",
+ "CacheError",
+ "Company",
+ "CompanyDetails",
+ "DetailsParser",
+ "FormError",
+ "HandelsRegister",
+ "HandelsregisterError",
+ "HistoryEntry",
+ "NetworkError",
+ "Owner",
+ "ParseError",
+ "PartialResultError",
+ "Representative",
+ "ResultParser",
+ "SearchCache",
+ "SearchOptions",
+ "Settings",
+ "build_url",
+ "get_companies_in_searchresults",
+ "get_details",
+ "parse_result",
+ "pr_company_details",
+ "pr_company_info",
+ "schlagwortOptionen",
+ "search",
+ "search_batch",
+ "settings",
+]
diff --git a/handelsregister/cache.py b/handelsregister/cache.py
new file mode 100644
index 00000000..1144b9a7
--- /dev/null
+++ b/handelsregister/cache.py
@@ -0,0 +1,148 @@
+"""Caching layer using DiskCache for the Handelsregister package."""
+
+import hashlib
+import logging
+import pathlib
+import tempfile
+from typing import Optional
+
+import diskcache
+
+from .settings import DEFAULT_CACHE_TTL_SECONDS, DETAILS_CACHE_TTL_SECONDS, settings
+
+logger = logging.getLogger(__name__)
+
+
+class SearchCache:
+ """Caches search results and company details using DiskCache.
+
+ Uses DiskCache for efficient, thread-safe caching with automatic TTL
+ expiration. Different TTLs for search results (1h default) vs details
+ (24h default) since details change less frequently.
+ """
+
+ def __init__(
+ self,
+ cache_dir: Optional[pathlib.Path] = None,
+ ttl_seconds: int = DEFAULT_CACHE_TTL_SECONDS,
+ details_ttl_seconds: int = DETAILS_CACHE_TTL_SECONDS,
+ ) -> None:
+ """Initialize the cache.
+
+ Args:
+ cache_dir: Directory to store cache files. Defaults to settings.cache_dir
+ or temp directory if not configured.
+ ttl_seconds: Time-to-live for search result cache entries in seconds.
+ details_ttl_seconds: Time-to-live for details cache entries in seconds.
+ """
+ self.ttl_seconds = ttl_seconds
+ self.details_ttl_seconds = details_ttl_seconds
+
+ # Use provided cache_dir, settings.cache_dir, or temp directory
+ if cache_dir is not None:
+ self.cache_dir = cache_dir
+ elif settings.cache_dir:
+ self.cache_dir = pathlib.Path(settings.cache_dir)
+ else:
+ self.cache_dir = pathlib.Path(tempfile.gettempdir()) / "handelsregister_cache"
+ # Initialize DiskCache with size limit (500MB default)
+ self._cache = diskcache.Cache(
+ str(self.cache_dir),
+ size_limit=500 * 1024 * 1024,
+ )
+
+ def _get_cache_key(self, query: str, options: str) -> str:
+ """Generate a safe cache key by hashing the query parameters."""
+ key_data = f"{query}|{options}"
+ return hashlib.sha256(key_data.encode("utf-8")).hexdigest()
+
+ def _get_cache_path(self, query: str, options: str) -> pathlib.Path:
+ """Get the cache file path for a query (for backward compatibility)."""
+ cache_key = self._get_cache_key(query, options)
+ return self.cache_dir / f"{cache_key}.json"
+
+ def get(self, query: str, options: str) -> Optional[str]:
+ """Returns cached HTML if available and not expired.
+
+ Args:
+ query: Search query string (or cache key for details).
+ options: Search options string.
+
+ Returns:
+ Cached HTML content, or None if not cached or expired.
+
+ DiskCache handles expiration automatically based on the TTL set
+ when the entry was stored.
+ """
+ cache_key = self._get_cache_key(query, options)
+ return self._cache.get(cache_key, default=None)
+
+ def set(self, query: str, options: str, html: str) -> None:
+ """Caches HTML content with automatic TTL.
+
+ Args:
+ query: Search query string.
+ options: Search options string.
+ html: HTML content to cache.
+ """
+ cache_key = self._get_cache_key(query, options)
+ # Use longer TTL for details cache
+ ttl = self.details_ttl_seconds if query.startswith("details:") else self.ttl_seconds
+ try:
+ self._cache.set(cache_key, html, expire=ttl)
+ except Exception as e:
+ logger.warning("Failed to write cache: %s", e)
+
+ def clear(self, details_only: bool = False) -> int:
+ """Deletes all cache entries.
+
+ Args:
+ details_only: If True, only delete details cache entries.
+ Note: With DiskCache this clears all entries as we
+ cannot efficiently filter by key prefix.
+
+ Returns:
+ Number of entries deleted.
+ """
+ if details_only:
+ # For details_only, we need to iterate and delete matching keys
+ count = 0
+ for key in list(self._cache):
+ # Keys starting with details prefix have "details:" in query
+ # Since we hash keys, we need to track this differently
+ # For simplicity, we just clear all when details_only is True
+ try:
+ del self._cache[key]
+ count += 1
+ except KeyError:
+ pass
+ return count
+ count = len(self._cache)
+ self._cache.clear()
+ return count
+
+ def get_stats(self) -> dict:
+ """Returns cache statistics.
+
+ Returns:
+ Dict with total_files, search_files, details_files, and
+ total_size_bytes.
+ """
+ return {
+ "total_files": len(self._cache),
+ "search_files": len(self._cache), # DiskCache doesn't distinguish
+ "details_files": 0, # Would need metadata tracking
+ "total_size_bytes": self._cache.volume(),
+ }
+
+ def close(self) -> None:
+ """Closes the cache connection."""
+ self._cache.close()
+
+ def __enter__(self) -> "SearchCache":
+ """Context manager entry."""
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
+ """Context manager exit."""
+ self.close()
diff --git a/handelsregister/cli.py b/handelsregister/cli.py
new file mode 100644
index 00000000..a4836668
--- /dev/null
+++ b/handelsregister/cli.py
@@ -0,0 +1,523 @@
+"""Command-line interface for the Handelsregister package."""
+
+import argparse
+import json
+import logging
+import sys
+from typing import Literal, Optional, Union
+
+from .client import HandelsRegister
+from .constants import (
+ REGISTER_TYPES,
+ RESULTS_PER_PAGE_OPTIONS,
+ STATE_CODES,
+ KeywordMatch,
+ RegisterType,
+ State,
+)
+from .exceptions import (
+ CacheError,
+ FormError,
+ HandelsregisterError,
+ NetworkError,
+ ParseError,
+)
+from .models import Company, CompanyDetails, SearchOptions
+
+
+def pr_company_info(c: Company) -> None:
+ """Prints company information to stdout.
+
+ Args:
+ c: Company object containing company information.
+ """
+ print(f"name: {c.name}")
+ print(f"court: {c.court}")
+ print(f"register_num: {c.register_num or '-'}")
+ print(f"state: {c.state}")
+ print(f"statusCurrent: {c.status_normalized or '-'}")
+ print(f"documents: {c.documents}")
+ print("history:")
+ for entry in c.history:
+ print(f"{entry.name} {entry.location}")
+
+
+def parse_args() -> argparse.Namespace:
+ """Parses command-line arguments.
+
+ Returns:
+ Parsed arguments namespace.
+ """
+ state_codes_help = ", ".join(f"{k}={v}" for k, v in sorted(STATE_CODES.items()))
+
+ parser = argparse.ArgumentParser(
+ description="A handelsregister CLI for the German commercial register",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog=f"""
+Examples:
+ %(prog)s -s "Deutsche Bahn" -so all
+ %(prog)s -s "GASAG AG" -so exact --json
+ %(prog)s -s "Munich" --states BE,BY --register-type HRB
+ %(prog)s -s "Bank" --include-deleted --similar-sounding
+
+State codes: {state_codes_help}
+ """,
+ )
+
+ # General options
+ parser.add_argument(
+ "-d", "--debug", help="Enable debug mode and activate logging", action="store_true"
+ )
+ parser.add_argument(
+ "-f", "--force", help="Force a fresh pull and skip the cache", action="store_true"
+ )
+ parser.add_argument("-j", "--json", help="Return response as JSON", action="store_true")
+
+ # Search parameters
+ search_group = parser.add_argument_group("Search parameters")
+ search_group.add_argument(
+ "-s",
+ "--schlagwoerter",
+ help="Search for the provided keywords (required)",
+ required=True,
+ metavar="KEYWORDS",
+ )
+ search_group.add_argument(
+ "-so",
+ "--schlagwortOptionen",
+ help="Keyword matching: all=all keywords; min=at least one; exact=exact name",
+ choices=["all", "min", "exact"],
+ default="all",
+ metavar="OPTION",
+ )
+ search_group.add_argument(
+ "--states",
+ help="Comma-separated list of state codes to filter by (e.g., BE,BY,HH)",
+ metavar="CODES",
+ )
+ search_group.add_argument(
+ "--register-type",
+ dest="register_type",
+ help="Filter by register type",
+ choices=REGISTER_TYPES,
+ metavar="TYPE",
+ )
+ search_group.add_argument(
+ "--register-number",
+ dest="register_number",
+ help="Search for a specific register number",
+ metavar="NUMBER",
+ )
+ search_group.add_argument(
+ "--include-deleted",
+ dest="include_deleted",
+ help="Include deleted/historical entries in results",
+ action="store_true",
+ )
+ search_group.add_argument(
+ "--similar-sounding",
+ dest="similar_sounding",
+ help="Use phonetic/similarity search (Kölner Phonetik)",
+ action="store_true",
+ )
+ search_group.add_argument(
+ "--results-per-page",
+ dest="results_per_page",
+ help="Number of results per page",
+ type=int,
+ choices=RESULTS_PER_PAGE_OPTIONS,
+ default=100,
+ metavar="N",
+ )
+
+ # Detail options
+ detail_group = parser.add_argument_group("Detail options")
+ detail_group.add_argument(
+ "--details", help="Fetch detailed information for each company result", action="store_true"
+ )
+ detail_group.add_argument(
+ "--detail-type",
+ dest="detail_type",
+ help="Type of details to fetch: SI=structured, AD=printout, UT=owners",
+ choices=["SI", "AD", "UT"],
+ default="SI",
+ metavar="TYPE",
+ )
+
+ args = parser.parse_args()
+
+ if args.debug:
+ logging.basicConfig(
+ level=logging.DEBUG,
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
+ stream=sys.stdout,
+ )
+ mechanize_logger = logging.getLogger("mechanize")
+ mechanize_logger.setLevel(logging.DEBUG)
+ else:
+ logging.basicConfig(level=logging.WARNING, format="%(levelname)s: %(message)s")
+
+ return args
+
+
+def search(
+ keywords: str,
+ keyword_option: Union[KeywordMatch, Literal["all", "min", "exact"]] = "all",
+ states: Optional[list[Union[State, str]]] = None,
+ register_type: Optional[Union[RegisterType, Literal["HRA", "HRB", "GnR", "PR", "VR"]]] = None,
+ register_number: Optional[str] = None,
+ include_deleted: bool = False,
+ similar_sounding: bool = False,
+ results_per_page: Literal[10, 25, 50, 100] = 100,
+ force_refresh: bool = False,
+ debug: bool = False,
+) -> list[Company]:
+ """Durchsucht das Handelsregister nach Unternehmen.
+
+ Dies ist die Haupt-API fĂŒr die programmatische Nutzung des Packages.
+
+ Args:
+ keywords: Suchbegriffe (erforderlich).
+ keyword_option: Suchmodus - "all" (alle Begriffe), "min" (mindestens einer),
+ "exact" (exakter Firmenname). Standard: "all".
+ Can be KeywordMatch enum or string.
+ states: Liste von Bundesland-Codes zum Filtern (z.B. ["BE", "BY", "HH"]).
+ Can be State enum values or strings.
+ register_type: Registerart-Filter (HRA, HRB, GnR, PR, VR).
+ Can be RegisterType enum or string.
+ register_number: Spezifische Registernummer suchen.
+ include_deleted: Auch gelöschte EintrÀge anzeigen.
+ similar_sounding: Phonetische Suche (Kölner Phonetik) verwenden.
+ results_per_page: Ergebnisse pro Seite (10, 25, 50, 100). Standard: 100.
+ force_refresh: Cache ignorieren und neue Daten abrufen.
+ debug: Debug-Logging aktivieren.
+
+ Returns:
+ Liste von Dictionaries mit Unternehmensdaten. Jedes Dictionary enthÀlt:
+ - name: Firmenname
+ - court: Registergericht
+ - register_num: Registernummer (z.B. "HRB 12345 B")
+ - state: Bundesland
+ - status: Aktueller Status
+ - statusCurrent: Normalisierter Status (z.B. "CURRENTLY_REGISTERED")
+ - documents: VerfĂŒgbare Dokumente
+ - history: Liste von (Name, Ort) Tupeln mit historischen EintrÀgen
+
+ Raises:
+ NetworkError: Bei Netzwerkfehlern.
+ FormError: Wenn die Website-Struktur sich geÀndert hat.
+ ParseError: Bei Fehlern beim Parsen der Ergebnisse.
+
+ Beispiel:
+ >>> from handelsregister import search, State, KeywordMatch, RegisterType
+ >>>
+ >>> # Einfache Suche
+ >>> companies = search("Deutsche Bahn")
+ >>>
+ >>> # Mit Filtern (string-basiert)
+ >>> banks = search("Bank", states=["BE", "HH"], register_type="HRB")
+ >>>
+ >>> # Mit Filtern (enum-basiert, mit AutovervollstÀndigung)
+ >>> banks = search("Bank", states=[State.BE, State.HH], register_type=RegisterType.HRB)
+ >>>
+ >>> for company in banks:
+ ... print(f"{company['name']} - {company['register_num']}")
+ """
+ # Configure logging if debug mode
+ if debug:
+ logging.basicConfig(
+ level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+ )
+
+ # Build args namespace for HandelsRegister
+ # Convert Enums to strings for backward compatibility
+ keyword_option_str = (
+ keyword_option.value if isinstance(keyword_option, KeywordMatch) else keyword_option
+ )
+ states_str = (
+ ",".join(s.value if isinstance(s, State) else s for s in states) if states else None
+ )
+ register_type_str = (
+ register_type.value if isinstance(register_type, RegisterType) else register_type
+ )
+
+ args = argparse.Namespace(
+ debug=debug,
+ force=force_refresh,
+ json=False,
+ schlagwoerter=keywords,
+ schlagwortOptionen=keyword_option_str,
+ states=states_str,
+ register_type=register_type_str,
+ register_number=register_number,
+ include_deleted=include_deleted,
+ similar_sounding=similar_sounding,
+ results_per_page=results_per_page,
+ )
+
+ hr = HandelsRegister(args)
+ hr.open_startpage()
+ return hr.search_company()
+
+
+def search_batch(
+ keywords_list: list[str],
+ states: Optional[list[Union[State, str]]] = None,
+ register_type: Optional[Union[RegisterType, str]] = None,
+ show_progress: Optional[bool] = None,
+ continue_on_error: bool = True,
+ raise_partial: bool = False,
+ **kwargs,
+) -> dict[str, list[Company]]:
+ """Performs multiple searches with progress indicators and error recovery.
+
+ Useful for batch processing multiple keywords or search terms.
+
+ Args:
+ keywords_list: List of keywords to search for.
+ states: List of state codes to filter by.
+ register_type: Register type filter.
+ show_progress: Show progress bar (auto-detected if None).
+ continue_on_error: Continue processing other keywords if one fails.
+ raise_partial: Raise PartialResultError if any searches fail.
+ **kwargs: Additional arguments passed to search().
+
+ Returns:
+ Dictionary mapping keywords to their search results.
+
+ Raises:
+ PartialResultError: If raise_partial=True and some searches failed.
+
+ Example:
+ >>> from handelsregister import search_batch, PartialResultError
+ >>>
+ >>> keywords = ["Bank", "Versicherung", "Immobilien"]
+ >>> try:
+ ... results = search_batch(keywords, states=["BE", "HH"])
+ ... except PartialResultError as e:
+ ... print(f"Some searches failed: {len(e.failed)}")
+ ... results = e.successful
+ >>> for keyword, companies in results.items():
+ ... print(f"{keyword}: {len(companies)} companies")
+ """
+ import logging
+ import sys
+
+ from tqdm import tqdm
+
+ from .exceptions import HandelsregisterError, PartialResultError
+
+ logger = logging.getLogger(__name__)
+
+ # Auto-detect if we should show progress
+ if show_progress is None:
+ show_progress = sys.stdout.isatty() and len(keywords_list) > 1
+
+ results: dict[str, list[Company]] = {}
+ failed: list[tuple[str, Exception]] = []
+ iterator = tqdm(keywords_list, desc="Searching", unit="keyword", disable=not show_progress)
+
+ for keyword in iterator:
+ if show_progress:
+ iterator.set_postfix(keyword=keyword[:30])
+ try:
+ results[keyword] = search(keyword, states=states, register_type=register_type, **kwargs)
+ except HandelsregisterError as e:
+ logger.exception("Failed to search for '%s'", keyword)
+ if not continue_on_error:
+ raise
+ failed.append((keyword, e))
+ results[keyword] = []
+ except Exception as e:
+ # Unexpected error
+ logger.exception("Unexpected error searching for '%s'", keyword)
+ if not continue_on_error:
+ raise
+ failed.append((keyword, e))
+ results[keyword] = []
+
+ # Raise partial result error if requested and there were failures
+ if raise_partial and failed:
+ error_msg = (
+ f"Batch search completed with {len(failed)} failures out of {len(keywords_list)} total"
+ )
+ raise PartialResultError(
+ error_msg,
+ successful=results,
+ failed=failed,
+ )
+
+ return results
+
+
+def get_details(
+ company: Company,
+ detail_type: Literal["SI", "AD", "UT", "CD", "HD", "VĂ"] = "SI",
+ force_refresh: bool = False,
+ debug: bool = False,
+) -> CompanyDetails:
+ """Ruft detaillierte Unternehmensinformationen ab.
+
+ Diese Funktion ruft erweiterte Informationen zu einem Unternehmen ab,
+ das zuvor ĂŒber search() gefunden wurde.
+
+ Args:
+ company: Unternehmen-Dictionary aus den Suchergebnissen.
+ detail_type: Art der Details:
+ - "SI": Strukturierter Registerinhalt (empfohlen)
+ - "AD": Aktueller Abdruck
+ - "UT": UnternehmenstrÀger
+ force_refresh: Cache ignorieren.
+ debug: Debug-Logging aktivieren.
+
+ Returns:
+ CompanyDetails mit allen verfĂŒgbaren Informationen.
+
+ Beispiel:
+ >>> from handelsregister import search, get_details
+ >>>
+ >>> # Erst suchen
+ >>> companies = search("GASAG AG", keyword_option="exact")
+ >>>
+ >>> # Dann Details abrufen
+ >>> if companies:
+ ... details = get_details(companies[0])
+ ... print(f"Kapital: {details.capital} {details.currency}")
+ ... print(f"Rechtsform: {details.legal_form}")
+ """
+ if debug:
+ logging.basicConfig(
+ level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+ )
+
+ hr = HandelsRegister(debug=debug)
+ hr.open_startpage()
+
+ register_num = company.register_num or ""
+ name = company.name
+
+ if register_num:
+ search_opts = SearchOptions(
+ keywords=name,
+ keyword_option="exact",
+ )
+ else:
+ search_opts = SearchOptions(
+ keywords=name,
+ keyword_option="all",
+ )
+
+ hr.search_with_options(search_opts, force_refresh=force_refresh)
+ company.row_index = 0
+ return hr.get_company_details(company, detail_type, force_refresh)
+
+
+def pr_company_details(details: CompanyDetails) -> None:
+ """Prints detailed company information to stdout.
+
+ Args:
+ details: CompanyDetails object with all information.
+ """
+ print(f"{'=' * 60}")
+ print(f"Firma: {details.name}")
+ print(f"Registernummer: {details.register_num}")
+ print(f"Gericht: {details.court}")
+ print(f"Bundesland: {details.state}")
+ print(f"Status: {details.status}")
+
+ if details.legal_form:
+ print(f"Rechtsform: {details.legal_form}")
+
+ if details.capital:
+ currency = details.currency or "EUR"
+ print(f"Kapital: {details.capital} {currency}")
+
+ if details.address:
+ print(f"Adresse: {details.address}")
+
+ if details.purpose:
+ print(f"Gegenstand: {details.purpose[:100]}{'...' if len(details.purpose) > 100 else ''}")
+
+ if details.representatives:
+ print("Vertretung:")
+ for rep in details.representatives:
+ loc = f" ({rep.location})" if rep.location else ""
+ print(f" - {rep.role}: {rep.name}{loc}")
+
+ if details.owners:
+ print("Gesellschafter:")
+ for owner in details.owners:
+ share = f" - {owner.share}" if owner.share else ""
+ print(f" - {owner.name}{share}")
+
+ if details.registration_date:
+ print(f"Eingetragen: {details.registration_date}")
+
+ print()
+
+
+def main() -> int: # noqa: PLR0912
+ """Main entry point for the CLI.
+
+ Returns:
+ Exit code (0 for success, non-zero for errors).
+ """
+ args = parse_args()
+
+ try:
+ hr = HandelsRegister(args)
+ hr.open_startpage()
+
+ fetch_details = getattr(args, "details", False)
+ detail_type = getattr(args, "detail_type", "SI")
+
+ if fetch_details:
+ search_opts = hr._build_search_options()
+ companies_details = hr.search_with_details(
+ search_opts,
+ fetch_details=True,
+ detail_type=detail_type,
+ force_refresh=getattr(args, "force", False),
+ show_progress=not args.json, # Show progress unless JSON output
+ )
+
+ if companies_details:
+ if args.json:
+ print(json.dumps([d.to_dict() for d in companies_details]))
+ else:
+ for details in companies_details:
+ pr_company_details(details)
+ else:
+ companies = hr.search_company()
+
+ if companies:
+ if args.json:
+ print(json.dumps(companies))
+ else:
+ for c in companies:
+ pr_company_info(c)
+
+ return 0 # noqa: TRY300
+ except NetworkError as e:
+ print(f"Network error: {e}", file=sys.stderr)
+ if args.debug and e.original_error:
+ print(f"Original error: {e.original_error}", file=sys.stderr)
+ return 1
+
+ except FormError as e:
+ print(f"Form error: {e}", file=sys.stderr)
+ return 2
+
+ except ParseError as e:
+ print(f"Parse error: {e}", file=sys.stderr)
+ if args.debug and e.html_snippet:
+ print(f"HTML snippet: {e.html_snippet}", file=sys.stderr)
+ return 3
+
+ except CacheError as e:
+ print(f"Cache error: {e}", file=sys.stderr)
+ return 4
+
+ except HandelsregisterError as e:
+ print(f"Error: {e}", file=sys.stderr)
+ return 1
diff --git a/handelsregister/client.py b/handelsregister/client.py
new file mode 100644
index 00000000..8d3609f9
--- /dev/null
+++ b/handelsregister/client.py
@@ -0,0 +1,656 @@
+"""Main client class for interacting with the Handelsregister portal."""
+
+import argparse
+import contextlib
+import logging
+import pathlib
+import sys
+import urllib.error
+from typing import Literal, Optional
+
+import mechanize
+from ratelimit import limits, sleep_and_retry
+from tenacity import (
+ before_sleep_log,
+ retry,
+ retry_if_exception_type,
+ stop_after_attempt,
+ wait_exponential,
+)
+from tqdm import tqdm
+
+from .cache import SearchCache
+from .constants import KEYWORD_OPTIONS, RESULTS_PER_PAGE_OPTIONS, STATE_CODES
+from .exceptions import FormError, NetworkError, ParseError, PartialResultError
+from .models import Company, CompanyDetails, SearchOptions
+from .parser import DetailsParser, ResultParser
+from .settings import (
+ BASE_URL,
+ MAX_RETRIES,
+ RATE_LIMIT_CALLS,
+ RATE_LIMIT_PERIOD,
+ REQUEST_TIMEOUT,
+ RETRY_WAIT_MAX,
+ RETRY_WAIT_MIN,
+)
+
+logger = logging.getLogger(__name__)
+
+
+def _with_retry_and_rate_limit(func):
+ """Decorator that applies rate limiting and retry logic to a method.
+
+ Combines rate limiting (60 calls/hour) with exponential backoff retry
+ logic for network operations. This decorator stack is reused across
+ all network operations in HandelsRegister.
+ """
+ return sleep_and_retry(
+ limits(calls=RATE_LIMIT_CALLS, period=RATE_LIMIT_PERIOD)(
+ retry(
+ stop=stop_after_attempt(MAX_RETRIES),
+ wait=wait_exponential(multiplier=1, min=RETRY_WAIT_MIN, max=RETRY_WAIT_MAX),
+ retry=retry_if_exception_type(urllib.error.URLError),
+ before_sleep=before_sleep_log(logger, logging.WARNING),
+ reraise=True,
+ )(func)
+ )
+ )
+
+
+class HandelsRegister:
+ """Browser-Automatisierung fĂŒr die Handelsregister-Suche.
+
+ Verwaltet die Interaktion mit der Handelsregister-Website, Navigation,
+ Formular-Ăbermittlung und Ergebnis-Abruf.
+
+ Beispiel:
+ >>> hr = HandelsRegister(debug=False)
+ >>> hr.open_startpage()
+ >>> results = hr.search_with_options(SearchOptions(keywords="Bank", states=["BE"]))
+ """
+
+ def __init__(
+ self,
+ args: Optional[argparse.Namespace] = None,
+ cache: Optional[SearchCache] = None,
+ debug: bool = False,
+ ) -> None:
+ """Initialisiert den HandelsRegister-Client.
+
+ Args:
+ args: CLI-Argumente (optional, fĂŒr RĂŒckwĂ€rtskompatibilitĂ€t).
+ cache: Cache-Instanz (optional, wird automatisch erstellt).
+ debug: Debug-Logging aktivieren.
+ """
+ self.args = args
+ self.cache = cache or SearchCache()
+ self._debug = debug if args is None else getattr(args, "debug", False)
+ self.browser = self._create_browser(debug=self._debug)
+
+ @classmethod
+ def from_options(
+ cls,
+ options: SearchOptions,
+ cache: Optional[SearchCache] = None,
+ debug: bool = False,
+ ) -> "HandelsRegister":
+ """Erstellt einen Client mit SearchOptions.
+
+ Args:
+ options: Suchoptionen.
+ cache: Cache-Instanz (optional).
+ debug: Debug-Logging aktivieren.
+
+ Returns:
+ Konfigurierte HandelsRegister-Instanz.
+ """
+ instance = cls(args=None, cache=cache, debug=debug)
+ instance._default_options = options
+ return instance
+
+ def _create_browser(self, debug: bool = False) -> mechanize.Browser:
+ """Creates and configures a mechanize browser instance.
+
+ Args:
+ debug: Enable debug output for HTTP requests.
+
+ Returns:
+ Configured Browser instance.
+ """
+ browser = mechanize.Browser()
+
+ browser.set_debug_http(debug)
+ browser.set_debug_responses(debug)
+
+ browser.set_handle_robots(False)
+ browser.set_handle_equiv(True)
+ browser.set_handle_gzip(True)
+ browser.set_handle_refresh(False)
+ browser.set_handle_redirect(True)
+ browser.set_handle_referer(True)
+
+ browser.addheaders = [
+ (
+ "User-Agent",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
+ "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15",
+ ),
+ ("Accept-Language", "en-GB,en;q=0.9"),
+ ("Accept-Encoding", "gzip, deflate, br"),
+ ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
+ ("Connection", "keep-alive"),
+ ]
+
+ return browser
+
+ # Backward compatibility: expose cachedir
+ @property
+ def cachedir(self) -> pathlib.Path:
+ """Gets the cache directory path."""
+ return self.cache.cache_dir
+
+ @_with_retry_and_rate_limit
+ def open_startpage(self) -> None:
+ """Opens the Handelsregister start page with automatic retries.
+
+ Uses exponential backoff for retries on network failures.
+ Rate limited to 60 requests per hour per portal terms of service.
+
+ Raises:
+ NetworkError: If the connection fails after all retry attempts.
+ """
+ try:
+ self.browser.open(str(BASE_URL), timeout=REQUEST_TIMEOUT)
+ except urllib.error.URLError as e:
+ error_msg = (
+ f"Failed to connect to {BASE_URL}: {e.reason}. "
+ f"Please check your internet connection and try again."
+ )
+ raise NetworkError(
+ error_msg,
+ original_error=e,
+ ) from e
+ except mechanize.BrowserStateError as e:
+ error_msg = f"Browser state error while opening {BASE_URL}: {e}"
+ raise NetworkError(
+ error_msg,
+ original_error=e,
+ ) from e
+
+ def _build_search_options(self) -> SearchOptions:
+ """Builds SearchOptions from command-line arguments.
+
+ Returns:
+ SearchOptions instance with all search parameters.
+ """
+ states = None
+ if hasattr(self.args, "states") and self.args.states:
+ states = [s.strip().upper() for s in self.args.states.split(",")]
+
+ return SearchOptions(
+ keywords=self.args.schlagwoerter,
+ keyword_option=self.args.schlagwortOptionen,
+ states=states,
+ register_type=getattr(self.args, "register_type", None),
+ register_number=getattr(self.args, "register_number", None),
+ include_deleted=getattr(self.args, "include_deleted", False),
+ similar_sounding=getattr(self.args, "similar_sounding", False),
+ results_per_page=getattr(self.args, "results_per_page", 100),
+ )
+
+ def search_with_options(
+ self,
+ options: SearchOptions,
+ force_refresh: bool = False,
+ ) -> list[Company]:
+ """FĂŒhrt eine Suche mit SearchOptions durch.
+
+ Args:
+ options: Suchoptionen.
+ force_refresh: Cache ignorieren.
+
+ Returns:
+ Liste von Dictionaries mit Unternehmensdaten.
+
+ Raises:
+ NetworkError: Bei Netzwerkfehlern.
+ FormError: Bei Formular-Problemen.
+ ParseError: Bei Parse-Fehlern.
+ """
+ cache_key = options.cache_key()
+
+ # Try to load from cache
+ if not force_refresh:
+ cached_html = self.cache.get(cache_key, "")
+ if cached_html is not None:
+ logger.info("Cache-Treffer fĂŒr: %s", options.keywords)
+ return ResultParser.parse_search_results(cached_html)
+
+ # Fetch fresh data from website
+ html = self._fetch_search_results(options)
+
+ # Save to cache
+ self.cache.set(cache_key, "", html)
+
+ return ResultParser.parse_search_results(html)
+
+ def search_company(self) -> list[Company]:
+ """Sucht nach Unternehmen basierend auf CLI-Argumenten.
+
+ Hinweis: FĂŒr programmatische Nutzung wird search_with_options() empfohlen.
+
+ Returns:
+ Liste von Dictionaries mit Unternehmensdaten.
+
+ Raises:
+ NetworkError: Bei Netzwerkfehlern.
+ FormError: Bei Formular-Problemen.
+ ParseError: Bei Parse-Fehlern.
+ """
+ if self.args is None:
+ error_msg = "search_company() benötigt args. Nutze search_with_options() stattdessen."
+ raise ValueError(error_msg)
+
+ search_opts = self._build_search_options()
+ force_refresh = getattr(self.args, "force", False)
+ return self.search_with_options(search_opts, force_refresh=force_refresh)
+
+ def _fetch_search_results(self, search_opts: SearchOptions) -> str:
+ """Fetches search results from the website.
+
+ Args:
+ search_opts: Search options specifying all search parameters.
+
+ Returns:
+ HTML content of search results page.
+
+ Raises:
+ NetworkError: If network requests fail.
+ FormError: If form selection or submission fails.
+ """
+ self._navigate_to_search()
+ return self._submit_search(search_opts)
+
+ @_with_retry_and_rate_limit
+ def _navigate_to_search(self) -> None:
+ """Navigates from start page to extended search form with retries.
+
+ Uses exponential backoff for retries on network failures.
+ Rate limited to 60 requests per hour per portal terms of service.
+
+ Raises:
+ FormError: If navigation form is not found.
+ NetworkError: If form submission fails after all retries.
+ """
+ try:
+ self.browser.select_form(name="naviForm")
+ except mechanize.FormNotFoundError as e:
+ current_url = self.browser.geturl() if hasattr(self.browser, "geturl") else "unknown"
+ error_msg = (
+ f"Navigation form 'naviForm' not found on page {current_url}. "
+ f"The website structure may have changed: {e}"
+ )
+ raise FormError(
+ error_msg,
+ original_error=e,
+ ) from e
+
+ self.browser.form.new_control(
+ "hidden", "naviForm:erweiterteSucheLink", {"value": "naviForm:erweiterteSucheLink"}
+ )
+ self.browser.form.new_control("hidden", "target", {"value": "erweiterteSucheLink"})
+
+ try:
+ self.browser.submit()
+ except urllib.error.URLError as e:
+ error_msg = f"Failed to submit navigation form: {e.reason}"
+ raise NetworkError(
+ error_msg,
+ original_error=e,
+ ) from e
+
+ logger.debug("Page title after navigation: %s", self.browser.title())
+
+ @_with_retry_and_rate_limit
+ def _submit_search(self, search_opts: SearchOptions) -> str: # noqa: PLR0912, PLR0915
+ """Submits the search form and returns results HTML with retries.
+
+ Uses exponential backoff for retries on network failures.
+ Rate limited to 60 requests per hour per portal terms of service.
+
+ Args:
+ search_opts: Search options specifying all search parameters.
+
+ Returns:
+ HTML content of search results page.
+
+ Raises:
+ FormError: If search form is not found.
+ NetworkError: If form submission fails after all retries.
+ """
+ try:
+ self.browser.select_form(name="form")
+ except mechanize.FormNotFoundError as e:
+ current_url = self.browser.geturl() if hasattr(self.browser, "geturl") else "unknown"
+ error_msg = (
+ f"Search form 'form' not found on page {current_url}. "
+ f"The website structure may have changed: {e}"
+ )
+ raise FormError(
+ error_msg,
+ original_error=e,
+ ) from e
+
+ self.browser["form:schlagwoerter"] = search_opts.keywords
+ option_id = KEYWORD_OPTIONS.get(search_opts.keyword_option)
+ self.browser["form:schlagwortOptionen"] = [str(option_id)]
+
+ if search_opts.states:
+ for state_code in search_opts.states:
+ if state_code in STATE_CODES:
+ try:
+ state_name = STATE_CODES[state_code]
+ control_name = f"form:{state_name}_input"
+ self.browser.form.find_control(control_name).value = ["on"]
+ logger.debug("Enabled state filter: %s (%s)", state_code, state_name)
+ except mechanize.ControlNotFoundError:
+ logger.warning("State control not found: %s", control_name)
+
+ if search_opts.register_type:
+ try:
+ self.browser["form:registerArt_input"] = [search_opts.register_type]
+ logger.debug("Set register type: %s", search_opts.register_type)
+ except mechanize.ControlNotFoundError:
+ logger.warning("Register type control not found")
+
+ if search_opts.register_number:
+ try:
+ self.browser["form:registerNummer"] = search_opts.register_number
+ logger.debug("Set register number: %s", search_opts.register_number)
+ except mechanize.ControlNotFoundError:
+ logger.warning("Register number control not found")
+
+ if search_opts.include_deleted:
+ try:
+ self.browser.form.find_control("form:auchGeloeschte_input").value = ["on"]
+ logger.debug("Enabled include deleted option")
+ except mechanize.ControlNotFoundError:
+ logger.warning("Include deleted control not found")
+
+ if search_opts.similar_sounding:
+ try:
+ self.browser.form.find_control(
+ "form:aenlichLautendeSchlagwoerterBoolChkbox_input"
+ ).value = ["on"]
+ logger.debug("Enabled similar sounding option")
+ except mechanize.ControlNotFoundError:
+ logger.warning("Similar sounding control not found")
+
+ if search_opts.results_per_page in RESULTS_PER_PAGE_OPTIONS:
+ try:
+ self.browser["form:ergebnisseProSeite_input"] = [str(search_opts.results_per_page)]
+ logger.debug("Set results per page: %d", search_opts.results_per_page)
+ except mechanize.ControlNotFoundError:
+ logger.warning("Results per page control not found")
+
+ try:
+ response = self.browser.submit()
+ except urllib.error.URLError as e:
+ error_msg = f"Failed to submit search form: {e.reason}"
+ raise NetworkError(error_msg, original_error=e) from e
+
+ logger.debug("Page title after search: %s", self.browser.title())
+
+ return response.read().decode("utf-8")
+
+ # =========================================================================
+ # Detail Fetching Methods
+ # =========================================================================
+
+ def get_company_details(
+ self,
+ company: Company,
+ detail_type: Literal["SI", "AD", "UT", "CD", "HD", "VĂ"] = "SI",
+ force_refresh: bool = False,
+ fallback_types: Optional[list[Literal["SI", "AD", "UT", "CD", "HD", "VĂ"]]] = None,
+ ) -> CompanyDetails:
+ """Fetches detailed company information with optional fallback strategies.
+
+ Args:
+ company: Company dict from search results (must contain row_index).
+ detail_type: Type of details to fetch:
+ - "SI": Strukturierter Registerinhalt (structured, recommended)
+ - "AD": Aktueller Abdruck (current printout)
+ - "UT": UnternehmenstrÀger (company owners)
+ force_refresh: Skip cache and fetch fresh data.
+ fallback_types: List of alternative detail types to try if primary fails.
+ If None, defaults to ["AD", "UT"] for graceful degradation.
+
+ Returns:
+ CompanyDetails with all available information.
+
+ Raises:
+ NetworkError: If the request fails after all retries and fallbacks.
+ ParseError: If parsing fails for all attempted types.
+ ValueError: If company dict is missing required fields.
+ """
+ valid_types = ["SI", "AD", "UT", "CD", "HD", "VĂ"]
+ if detail_type not in valid_types:
+ error_msg = f"Invalid detail_type: {detail_type}. Must be one of {valid_types}"
+ raise ValueError(error_msg)
+
+ # Default fallback types if not specified
+ if fallback_types is None:
+ fallback_types = ["AD", "UT"]
+
+ # Try primary detail type first
+ types_to_try = [detail_type] + [
+ ft for ft in fallback_types if ft != detail_type and ft in valid_types
+ ]
+ last_error: Optional[Exception] = None
+
+ for attempt_type in types_to_try:
+ cache_key = f"details:{attempt_type}:{company.register_num or ''}:{company.court}"
+
+ try:
+ if not force_refresh:
+ cached_html = self.cache.get(cache_key, "")
+ if cached_html is not None:
+ logger.info("Cache hit for details: %s", cache_key)
+ return self._parse_details(cached_html, company, attempt_type)
+
+ html = self._fetch_detail_page(company, attempt_type)
+ self.cache.set(cache_key, "", html)
+
+ return self._parse_details(html, company, attempt_type)
+
+ except (NetworkError, ParseError) as e:
+ last_error = e
+ if attempt_type != types_to_try[-1]: # Not the last attempt
+ logger.warning(
+ "Failed to fetch %s details for %s, trying fallback: %s",
+ attempt_type,
+ company.name or "unknown",
+ e,
+ )
+ else:
+ # Last attempt failed, re-raise with context
+ raise
+ except Exception as e:
+ last_error = e
+ if attempt_type != types_to_try[-1]:
+ logger.warning(
+ "Unexpected error fetching %s details for %s, trying fallback: %s",
+ attempt_type,
+ company.name or "unknown",
+ e,
+ )
+ else:
+ raise
+
+ # Should never reach here, but just in case
+ if last_error:
+ raise last_error
+ error_msg = "Failed to fetch company details after all attempts"
+ raise NetworkError(error_msg)
+
+ @_with_retry_and_rate_limit
+ def _fetch_detail_page(self, company: Company, detail_type: str) -> str:
+ """Fetches a detail page for a company with retries.
+
+ The Handelsregister uses JSF/PrimeFaces which requires specific
+ form parameters. We reconstruct these based on the search results.
+ Uses exponential backoff for retries on network failures.
+ Rate limited to 60 requests per hour per portal terms of service.
+
+ Args:
+ company: Company dict with at least 'row_index' from search.
+ detail_type: Type of detail page (SI, AD, UT, etc.).
+
+ Returns:
+ HTML content of the detail page.
+ """
+ row_index = company.row_index or 0
+
+ detail_type_mapping = {
+ "AD": "ergebnissForm:selectedSuchErgebnisFormTable:{row}:j_idt161:0:fade",
+ "CD": "ergebnissForm:selectedSuchErgebnisFormTable:{row}:j_idt161:1:fade",
+ "HD": "ergebnissForm:selectedSuchErgebnisFormTable:{row}:j_idt161:2:fade",
+ "UT": "ergebnissForm:selectedSuchErgebnisFormTable:{row}:j_idt161:4:fade",
+ "VĂ": "ergebnissForm:selectedSuchErgebnisFormTable:{row}:j_idt161:5:fade",
+ "SI": "ergebnissForm:selectedSuchErgebnisFormTable:{row}:j_idt161:6:fade",
+ }
+
+ control_name = detail_type_mapping.get(detail_type, detail_type_mapping["SI"])
+ control_name = control_name.format(row=row_index)
+
+ try:
+ self.browser.select_form(name="ergebnissForm")
+ self.browser.form.new_control("hidden", control_name, {"value": control_name})
+ response = self.browser.submit()
+ return response.read().decode("utf-8")
+
+ except mechanize.FormNotFoundError as e:
+ error_msg = (
+ f"Results form not found. Unable to fetch {detail_type} details for "
+ f"{company.name} ({company.register_num or 'N/A'}). "
+ f"The website structure may have changed or the search results page is no longer available."
+ )
+ raise FormError(
+ error_msg,
+ original_error=e,
+ ) from e
+ except urllib.error.URLError as e:
+ error_msg = f"Failed to fetch detail page: {e.reason}"
+ raise NetworkError(error_msg, original_error=e) from e
+
+ def _parse_details(self, html: str, company: Company, detail_type: str) -> CompanyDetails:
+ """Parses detail HTML into CompanyDetails.
+
+ Args:
+ html: HTML content of detail page.
+ company: Base company info from search.
+ detail_type: Type of detail page.
+
+ Returns:
+ Parsed CompanyDetails.
+ """
+ if detail_type == "SI":
+ return DetailsParser.parse_si(html, company)
+ if detail_type == "AD":
+ return DetailsParser.parse_ad(html, company)
+ if detail_type == "UT":
+ return DetailsParser.parse_ut(html, company)
+ return DetailsParser.parse_si(html, company)
+
+ def search_with_details(
+ self,
+ options: SearchOptions,
+ fetch_details: bool = True,
+ detail_type: Literal["SI", "AD", "UT", "CD", "HD", "VĂ"] = "SI",
+ force_refresh: bool = False,
+ show_progress: Optional[bool] = None,
+ continue_on_error: bool = True,
+ raise_partial: bool = False,
+ ) -> list[CompanyDetails]:
+ """Searches for companies and optionally fetches details.
+
+ Args:
+ options: Search options.
+ fetch_details: Whether to fetch details for each result.
+ detail_type: Type of details to fetch (SI, AD, UT).
+ force_refresh: Skip cache.
+ show_progress: Show progress bar (auto-detected if None based on TTY).
+ continue_on_error: Continue processing other companies if one fails.
+ raise_partial: Raise PartialResultError if any failures occur.
+
+ Returns:
+ List of CompanyDetails with full information.
+
+ Raises:
+ PartialResultError: If raise_partial=True and some operations failed.
+ """
+ companies = self.search_with_options(options, force_refresh=force_refresh)
+
+ if not fetch_details:
+ return [CompanyDetails.from_company(c) for c in companies]
+
+ # Auto-detect if we should show progress (only if TTY and more than 1 item)
+ if show_progress is None:
+ show_progress = sys.stdout.isatty() and len(companies) > 1
+
+ results: list[CompanyDetails] = []
+ failed: list[tuple[Company, Exception]] = []
+ iterator = tqdm(
+ companies, desc="Fetching details", unit="company", disable=not show_progress
+ )
+
+ for i, company in enumerate(iterator):
+ company.row_index = i
+ company_name = company.name
+ if show_progress:
+ iterator.set_postfix(name=company_name[:30])
+
+ try:
+ details = self.get_company_details(
+ company, detail_type=detail_type, force_refresh=force_refresh
+ )
+ results.append(details)
+ except (NetworkError, ParseError) as e:
+ if not continue_on_error:
+ raise
+
+ logger.warning(
+ "Failed to fetch details for %s (%s): %s",
+ company_name,
+ company.register_num or "N/A",
+ e,
+ )
+
+ # Try fallback: use basic company info
+ try:
+ fallback = CompanyDetails.from_company(company)
+ results.append(fallback)
+ except Exception:
+ logger.exception("Failed to create fallback for %s", company_name)
+ failed.append((company, e))
+ except Exception as e:
+ # Unexpected error
+ logger.exception("Unexpected error fetching details for %s", company_name)
+ if not continue_on_error:
+ raise
+ failed.append((company, e))
+ # Still try to add basic info
+ with contextlib.suppress(Exception):
+ results.append(CompanyDetails.from_company(company))
+
+ # Raise partial result error if requested and there were failures
+ if raise_partial and failed:
+ error_msg = f"Batch operation completed with {len(failed)} failures out of {len(companies)} total"
+ raise PartialResultError(
+ error_msg,
+ successful=results,
+ failed=failed,
+ )
+
+ return results
diff --git a/handelsregister/constants.py b/handelsregister/constants.py
new file mode 100644
index 00000000..15d5f566
--- /dev/null
+++ b/handelsregister/constants.py
@@ -0,0 +1,145 @@
+"""Constants and configuration for the Handelsregister package."""
+
+from enum import Enum
+from typing import Optional
+
+from yarl import URL
+
+
+class State(str, Enum):
+ """German federal states (BundeslÀnder).
+
+ Usage:
+ >>> from handelsregister import State
+ >>> search("Bank", states=[State.BE, State.HH])
+ """
+
+ BW = "BW" # Baden-WĂŒrttemberg
+ BY = "BY" # Bayern
+ BE = "BE" # Berlin
+ BR = "BR" # Brandenburg
+ HB = "HB" # Bremen
+ HH = "HH" # Hamburg
+ HE = "HE" # Hessen
+ MV = "MV" # Mecklenburg-Vorpommern
+ NI = "NI" # Niedersachsen
+ NW = "NW" # Nordrhein-Westfalen
+ RP = "RP" # Rheinland-Pfalz
+ SL = "SL" # Saarland
+ SN = "SN" # Sachsen
+ ST = "ST" # Sachsen-Anhalt
+ SH = "SH" # Schleswig-Holstein
+ TH = "TH" # ThĂŒringen
+
+ @property
+ def name_de(self) -> str:
+ """Returns the German name of the state."""
+ return STATE_CODES[self.value]
+
+ def __str__(self) -> str:
+ return self.value
+
+
+class KeywordMatch(str, Enum):
+ """Keyword matching options for search.
+
+ Usage:
+ >>> from handelsregister import KeywordMatch
+ >>> search("Bank", keyword_option=KeywordMatch.EXACT)
+ """
+
+ ALL = "all" # All keywords must match
+ MIN = "min" # At least one keyword must match
+ EXACT = "exact" # Exact name match
+
+ @property
+ def form_value(self) -> int:
+ """Returns the form value for this option."""
+ return KEYWORD_OPTIONS[self.value]
+
+ def __str__(self) -> str:
+ return self.value
+
+
+class RegisterType(str, Enum):
+ """Register types in the Handelsregister.
+
+ Usage:
+ >>> from handelsregister import RegisterType
+ >>> search("Bank", register_type=RegisterType.HRB)
+ """
+
+ HRA = "HRA" # Handelsregister A (Partnerships)
+ HRB = "HRB" # Handelsregister B (Corporations)
+ GnR = "GnR" # Genossenschaftsregister (Cooperatives)
+ PR = "PR" # Partnerschaftsregister (Partnerships)
+ VR = "VR" # Vereinsregister (Associations)
+
+ def __str__(self) -> str:
+ return self.value
+
+
+# Mapping of keyword option names to form values
+KEYWORD_OPTIONS: dict[str, int] = {"all": 1, "min": 2, "exact": 3}
+
+# Mapping of states to register type suffixes
+SUFFIX_MAP: dict[str, dict[str, str]] = {
+ "Berlin": {"HRB": " B"},
+ "Bremen": {"HRA": " HB", "HRB": " HB", "GnR": " HB", "VR": " HB", "PR": " HB"},
+}
+
+# German state codes for filtering (bundesland parameters)
+STATE_CODES: dict[str, str] = {
+ "BW": "Baden-WĂŒrttemberg",
+ "BY": "Bayern",
+ "BE": "Berlin",
+ "BR": "Brandenburg",
+ "HB": "Bremen",
+ "HH": "Hamburg",
+ "HE": "Hessen",
+ "MV": "Mecklenburg-Vorpommern",
+ "NI": "Niedersachsen",
+ "NW": "Nordrhein-Westfalen",
+ "RP": "Rheinland-Pfalz",
+ "SL": "Saarland",
+ "SN": "Sachsen",
+ "ST": "Sachsen-Anhalt",
+ "SH": "Schleswig-Holstein",
+ "TH": "ThĂŒringen",
+}
+
+# Register types
+REGISTER_TYPES: list[str] = ["HRA", "HRB", "GnR", "PR", "VR"]
+
+# Results per page options (must be one of these values)
+RESULTS_PER_PAGE_OPTIONS: list[int] = [10, 25, 50, 100]
+
+# For backward compatibility
+schlagwortOptionen = KEYWORD_OPTIONS # noqa: N816
+
+
+def build_url(path: str = "", base_url: Optional[URL] = None, **query_params) -> URL:
+ """Builds a URL from BASE_URL with path and optional query parameters.
+
+ Uses yarl for safe URL construction with proper encoding.
+
+ Args:
+ path: Path to append to BASE_URL (e.g., "rp_web/erweitertesuche.xhtml").
+ base_url: Base URL to use (defaults to settings.base_url_parsed).
+ **query_params: Query parameters to add to the URL.
+
+ Returns:
+ yarl.URL object with the constructed URL.
+
+ Example:
+ >>> url = build_url("rp_web/search", q="Bank", page="1")
+ >>> str(url)
+ 'https://www.handelsregister.de/rp_web/search?q=Bank&page=1'
+ """
+ from .settings import settings
+
+ url_base = base_url if base_url is not None else settings.base_url_parsed
+ url = url_base / path if path else url_base
+ if query_params:
+ url = url.with_query(query_params)
+ return url
diff --git a/handelsregister/exceptions.py b/handelsregister/exceptions.py
new file mode 100644
index 00000000..a158683f
--- /dev/null
+++ b/handelsregister/exceptions.py
@@ -0,0 +1,53 @@
+"""Exception classes for the Handelsregister package."""
+
+from typing import Optional
+
+
+class HandelsregisterError(Exception):
+ """Base exception for all Handelsregister errors."""
+
+
+class NetworkError(HandelsregisterError):
+ """Raised when a network request fails."""
+
+ def __init__(self, message: str, original_error: Optional[Exception] = None):
+ super().__init__(message)
+ self.original_error = original_error
+
+
+class ParseError(HandelsregisterError):
+ """Raised when HTML parsing fails."""
+
+ def __init__(self, message: str, html_snippet: Optional[str] = None):
+ super().__init__(message)
+ self.html_snippet = html_snippet
+
+
+class FormError(HandelsregisterError):
+ """Raised when form interaction fails."""
+
+ def __init__(self, message: str, original_error: Optional[Exception] = None):
+ super().__init__(message)
+ self.original_error = original_error
+
+
+class CacheError(HandelsregisterError):
+ """Raised when cache operations fail."""
+
+
+class PartialResultError(HandelsregisterError):
+ """Raised when a batch operation completes with some failures.
+
+ This exception contains information about which operations succeeded
+ and which failed, allowing for graceful degradation.
+ """
+
+ def __init__(
+ self,
+ message: str,
+ successful: list,
+ failed: list[tuple[object, Exception]],
+ ):
+ super().__init__(message)
+ self.successful = successful
+ self.failed = failed # List of (item, exception) tuples
diff --git a/handelsregister/models.py b/handelsregister/models.py
new file mode 100644
index 00000000..f96cecc4
--- /dev/null
+++ b/handelsregister/models.py
@@ -0,0 +1,330 @@
+"""Data models for the Handelsregister package using Pydantic."""
+
+import time
+from dataclasses import dataclass
+from typing import Any, Optional, Union
+
+from pydantic import BaseModel, ConfigDict, Field, field_validator
+
+from .constants import (
+ RESULTS_PER_PAGE_OPTIONS,
+ STATE_CODES,
+ KeywordMatch,
+ RegisterType,
+ State,
+)
+from .settings import DEFAULT_CACHE_TTL_SECONDS
+
+
+@dataclass
+class CacheEntry:
+ """Represents a cached search result with metadata.
+
+ Note: Kept as dataclass for internal use only. Not part of public API.
+ """
+
+ query: str
+ options: str
+ timestamp: float
+ html: str
+
+ def is_expired(self, ttl_seconds: int = DEFAULT_CACHE_TTL_SECONDS) -> bool:
+ """Checks if the cache entry has expired.
+
+ Args:
+ ttl_seconds: Time-to-live in seconds.
+
+ Returns:
+ True if expired, False otherwise.
+ """
+ return (time.time() - self.timestamp) > ttl_seconds
+
+ def to_dict(self) -> dict:
+ """Converts to dictionary for JSON serialization."""
+ return {
+ "query": self.query,
+ "options": self.options,
+ "timestamp": self.timestamp,
+ "html": self.html,
+ }
+
+ @classmethod
+ def from_dict(cls, data: dict) -> "CacheEntry":
+ """Creates a CacheEntry from a dictionary."""
+ return cls(
+ query=data["query"],
+ options=data["options"],
+ timestamp=data["timestamp"],
+ html=data["html"],
+ )
+
+
+class SearchOptions(BaseModel):
+ """Encapsulates all search parameters for the Handelsregister.
+
+ Uses Pydantic for validation and serialization.
+
+ Attributes:
+ keywords: Search keywords (schlagwoerter).
+ keyword_option: How to match keywords (all, min, exact). Can be KeywordMatch enum or string.
+ states: List of state codes to filter by (e.g., ['BE', 'HH']). Can be State enum or string.
+ register_type: Register type filter (HRA, HRB, GnR, PR, VR). Can be RegisterType enum or string.
+ register_number: Specific register number to search for.
+ include_deleted: Include deleted/historical entries.
+ similar_sounding: Use phonetic/similarity search.
+ results_per_page: Number of results per page (10, 25, 50, 100). Must be in RESULTS_PER_PAGE_OPTIONS.
+ """
+
+ model_config = ConfigDict(frozen=False, validate_assignment=True)
+
+ keywords: str = Field(..., min_length=1, description="Search keywords")
+ keyword_option: Union[str, KeywordMatch] = Field(default="all", pattern="^(all|min|exact)$")
+ states: Optional[list[Union[str, State]]] = Field(
+ default=None, description="State codes to filter by"
+ )
+ register_type: Optional[Union[str, RegisterType]] = Field(
+ default=None, pattern="^(HRA|HRB|GnR|PR|VR)$"
+ )
+ register_number: Optional[str] = None
+ include_deleted: bool = False
+ similar_sounding: bool = False
+ results_per_page: int = Field(default=100, ge=10, le=100)
+
+ @field_validator("keyword_option", mode="before")
+ @classmethod
+ def validate_keyword_option(cls, v: Union[str, KeywordMatch]) -> str:
+ """Accepts both KeywordMatch enum and string."""
+ if isinstance(v, KeywordMatch):
+ return v.value
+ return v
+
+ @field_validator("states", mode="before")
+ @classmethod
+ def validate_states(cls, v: Optional[list[Union[str, State]]]) -> Optional[list[str]]:
+ """Validates state codes against known values. Accepts both State enum and string."""
+ if v is None:
+ return None
+ valid_codes = set(STATE_CODES.keys())
+ result = []
+ for state in v:
+ # Extract value from enum if needed
+ state_value = state.value if isinstance(state, State) else state
+ state_upper = state_value.upper()
+ if state_upper not in valid_codes:
+ error_msg = (
+ f"Invalid state code: {state_value}. "
+ f"Valid: {', '.join(sorted(valid_codes))}"
+ )
+ raise ValueError(error_msg)
+ result.append(state_upper)
+ return result
+
+ @field_validator("register_type", mode="before")
+ @classmethod
+ def validate_register_type(cls, v: Optional[Union[str, RegisterType]]) -> Optional[str]:
+ """Accepts both RegisterType enum and string."""
+ if v is None:
+ return None
+ if isinstance(v, RegisterType):
+ return v.value
+ return v
+
+ @field_validator("results_per_page")
+ @classmethod
+ def validate_results_per_page(cls, v: int) -> int:
+ """Validates results_per_page is a valid option."""
+ if v not in RESULTS_PER_PAGE_OPTIONS:
+ error_msg = f"results_per_page must be one of {RESULTS_PER_PAGE_OPTIONS}"
+ raise ValueError(error_msg)
+ return v
+
+ def cache_key(self) -> str:
+ """Generates a unique key for caching based on all options."""
+ parts = [
+ self.keywords,
+ self.keyword_option,
+ ",".join(sorted(self.states or [])),
+ self.register_type or "",
+ self.register_number or "",
+ str(self.include_deleted),
+ str(self.similar_sounding),
+ str(self.results_per_page),
+ ]
+ return "|".join(parts)
+
+
+class HistoryEntry(BaseModel):
+ """Represents a historical name/location entry for a company."""
+
+ model_config = ConfigDict(frozen=True)
+
+ name: str
+ location: str
+
+
+class Address(BaseModel):
+ """Represents a business address with validation."""
+
+ model_config = ConfigDict(frozen=False)
+
+ street: Optional[str] = None
+ postal_code: Optional[str] = Field(default=None, pattern=r"^\d{5}$|^$|None")
+ city: Optional[str] = None
+ country: str = "Deutschland"
+
+ @field_validator("postal_code", mode="before")
+ @classmethod
+ def validate_postal_code(cls, v: Any) -> Optional[str]:
+ """Allow None or valid German postal codes."""
+ if v is None or v == "":
+ return None
+ if isinstance(v, str) and len(v) == 5 and v.isdigit():
+ return v
+ # Be lenient - just return as-is for non-standard codes
+ return str(v) if v else None
+
+ def __str__(self) -> str:
+ """Formats address as string."""
+ parts = []
+ if self.street:
+ parts.append(self.street)
+ if self.postal_code and self.city:
+ parts.append(f"{self.postal_code} {self.city}")
+ elif self.city:
+ parts.append(self.city)
+ if self.country and self.country != "Deutschland":
+ parts.append(self.country)
+ return ", ".join(parts) if parts else ""
+
+ def to_dict(self) -> dict:
+ """Convert to dictionary (for backward compatibility)."""
+ return self.model_dump()
+
+
+class Representative(BaseModel):
+ """Represents a company representative (GeschĂ€ftsfĂŒhrer, Vorstand, etc.)."""
+
+ model_config = ConfigDict(frozen=False)
+
+ name: str = Field(..., min_length=1, description="Name of the representative")
+ role: str = Field(..., description="Role (e.g., GeschĂ€ftsfĂŒhrer, Vorstand)")
+ location: Optional[str] = None
+ birth_date: Optional[str] = None
+ restrictions: Optional[str] = None # e.g., "einzelvertretungsberechtigt"
+
+ def to_dict(self) -> dict:
+ """Converts to dictionary (for backward compatibility)."""
+ return self.model_dump()
+
+
+class Owner(BaseModel):
+ """Represents a company owner/shareholder (Gesellschafter)."""
+
+ model_config = ConfigDict(frozen=False)
+
+ name: str = Field(..., min_length=1, description="Name of the owner")
+ share: Optional[str] = None # e.g., "50%", "25.000 EUR"
+ owner_type: Optional[str] = None # e.g., "Kommanditist", "Gesellschafter"
+ location: Optional[str] = None
+
+ def to_dict(self) -> dict:
+ """Converts to dictionary (for backward compatibility)."""
+ return self.model_dump()
+
+
+class CompanyDetails(BaseModel):
+ """Extended company information from detail views.
+
+ Contains all information available from the Handelsregister detail
+ views (AD, SI, UT). Uses Pydantic for validation and serialization.
+ """
+
+ model_config = ConfigDict(frozen=False, validate_assignment=True)
+
+ # Basic identification (from search results)
+ name: str = Field(..., description="Company name")
+ register_num: str = Field(default="", description="Register number (e.g., HRB 12345 B)")
+ court: str = Field(default="", description="Registration court")
+ state: str = Field(default="", description="Federal state")
+ status: str = Field(default="", description="Registration status")
+
+ # Extended information (from detail views)
+ legal_form: Optional[str] = Field(default=None, description="Legal form (AG, GmbH, KG, etc.)")
+ capital: Optional[str] = Field(default=None, description="Share capital / Stammkapital")
+ currency: Optional[str] = Field(default=None, description="Currency (EUR, etc.)")
+ address: Optional[Address] = None
+ purpose: Optional[str] = Field(
+ default=None, description="Business purpose / Unternehmensgegenstand"
+ )
+ representatives: list[Representative] = Field(default_factory=list)
+ owners: list[Owner] = Field(default_factory=list)
+ registration_date: Optional[str] = Field(default=None, description="Registration date")
+ last_update: Optional[str] = Field(default=None, description="Last update date")
+ deletion_date: Optional[str] = Field(default=None, description="Deletion date (if deleted)")
+
+ # Additional metadata
+ raw_data: Optional[dict] = Field(default=None, repr=False, exclude=True)
+
+ def to_dict(self) -> dict:
+ """Converts to dictionary for JSON serialization (backward compatibility)."""
+ # Pydantic's model_dump() automatically handles nested models
+ return self.model_dump(exclude={"raw_data"}, mode="python")
+
+ @classmethod
+ def from_company(cls, company: Union["Company", dict[str, Any]]) -> "CompanyDetails":
+ """Creates CompanyDetails from a Company search result or dict.
+
+ Args:
+ company: Company object or dict with company information.
+
+ Returns:
+ CompanyDetails with basic information from the company.
+ """
+ if isinstance(company, dict):
+ # Backward compatibility: accept dict
+ return cls(
+ name=company.get("name", ""),
+ register_num=company.get("register_num", "") or "",
+ court=company.get("court", ""),
+ state=company.get("state", ""),
+ status=company.get("status", ""),
+ )
+ # Company object
+ return cls(
+ name=company.name,
+ register_num=company.register_num or "",
+ court=company.court,
+ state=company.state,
+ status=company.status,
+ )
+
+
+class Company(BaseModel):
+ """Represents a company record from the Handelsregister.
+
+ This is the primary model for search results. It provides validation
+ and type safety while maintaining backward compatibility with dict access.
+ """
+
+ model_config = ConfigDict(frozen=False, populate_by_name=True)
+
+ court: str
+ name: str
+ state: str
+ status: str
+ status_normalized: str = Field(default="", alias="statusCurrent")
+ documents: str
+ register_num: Optional[str] = None
+ history: list[HistoryEntry] = Field(default_factory=list)
+ row_index: Optional[int] = Field(default=None, exclude=True) # Internal use for detail fetching
+
+ def to_dict(self) -> dict:
+ """Converts to dictionary for backward compatibility."""
+ data = self.model_dump(by_alias=True, exclude={"row_index"})
+ # Convert history from HistoryEntry objects to tuples for backward compatibility
+ data["history"] = [(h.name, h.location) for h in self.history]
+ return data
+
+ def get(self, key: str, default: Any = None) -> Any:
+ """Dict-like access for backward compatibility."""
+ return getattr(self, key, default)
diff --git a/handelsregister/parser.py b/handelsregister/parser.py
new file mode 100644
index 00000000..68286375
--- /dev/null
+++ b/handelsregister/parser.py
@@ -0,0 +1,591 @@
+"""HTML parsing layer for the Handelsregister package."""
+
+import re
+from typing import Any, Optional, Union
+
+from bs4 import BeautifulSoup
+from bs4.element import Tag
+from dateutil import parser as dateutil_parser
+from dateutil.parser import ParserError
+
+from .constants import SUFFIX_MAP
+from .exceptions import ParseError
+from .models import Address, Company, CompanyDetails, HistoryEntry, Owner, Representative
+
+
+class DetailsParser:
+ """Parses detail view HTML (SI, AD, UT) into CompanyDetails objects."""
+
+ # Common patterns for extracting data
+ CAPITAL_PATTERN = re.compile(
+ r"(?:Stamm|Grund)kapital[:\s]*([0-9.,]+)\s*(EUR|âŹ|DM)?", re.IGNORECASE
+ )
+ DATE_PATTERN = re.compile(r"\d{1,2}\.\d{1,2}\.\d{4}")
+
+ @classmethod
+ def parse_date(cls, text: str, output_format: str = "%d.%m.%Y") -> Optional[str]:
+ """Parses a date from text using dateutil.
+
+ Handles German date formats (DD.MM.YYYY) and various other formats.
+ Returns the date in a normalized format.
+
+ Args:
+ text: Text containing a date.
+ output_format: Output format for the date string.
+
+ Returns:
+ Normalized date string, or None if no date found.
+ """
+ # First try to find a German-style date pattern
+ date_match = cls.DATE_PATTERN.search(text)
+ if date_match:
+ date_str = date_match.group(0)
+ try:
+ # Parse with dayfirst=True for German DD.MM.YYYY format
+ parsed = dateutil_parser.parse(date_str, dayfirst=True)
+ return parsed.strftime(output_format)
+ except (ParserError, ValueError):
+ # If dateutil fails, return the original match
+ return date_str
+
+ # Try dateutil on the entire text as fallback
+ try:
+ parsed = dateutil_parser.parse(text, dayfirst=True, fuzzy=True)
+ return parsed.strftime(output_format)
+ except (ParserError, ValueError):
+ return None
+
+ @classmethod
+ def parse_si(
+ cls, html: str, base_info: Optional[Union[Company, dict[str, Any]]] = None
+ ) -> CompanyDetails:
+ """Parses structured register content (SI - Strukturierter Registerinhalt).
+
+ Args:
+ html: HTML content of the SI detail view.
+ base_info: Optional base company info from search results (Company or dict).
+
+ Returns:
+ CompanyDetails with parsed information.
+ """
+ soup = BeautifulSoup(html, "html.parser")
+
+ # Initialize with base info or empty
+ if base_info:
+ if isinstance(base_info, dict):
+ # Backward compatibility: accept dict
+ details = CompanyDetails(
+ name=base_info.get("name", ""),
+ register_num=base_info.get("register_num", "") or "",
+ court=base_info.get("court", ""),
+ state=base_info.get("state", ""),
+ status=base_info.get("status", ""),
+ )
+ else:
+ # Company object
+ details = CompanyDetails(
+ name=base_info.name,
+ register_num=base_info.register_num or "",
+ court=base_info.court,
+ state=base_info.state,
+ status=base_info.status,
+ )
+ else:
+ details = CompanyDetails(
+ name="",
+ register_num="",
+ court="",
+ state="",
+ status="",
+ )
+
+ # Parse structured content - typically in tables or definition lists
+ details = cls._parse_si_tables(soup, details)
+ return cls._parse_si_sections(soup, details)
+
+ @classmethod
+ def _parse_si_tables(cls, soup: BeautifulSoup, details: CompanyDetails) -> CompanyDetails:
+ """Extracts data from SI tables."""
+ tables = soup.find_all("table")
+
+ for table in tables:
+ rows = table.find_all("tr")
+ for row in rows:
+ cells = row.find_all(["td", "th"])
+ if len(cells) >= 2:
+ label = cells[0].get_text(strip=True).lower()
+ value = cells[1].get_text(strip=True)
+
+ details = cls._map_field(label, value, details)
+
+ return details
+
+ @classmethod
+ def _parse_si_sections(cls, soup: BeautifulSoup, details: CompanyDetails) -> CompanyDetails:
+ """Extracts data from SI sections (divs, panels, etc.)."""
+ for div in soup.find_all(["div", "span", "p"]):
+ text = div.get_text(strip=True)
+
+ if details.capital is None:
+ capital_match = cls.CAPITAL_PATTERN.search(text)
+ if capital_match:
+ details.capital = capital_match.group(1)
+ if capital_match.group(2):
+ details.currency = capital_match.group(2).replace("âŹ", "EUR")
+
+ if details.legal_form is None:
+ details.legal_form = cls._extract_legal_form(text)
+
+ reps = cls._extract_representatives(div)
+ if reps:
+ details.representatives.extend(reps)
+
+ return details
+
+ @classmethod
+ def _map_field(cls, label: str, value: str, details: CompanyDetails) -> CompanyDetails: # noqa: PLR0912
+ """Maps a label-value pair to the appropriate CompanyDetails field."""
+ if not value:
+ return details
+
+ if any(x in label for x in ["firma", "name"]) and not details.name:
+ details.name = value
+ elif "rechtsform" in label:
+ details.legal_form = value
+ elif "sitz" in label or "geschÀftsanschrift" in label:
+ details.address = cls._parse_address(value)
+ elif "kapital" in label:
+ amount_pattern = re.match(r"([0-9.,]+)\s*(EUR|âŹ|DM)?", value)
+ if amount_pattern:
+ details.capital = amount_pattern.group(1).strip()
+ if amount_pattern.group(2):
+ details.currency = amount_pattern.group(2).replace("âŹ", "EUR")
+ else:
+ details.capital = value
+ elif "gegenstand" in label or "unternehmensgegenstand" in label:
+ details.purpose = value
+ elif "registernummer" in label or "aktenzeichen" in label:
+ if not details.register_num:
+ details.register_num = value
+ elif "eintrag" in label and "datum" in label:
+ details.registration_date = cls.parse_date(value) or value
+ elif "lösch" in label:
+ details.deletion_date = cls.parse_date(value) or value
+ elif "Ă€nderung" in label or "aktualisiert" in label:
+ details.last_update = cls.parse_date(value) or value
+
+ return details
+
+ @classmethod
+ def _parse_address(cls, text: str) -> Address:
+ """Parses an address string into an Address object."""
+ plz_city_match = re.search(r"(\d{5})\s+(.+?)(?:,|$)", text)
+
+ if plz_city_match:
+ postal_code = plz_city_match.group(1)
+ city = plz_city_match.group(2).strip()
+ street_part = text[: plz_city_match.start()].strip().rstrip(",")
+ return Address(
+ street=street_part if street_part else None,
+ postal_code=postal_code,
+ city=city,
+ )
+ return Address(city=text)
+
+ @classmethod
+ def _extract_legal_form(cls, text: str) -> Optional[str]:
+ """Extracts legal form from text.
+
+ Order matters: more specific forms (like GmbH & Co. KG) must be
+ checked before less specific ones (like GmbH or KG).
+ """
+ legal_forms = [
+ ("GmbH & Co. KG", "GmbH & Co. KG"),
+ ("GmbH & Co. OHG", "GmbH & Co. OHG"),
+ ("UG (haftungsbeschrÀnkt) & Co. KG", "UG & Co. KG"),
+ ("EuropÀische Aktiengesellschaft", "SE"),
+ ("Aktiengesellschaft", "AG"),
+ ("Gesellschaft mit beschrÀnkter Haftung", "GmbH"),
+ ("UG (haftungsbeschrÀnkt)", "UG"),
+ ("Kommanditgesellschaft", "KG"),
+ ("Offene Handelsgesellschaft", "OHG"),
+ ("Eingetragene Genossenschaft", "eG"),
+ ("Eingetragener Verein", "e.V."),
+ ("Partnerschaftsgesellschaft", "PartG"),
+ ("Einzelkaufmann", "e.K."),
+ ("Einzelkauffrau", "e.Kfr."),
+ ]
+
+ text_lower = text.lower()
+ for full_name, abbreviation in legal_forms:
+ if full_name.lower() in text_lower:
+ return full_name
+ if f" {abbreviation}" in text or text.endswith(abbreviation):
+ return full_name
+ if abbreviation in text and "&" in abbreviation:
+ return full_name
+
+ return None
+
+ @classmethod
+ def _extract_representatives(cls, element: Tag) -> list[Representative]:
+ """Extracts representative information from an element."""
+ representatives = []
+ text = element.get_text()
+
+ role_patterns = [
+ (r"GeschĂ€ftsfĂŒhrer(?:in)?[:\s]+([^,;]+)", "GeschĂ€ftsfĂŒhrer"),
+ (r"Vorstand[:\s]+([^,;]+)", "Vorstand"),
+ (r"Prokurist(?:in)?[:\s]+([^,;]+)", "Prokurist"),
+ (r"Inhaber(?:in)?[:\s]+([^,;]+)", "Inhaber"),
+ (
+ r"Persönlich haftende(?:r)? Gesellschafter(?:in)?[:\s]+([^,;]+)",
+ "Persönlich haftender Gesellschafter",
+ ),
+ ]
+
+ for pattern, role in role_patterns:
+ matches = re.finditer(pattern, text, re.IGNORECASE)
+ for match in matches:
+ name = match.group(1).strip()
+ if name and len(name) > 2:
+ location = None
+ loc_match = re.search(r"\(([^)]+)\)", name)
+ if loc_match:
+ location = loc_match.group(1)
+ name = name[: loc_match.start()].strip()
+
+ representatives.append(
+ Representative(
+ name=name,
+ role=role,
+ location=location,
+ )
+ )
+
+ return representatives
+
+ @classmethod
+ def parse_ad(
+ cls, html: str, base_info: Optional[Union[Company, dict[str, Any]]] = None
+ ) -> CompanyDetails:
+ """Parses current printout (AD - Aktueller Abdruck).
+
+ The AD view contains the current state of the register entry as
+ formatted text rather than structured tables.
+
+ Args:
+ html: HTML content of the AD detail view.
+ base_info: Optional base company info from search results.
+
+ Returns:
+ CompanyDetails with parsed information.
+ """
+ soup = BeautifulSoup(html, "html.parser")
+
+ details = CompanyDetails(
+ name=base_info.get("name", "") if base_info else "",
+ register_num=base_info.get("register_num", "") if base_info else "",
+ court=base_info.get("court", "") if base_info else "",
+ state=base_info.get("state", "") if base_info else "",
+ status=base_info.get("status", "") if base_info else "",
+ )
+
+ content_div = soup.find("div", class_=re.compile(r"content|abdruck|register", re.I))
+ if content_div is None:
+ content_div = soup.find("body")
+
+ if content_div:
+ text = content_div.get_text()
+
+ details.legal_form = cls._extract_legal_form(text)
+
+ capital_match = cls.CAPITAL_PATTERN.search(text)
+ if capital_match:
+ details.capital = capital_match.group(1)
+ if capital_match.group(2):
+ details.currency = capital_match.group(2).replace("âŹ", "EUR")
+
+ purpose_match = re.search(
+ r"Gegenstand(?:\s+des\s+Unternehmens)?[:\s]*(.+?)(?:Stammkapital|Grundkapital|GeschĂ€ftsfĂŒhrer|Vorstand|Vertretung|$)",
+ text,
+ re.IGNORECASE | re.DOTALL,
+ )
+ if purpose_match:
+ details.purpose = purpose_match.group(1).strip()
+
+ details.representatives = cls._extract_representatives_from_text(text)
+ details = cls._parse_si_tables(soup, details)
+
+ return details
+
+ @classmethod
+ def parse_ut(
+ cls, html: str, base_info: Optional[Union[Company, dict[str, Any]]] = None
+ ) -> CompanyDetails:
+ """Parses company owner information (UT - UnternehmenstrÀger).
+
+ The UT view focuses on ownership and shareholder information.
+
+ Args:
+ html: HTML content of the UT detail view.
+ base_info: Optional base company info from search results.
+
+ Returns:
+ CompanyDetails with owner information.
+ """
+ soup = BeautifulSoup(html, "html.parser")
+
+ if base_info:
+ if isinstance(base_info, dict):
+ details = CompanyDetails(
+ name=base_info.get("name", ""),
+ register_num=base_info.get("register_num", "") or "",
+ court=base_info.get("court", ""),
+ state=base_info.get("state", ""),
+ status=base_info.get("status", ""),
+ )
+ else:
+ details = CompanyDetails(
+ name=base_info.name,
+ register_num=base_info.register_num or "",
+ court=base_info.court,
+ state=base_info.state,
+ status=base_info.status,
+ )
+ else:
+ details = CompanyDetails(
+ name="",
+ register_num="",
+ court="",
+ state="",
+ status="",
+ )
+
+ details = cls._parse_si_tables(soup, details)
+ text = soup.get_text()
+ details.owners = cls._extract_owners(text)
+ details.representatives = cls._extract_representatives_from_text(text)
+
+ return details
+
+ @classmethod
+ def _extract_representatives_from_text(cls, text: str) -> list[Representative]:
+ """Extracts all representatives from free-form text."""
+ representatives = []
+ seen_names = set()
+
+ patterns = [
+ (
+ r"GeschĂ€ftsfĂŒhrer(?:in)?[:\s]*([A-ZĂĂĂ][a-zĂ€Ă¶ĂŒĂ]+(?:\s+[A-ZĂĂĂ][a-zĂ€Ă¶ĂŒĂ]+)+)",
+ "GeschĂ€ftsfĂŒhrer",
+ ),
+ (r"Vorstand[:\s]*([A-ZĂĂĂ][a-zĂ€Ă¶ĂŒĂ]+(?:\s+[A-ZĂĂĂ][a-zĂ€Ă¶ĂŒĂ]+)+)", "Vorstand"),
+ (r"Prokurist(?:in)?[:\s]*([A-ZĂĂĂ][a-zĂ€Ă¶ĂŒĂ]+(?:\s+[A-ZĂĂĂ][a-zĂ€Ă¶ĂŒĂ]+)+)", "Prokurist"),
+ (
+ r"Persönlich\s+haftende[r]?\s+Gesellschafter(?:in)?[:\s]*([A-ZĂĂĂ][^\n,;]+)",
+ "Persönlich haftender Gesellschafter",
+ ),
+ ]
+
+ for pattern, role in patterns:
+ for match in re.finditer(pattern, text):
+ name = match.group(1).strip()
+ name = re.sub(r"\s*\([^)]*\)\s*", "", name).strip()
+ name = re.sub(r"\s+", " ", name)
+
+ if name and len(name) > 3 and name not in seen_names:
+ seen_names.add(name)
+ location = None
+ full_match = match.group(0)
+ loc_match = re.search(r"\(([^)]+)\)", full_match)
+ if loc_match:
+ location = loc_match.group(1)
+
+ representatives.append(
+ Representative(
+ name=name,
+ role=role,
+ location=location,
+ )
+ )
+
+ return representatives
+
+ @classmethod
+ def _extract_owners(cls, text: str) -> list[Owner]:
+ """Extracts owner/shareholder information from text."""
+ owners = []
+ seen_names = set()
+
+ owner_patterns = [
+ (
+ r"Gesellschafter[:\s]+([^,\n]+?)(?:[,\s]+(?:Anteil|Einlage)[:\s]*([0-9.,]+\s*(?:EUR|âŹ|%)))?(?:\n|$|,)",
+ "Gesellschafter",
+ ),
+ (
+ r"Kommanditist(?:in)?[:\s]+([^,\n]+?)(?:[,\s]+(?:Anteil|Einlage|Haftsumme)[:\s]*([0-9.,]+\s*(?:EUR|âŹ|%)))?(?:\n|$|,)",
+ "Kommanditist",
+ ),
+ (r"KomplementÀr(?:in)?[:\s]+([^,\n]+)", "KomplementÀr"),
+ ]
+
+ for pattern, owner_type in owner_patterns:
+ for match in re.finditer(pattern, text, re.IGNORECASE):
+ name = match.group(1).strip()
+ name = re.sub(r"\s+", " ", name)
+ share = None
+ if len(match.groups()) > 1 and match.group(2):
+ share = match.group(2).strip()
+
+ if name and len(name) > 2 and name not in seen_names:
+ seen_names.add(name)
+ owners.append(
+ Owner(
+ name=name,
+ share=share,
+ owner_type=owner_type,
+ )
+ )
+
+ return owners
+
+
+class ResultParser:
+ """Parses HTML search results into structured company data."""
+
+ @staticmethod
+ def parse_search_results(html: str) -> list[Company]:
+ """Extracts company records from search results HTML.
+
+ Args:
+ html: HTML content of the search results page.
+
+ Returns:
+ List of Company objects with company information.
+ """
+ soup = BeautifulSoup(html, "html.parser")
+ grid = soup.find("table", role="grid")
+
+ results: list[Company] = []
+ if grid is None:
+ return results
+
+ for row in grid.find_all("tr"):
+ data_ri = row.get("data-ri")
+ if data_ri is not None:
+ company_data = ResultParser.parse_result_row(row)
+ results.append(Company.model_validate(company_data))
+
+ return results
+
+ @staticmethod
+ def parse_result_row(row: Tag) -> dict[str, Any]:
+ """Parses a single search result row into a company dictionary.
+
+ This returns a dict that can be validated into a Company model.
+ Use parse_search_results() to get Company objects directly.
+
+ Args:
+ row: BeautifulSoup Tag representing a table row.
+
+ Returns:
+ Dictionary containing company information (ready for Company.model_validate).
+
+ Raises:
+ ParseError: If the result row has unexpected structure.
+ """
+ cells: list[str] = [cell.text.strip() for cell in row.find_all("td")]
+
+ if len(cells) < 6:
+ error_msg = f"Expected at least 6 cells in result row, got {len(cells)}"
+ raise ParseError(
+ error_msg,
+ html_snippet=str(row)[:500],
+ )
+
+ court = cells[1]
+ state = cells[3]
+ status = cells[4].strip()
+
+ # Extract register number
+ register_num = ResultParser._extract_register_number(court, state)
+
+ # Parse history entries
+ history_tuples = ResultParser._parse_history(cells)
+ history = [HistoryEntry(name=name, location=location) for name, location in history_tuples]
+
+ return {
+ "court": court,
+ "register_num": register_num,
+ "name": cells[2],
+ "state": state,
+ "status": status,
+ "statusCurrent": status.upper().replace(" ", "_"),
+ "documents": cells[5],
+ "history": history,
+ }
+
+ @staticmethod
+ def _extract_register_number(court: str, state: str) -> Optional[str]:
+ """Extracts and normalizes the register number from court string.
+
+ Args:
+ court: Court field containing the register number.
+ state: State, used to add appropriate suffix.
+
+ Returns:
+ Normalized register number, or None if not found.
+ """
+ reg_match = re.search(r"(HRA|HRB|GnR|VR|PR)\s*\d+(\s+[A-Z])?(?!\w)", court)
+
+ if not reg_match:
+ return None
+
+ register_num = reg_match.group(0)
+ reg_type = register_num.split()[0]
+ suffix = SUFFIX_MAP.get(state, {}).get(reg_type)
+ if suffix and not register_num.endswith(suffix):
+ register_num += suffix
+
+ return register_num
+
+ @staticmethod
+ def _parse_history(cells: list[str]) -> list[tuple[str, str]]:
+ """Parses history entries from cell data.
+
+ Args:
+ cells: List of cell text content.
+
+ Returns:
+ List of (name, location) tuples.
+ """
+ history: list[tuple[str, str]] = []
+ hist_start = 8
+
+ for i in range(hist_start, len(cells), 3):
+ if i + 1 >= len(cells):
+ break
+ if "Branches" in cells[i] or "Niederlassungen" in cells[i]:
+ break
+ history.append((cells[i], cells[i + 1]))
+
+ return history
+
+
+# Backward-compatible function aliases
+def parse_result(result: Tag) -> Company:
+ """Parses a single search result row into a Company object.
+
+ Deprecated: Use ResultParser.parse_result_row() and Company.model_validate() instead.
+ """
+ data = ResultParser.parse_result_row(result)
+ return Company.model_validate(data)
+
+
+def get_companies_in_searchresults(html: str) -> list[Company]:
+ """Extracts company records from search results HTML.
+
+ Deprecated: Use ResultParser.parse_search_results() instead.
+ """
+ return ResultParser.parse_search_results(html)
diff --git a/handelsregister/settings.py b/handelsregister/settings.py
new file mode 100644
index 00000000..f46e46e1
--- /dev/null
+++ b/handelsregister/settings.py
@@ -0,0 +1,81 @@
+"""Settings and configuration management using pydantic-settings."""
+
+from typing import Optional
+
+from pydantic import Field
+from pydantic_settings import BaseSettings, SettingsConfigDict
+from yarl import URL
+
+
+class Settings(BaseSettings):
+ """Centralized configuration for the Handelsregister client.
+
+ All settings can be overridden via environment variables with the
+ HRG_ prefix. For example:
+
+ export HRG_CACHE_TTL_SECONDS=7200
+ export HRG_DEBUG=true
+ export HRG_CACHE_DIR=/tmp/hr-cache
+
+ Attributes:
+ cache_ttl_seconds: TTL for search result cache (default: 1 hour).
+ details_ttl_seconds: TTL for details cache (default: 24 hours).
+ base_url: Base URL for the Handelsregister portal.
+ request_timeout: HTTP request timeout in seconds.
+ max_retries: Maximum retry attempts for failed requests.
+ retry_wait_min: Minimum wait between retries in seconds.
+ retry_wait_max: Maximum wait between retries in seconds.
+ rate_limit_calls: Maximum requests per rate limit period.
+ rate_limit_period: Rate limit period in seconds (default: 1 hour).
+ cache_dir: Optional custom cache directory path.
+ debug: Enable debug logging.
+ """
+
+ model_config = SettingsConfigDict(
+ env_prefix="HRG_",
+ case_sensitive=False,
+ env_file=".env",
+ env_file_encoding="utf-8",
+ extra="ignore",
+ )
+
+ # Cache settings
+ cache_ttl_seconds: int = Field(default=3600, description="TTL for search cache in seconds")
+ details_ttl_seconds: int = Field(default=86400, description="TTL for details cache in seconds")
+ cache_dir: Optional[str] = Field(default=None, description="Custom cache directory path")
+
+ # Network settings
+ base_url: str = Field(default="https://www.handelsregister.de", description="Base URL")
+ request_timeout: int = Field(default=10, ge=1, le=60, description="Request timeout in seconds")
+
+ # Retry settings
+ max_retries: int = Field(default=3, ge=1, le=10, description="Maximum retry attempts")
+ retry_wait_min: int = Field(default=2, ge=1, description="Minimum retry wait in seconds")
+ retry_wait_max: int = Field(default=10, ge=1, description="Maximum retry wait in seconds")
+
+ # Rate limiting (per portal terms of service: max 60 requests/hour)
+ rate_limit_calls: int = Field(default=60, ge=1, description="Max requests per period")
+ rate_limit_period: int = Field(default=3600, description="Rate limit period in seconds")
+
+ # Debug settings
+ debug: bool = Field(default=False, description="Enable debug logging")
+
+ @property
+ def base_url_parsed(self) -> URL:
+ """Returns base_url as a yarl.URL object."""
+ return URL(self.base_url)
+
+
+# Initialize global settings (can be overridden by environment variables)
+settings = Settings()
+
+# Backward-compatible constants (use settings.xxx for new code)
+DEFAULT_CACHE_TTL_SECONDS: int = settings.cache_ttl_seconds
+DETAILS_CACHE_TTL_SECONDS: int = settings.details_ttl_seconds
+BASE_URL: URL = settings.base_url_parsed
+REQUEST_TIMEOUT: int = settings.request_timeout
+MAX_RETRIES: int = settings.max_retries
+RETRY_WAIT_MIN: int = settings.retry_wait_min
+RETRY_WAIT_MAX: int = settings.retry_wait_max
+RATE_LIMIT_CALLS: int = settings.rate_limit_calls
+RATE_LIMIT_PERIOD: int = settings.rate_limit_period
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 00000000..5bea98a3
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,194 @@
+site_name: Handelsregister Documentation
+site_description: Python package for the German commercial register portal
+site_author: BundesAPI Contributors
+site_url: https://bundesapi.github.io/handelsregister
+
+repo_name: bundesAPI/handelsregister
+repo_url: https://github.com/bundesAPI/handelsregister
+
+copyright: Copyright © 2025 BundesAPI Contributors
+
+theme:
+ name: material
+ language: en
+ palette:
+ - scheme: default
+ primary: indigo
+ accent: blue
+ toggle:
+ icon: material/brightness-7
+ name: Switch to dark mode
+ - scheme: slate
+ primary: indigo
+ accent: blue
+ toggle:
+ icon: material/brightness-4
+ name: Switch to light mode
+ font:
+ text: Fira Sans
+ code: Fira Code
+ features:
+ - navigation.tabs
+ - navigation.tabs.sticky
+ - navigation.sections
+ - navigation.expand
+ - navigation.path
+ - navigation.top
+ - navigation.footer
+ - search.suggest
+ - search.highlight
+ - content.code.copy
+ - content.code.annotate
+ - content.tabs.link
+ - toc.follow
+ icon:
+ repo: fontawesome/brands/github
+ logo: material/file-document-multiple
+
+plugins:
+ - search
+ - i18n:
+ docs_structure: suffix
+ fallback_to_default: true
+ reconfigure_material: true
+ reconfigure_search: true
+ languages:
+ - locale: en
+ default: true
+ name: English
+ build: true
+ - locale: de
+ name: Deutsch
+ build: true
+ nav_translations:
+ Home: Startseite
+ Getting Started: Erste Schritte
+ Installation: Installation
+ Quickstart: Schnellstart
+ User Guide: User Guide
+ Overview: Ăbersicht
+ Using as Library: Als Library verwenden
+ Command Line (CLI): Kommandozeile (CLI)
+ Fetching Details: Unternehmensdetails abrufen
+ Caching: Caching
+ API Reference: Quellcodebeschreibung
+ Public Functions: Ăffentliche Funktionen
+ Classes: Klassen
+ Data Models: Datenmodelle
+ Exceptions: Exceptions
+ Reference Tables: Referenztabellen
+ State Codes: BundeslÀnder-Codes
+ Register Types: Registerarten
+ Legal Forms: Rechtsformen
+ API Parameters: API-Parameter
+ Examples: Beispiele
+ Simple: Einfache
+ Advanced: Fortgeschritten
+ Integrations: Integrationen
+ Legal Notice: Rechtliche Hinweise
+ Changelog: Changelog
+ - mkdocstrings:
+ default_handler: python
+ handlers:
+ python:
+ paths: ["."]
+ options:
+ docstring_style: google
+ show_source: true
+ show_root_heading: true
+ show_root_full_path: false
+ show_symbol_type_heading: true
+ show_symbol_type_toc: true
+ members_order: source
+ show_signature_annotations: true
+ separate_signature: true
+ signature_crossrefs: true
+ merge_init_into_class: true
+ docstring_section_style: spacy
+ heading_level: 2
+
+markdown_extensions:
+ - abbr
+ - admonition
+ - attr_list
+ - def_list
+ - footnotes
+ - md_in_html
+ - tables
+ - toc:
+ permalink: true
+ title: On this page
+ - pymdownx.arithmatex:
+ generic: true
+ - pymdownx.betterem:
+ smart_enable: all
+ - pymdownx.caret
+ - pymdownx.details
+ - pymdownx.emoji:
+ emoji_index: !!python/name:material.extensions.emoji.twemoji
+ emoji_generator: !!python/name:material.extensions.emoji.to_svg
+ - pymdownx.highlight:
+ anchor_linenums: true
+ line_spans: __span
+ pygments_lang_class: true
+ - pymdownx.inlinehilite
+ - pymdownx.keys
+ - pymdownx.magiclink:
+ repo_url_shorthand: true
+ user: bundesAPI
+ repo: handelsregister
+ - pymdownx.mark
+ - pymdownx.smartsymbols
+ - pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format
+ - pymdownx.tabbed:
+ alternate_style: true
+ - pymdownx.tasklist:
+ custom_checkbox: true
+ - pymdownx.tilde
+
+extra:
+ social:
+ - icon: fontawesome/brands/github
+ link: https://github.com/bundesAPI
+ name: BundesAPI on GitHub
+ generator: false
+ alternate:
+ - name: English
+ link: /handelsregister/
+ lang: en
+ - name: Deutsch
+ link: /handelsregister/de/
+ lang: de
+
+nav:
+ - Home: index.md
+ - Getting Started:
+ - Installation: installation.md
+ - Quickstart: quickstart.md
+ - User Guide:
+ - Overview: guide/index.md
+ - Using as Library: guide/library.md
+ - Command Line (CLI): guide/cli.md
+ - Fetching Details: guide/details.md
+ - Caching: guide/cache.md
+ - API Reference:
+ - Overview: api/index.md
+ - Public Functions: api/functions.md
+ - Classes: api/classes.md
+ - Data Models: api/models.md
+ - Exceptions: api/exceptions.md
+ - Reference Tables:
+ - State Codes: reference/states.md
+ - Register Types: reference/registers.md
+ - Legal Forms: reference/legal-forms.md
+ - API Parameters: reference/parameters.md
+ - Examples:
+ - Simple Examples: examples/simple.md
+ - Advanced: examples/advanced.md
+ - Integrations: examples/integrations.md
+ - Legal Notice: legal.md
+ - Changelog: changelog.md
diff --git a/poetry.lock b/poetry.lock
deleted file mode 100644
index 4538f8b5..00000000
--- a/poetry.lock
+++ /dev/null
@@ -1,508 +0,0 @@
-[[package]]
-name = "beautifulsoup4"
-version = "4.11.1"
-description = "Screen-scraping library"
-category = "main"
-optional = false
-python-versions = ">=3.6.0"
-
-[package.dependencies]
-soupsieve = ">1.2"
-
-[package.extras]
-html5lib = ["html5lib"]
-lxml = ["lxml"]
-
-[[package]]
-name = "black"
-version = "22.6.0"
-description = "The uncompromising code formatter."
-category = "dev"
-optional = false
-python-versions = ">=3.6.2"
-
-[package.dependencies]
-click = ">=8.0.0"
-dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""}
-mypy-extensions = ">=0.4.3"
-pathspec = ">=0.9.0"
-platformdirs = ">=2"
-tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
-typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
-typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
-
-[package.extras]
-colorama = ["colorama (>=0.4.3)"]
-d = ["aiohttp (>=3.7.4)"]
-jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
-uvloop = ["uvloop (>=0.15.2)"]
-
-[[package]]
-name = "certifi"
-version = "2022.6.15"
-description = "Python package for providing Mozilla's CA Bundle."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[[package]]
-name = "charset-normalizer"
-version = "2.0.12"
-description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-category = "main"
-optional = false
-python-versions = ">=3.5.0"
-
-[package.extras]
-unicode_backport = ["unicodedata2"]
-
-[[package]]
-name = "click"
-version = "8.0.4"
-description = "Composable command line interface toolkit"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-colorama = {version = "*", markers = "platform_system == \"Windows\""}
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
-
-[[package]]
-name = "colorama"
-version = "0.4.5"
-description = "Cross-platform colored terminal text."
-category = "dev"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[[package]]
-name = "dataclasses"
-version = "0.8"
-description = "A backport of the dataclasses module for Python 3.6"
-category = "dev"
-optional = false
-python-versions = ">=3.6, <3.7"
-
-[[package]]
-name = "html5lib"
-version = "1.1"
-description = "HTML parser based on the WHATWG HTML specification"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[package.dependencies]
-six = ">=1.9"
-webencodings = "*"
-
-[package.extras]
-lxml = ["lxml"]
-genshi = ["genshi"]
-chardet = ["chardet (>=2.2)"]
-all = ["lxml", "chardet (>=2.2)", "genshi"]
-
-[[package]]
-name = "idna"
-version = "3.3"
-description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
-optional = false
-python-versions = ">=3.5"
-
-[[package]]
-name = "importlib-metadata"
-version = "4.8.3"
-description = "Read metadata from Python packages"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
-zipp = ">=0.5"
-
-[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-perf = ["ipython"]
-testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
-
-[[package]]
-name = "lxml"
-version = "4.9.1"
-description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
-
-[package.extras]
-cssselect = ["cssselect (>=0.7)"]
-html5 = ["html5lib"]
-htmlsoup = ["beautifulsoup4"]
-source = ["Cython (>=0.29.7)"]
-
-[[package]]
-name = "mechanicalsoup"
-version = "1.1.0"
-description = "A Python library for automating interaction with websites"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-beautifulsoup4 = ">=4.7"
-lxml = "*"
-requests = ">=2.22.0"
-
-[[package]]
-name = "mechanize"
-version = "0.4.8"
-description = "Stateful, programmatic web browsing"
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-html5lib = ">=0.999999999"
-
-[package.extras]
-binarytest = ["lxml", "html5-parser"]
-fast = ["html5-parser (>=0.4.4)"]
-test = ["twisted", "service-identity", "six", "html5lib"]
-
-[[package]]
-name = "mypy-extensions"
-version = "0.4.3"
-description = "Experimental type system extensions for programs checked with the mypy typechecker."
-category = "dev"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "pathspec"
-version = "0.9.0"
-description = "Utility library for gitignore style pattern matching of file paths."
-category = "dev"
-optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
-
-[[package]]
-name = "platformdirs"
-version = "2.4.0"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-
-[package.extras]
-test = ["pytest-mock (>=3.6)", "pytest-cov (>=2.7)", "pytest (>=6)", "appdirs (==1.4.4)"]
-docs = ["sphinx-autodoc-typehints (>=1.12)", "proselint (>=0.10.2)", "furo (>=2021.7.5b38)", "Sphinx (>=4)"]
-
-[[package]]
-name = "requests"
-version = "2.27.1"
-description = "Python HTTP for Humans."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
-
-[package.dependencies]
-certifi = ">=2017.4.17"
-charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
-idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
-urllib3 = ">=1.21.1,<1.27"
-
-[package.extras]
-socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
-use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
-
-[[package]]
-name = "six"
-version = "1.16.0"
-description = "Python 2 and 3 compatibility utilities"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-
-[[package]]
-name = "soupsieve"
-version = "2.3.2.post1"
-description = "A modern CSS selector implementation for Beautiful Soup."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[[package]]
-name = "tomli"
-version = "1.2.3"
-description = "A lil' TOML parser"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-
-[[package]]
-name = "typed-ast"
-version = "1.5.4"
-description = "a fork of Python 2 and 3 ast modules with type comment support"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-
-[[package]]
-name = "typing-extensions"
-version = "4.1.1"
-description = "Backported and Experimental Type Hints for Python 3.6+"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-
-[[package]]
-name = "urllib3"
-version = "1.26.11"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
-
-[package.extras]
-brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
-secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
-socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
-
-[[package]]
-name = "webencodings"
-version = "0.5.1"
-description = "Character encoding aliases for legacy web content"
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "zipp"
-version = "3.6.0"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-
-[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
-
-[metadata]
-lock-version = "1.1"
-python-versions = ">3.6.2,<4"
-content-hash = "5996eb819fc4927b783b41546d165d92235ed12738ae3760bae1db571d1bb693"
-
-[metadata.files]
-beautifulsoup4 = [
- {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"},
- {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"},
-]
-black = [
- {file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"},
- {file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"},
- {file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"},
- {file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"},
- {file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"},
- {file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"},
- {file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"},
- {file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"},
- {file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"},
- {file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"},
- {file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"},
- {file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"},
- {file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"},
- {file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"},
- {file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"},
- {file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"},
- {file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"},
- {file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"},
- {file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"},
- {file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"},
- {file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"},
- {file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"},
- {file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"},
-]
-certifi = [
- {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
- {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
-]
-charset-normalizer = [
- {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
- {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
-]
-click = [
- {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
- {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
-]
-colorama = [
- {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
- {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
-]
-dataclasses = [
- {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
- {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
-]
-html5lib = [
- {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"},
- {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"},
-]
-idna = [
- {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
- {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
-]
-importlib-metadata = [
- {file = "importlib_metadata-4.8.3-py3-none-any.whl", hash = "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e"},
- {file = "importlib_metadata-4.8.3.tar.gz", hash = "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668"},
-]
-lxml = [
- {file = "lxml-4.9.1-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed"},
- {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc"},
- {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc"},
- {file = "lxml-4.9.1-cp27-cp27m-win32.whl", hash = "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3"},
- {file = "lxml-4.9.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627"},
- {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84"},
- {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837"},
- {file = "lxml-4.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad"},
- {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5"},
- {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8"},
- {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8"},
- {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d"},
- {file = "lxml-4.9.1-cp310-cp310-win32.whl", hash = "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7"},
- {file = "lxml-4.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b"},
- {file = "lxml-4.9.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d"},
- {file = "lxml-4.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3"},
- {file = "lxml-4.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29"},
- {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d"},
- {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318"},
- {file = "lxml-4.9.1-cp35-cp35m-win32.whl", hash = "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7"},
- {file = "lxml-4.9.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4"},
- {file = "lxml-4.9.1-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb"},
- {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067"},
- {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536"},
- {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8"},
- {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b"},
- {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf"},
- {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3"},
- {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391"},
- {file = "lxml-4.9.1-cp36-cp36m-win32.whl", hash = "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e"},
- {file = "lxml-4.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7"},
- {file = "lxml-4.9.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2"},
- {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc"},
- {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c"},
- {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4"},
- {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3"},
- {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca"},
- {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785"},
- {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785"},
- {file = "lxml-4.9.1-cp37-cp37m-win32.whl", hash = "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a"},
- {file = "lxml-4.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e"},
- {file = "lxml-4.9.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b"},
- {file = "lxml-4.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97"},
- {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21"},
- {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2"},
- {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130"},
- {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715"},
- {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036"},
- {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387"},
- {file = "lxml-4.9.1-cp38-cp38-win32.whl", hash = "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94"},
- {file = "lxml-4.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345"},
- {file = "lxml-4.9.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67"},
- {file = "lxml-4.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb"},
- {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448"},
- {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7"},
- {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91"},
- {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000"},
- {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25"},
- {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd"},
- {file = "lxml-4.9.1-cp39-cp39-win32.whl", hash = "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb"},
- {file = "lxml-4.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d"},
- {file = "lxml-4.9.1-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c"},
- {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b"},
- {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc"},
- {file = "lxml-4.9.1-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b"},
- {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2"},
- {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73"},
- {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c"},
- {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9"},
- {file = "lxml-4.9.1.tar.gz", hash = "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"},
-]
-mechanicalsoup = [
- {file = "MechanicalSoup-1.1.0-py3-none-any.whl", hash = "sha256:0685465c449018f8bce9055b74cf60fbec2ada8391c03aa179586edc6b5a5ee8"},
- {file = "MechanicalSoup-1.1.0.tar.gz", hash = "sha256:2be8ad9b7571990fce16d0f99a741779921510eb133d3a4dfdeccb2ff2cd00e5"},
-]
-mechanize = [
- {file = "mechanize-0.4.8-py2.py3-none-any.whl", hash = "sha256:961fd171b5eb37a7578fce62ba81ba85803dff3c5ba4ac24f6f569ae27198439"},
- {file = "mechanize-0.4.8.tar.gz", hash = "sha256:5e86ac0777357e006eb04cd28f7ed9f811d48dffa603d3891ac6d2b92280dc91"},
-]
-mypy-extensions = [
- {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
- {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
-]
-pathspec = [
- {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
- {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
-]
-platformdirs = [
- {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
- {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
-]
-requests = [
- {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
- {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
-]
-six = [
- {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
- {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
-]
-soupsieve = [
- {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"},
- {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"},
-]
-tomli = [
- {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"},
- {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"},
-]
-typed-ast = [
- {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
- {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
- {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
- {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
- {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
- {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
- {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
- {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
- {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
- {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
- {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
- {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
- {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
- {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
- {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
- {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
- {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
- {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
- {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
- {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
- {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
- {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
- {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
- {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
-]
-typing-extensions = [
- {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
- {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
-]
-urllib3 = [
- {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"},
- {file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"},
-]
-webencodings = [
- {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
- {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
-]
-zipp = [
- {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
- {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
-]
diff --git a/pyproject.toml b/pyproject.toml
index acf7d2c4..b784a319 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,38 +1,117 @@
-[[source]]
-url = "https://pypi.org/simple"
-verify_ssl = true
-name = "pypi"
+[project]
+name = "handelsregister"
+version = "0.3.0"
+description = "Python-Package fĂŒr das deutsche Handelsregister"
+readme = "README.md"
+license = "MIT"
+authors = [
+ { name = "BundesAPI", email = "kontakt@bund.dev" },
+ { name = "maximiliancw", email = "wunderkind-serie0f@icloud.com" }
+]
+keywords = ["handelsregister", "germany", "commercial-register", "cli", "api"]
+requires-python = ">=3.9"
-[tool.poetry.dependencies]
-python = ">3.6.2,<4"
-mechanize = "*"
-mechanicalsoup = "*"
+dependencies = [
+ "mechanize>=0.4.8",
+ "beautifulsoup4>=4.11.0",
+ "diskcache>=5.6.0",
+ "pydantic>=2.0.0",
+ "pydantic-settings>=2.0.0",
+ "tenacity>=8.2.0",
+ "ratelimit>=2.2.1",
+ "python-dateutil>=2.8.0",
+ "yarl>=1.9.0",
+ "tqdm>=4.66.0",
+]
-[tool.poetry.dev-dependencies]
-black = "^22.6.0"
+[project.optional-dependencies]
+dev = [
+ "ruff>=0.6.0",
+ "pytest>=7.0.0",
+ "pre-commit>=3.0.0",
+]
+docs = [
+ "mkdocs>=1.5.0",
+ "mkdocs-material>=9.5.0",
+ "mkdocstrings[python]>=0.24.0",
+ "mkdocs-static-i18n>=1.2.0",
+]
-[tool.poetry]
-name = "handelsregister"
-version = "0.1.0"
-description = ""
-authors = ["BundesAPI "]
+[project.urls]
+Homepage = "https://github.com/bundesAPI/handelsregister"
+Repository = "https://github.com/bundesAPI/handelsregister"
+Issues = "https://github.com/bundesAPI/handelsregister/issues"
+
+[project.scripts]
+handelsregister = "handelsregister.cli:main"
+hrg = "handelsregister.cli:main"
[build-system]
-requires = ["poetry-core>=1.0.0"]
-build-backend = "poetry.core.masonry.api"
-
-[tool.tox]
-legacy_tox_ini = """
-[tox]
-envlist = py36,py37,py38,py310
-isolated_build = True
-
-[tox:.package]
-basepython = python3
-
-[testenv]
-deps = pytest
-usedevelop = true
-commands =
- pytest
-"""
\ No newline at end of file
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[dependency-groups]
+dev = [
+ "ruff>=0.6.0",
+ "pytest>=7.0.0",
+ "pre-commit>=3.0.0",
+]
+
+[tool.pytest.ini_options]
+markers = [
+ "integration: marks tests that hit live API (deselect with '-m \"not integration\"')",
+ "slow: marks tests as slow running (deselect with '-m \"not slow\"')",
+]
+
+[tool.ruff]
+line-length = 100
+target-version = "py39"
+
+[tool.ruff.lint]
+select = [
+ "E", # pycodestyle errors
+ "W", # pycodestyle warnings
+ "F", # pyflakes
+ "I", # isort (import sorting)
+ "N", # pep8-naming
+ "UP", # pyupgrade
+ "B", # flake8-bugbear
+ "C4", # flake8-comprehensions
+ "DTZ", # flake8-datetimez
+ "T10", # flake8-debugger
+ "EM", # flake8-errmsg
+ "ISC", # flake8-implicit-str-concat
+ "ICN", # flake8-import-conventions
+ "PIE", # flake8-pie
+ "PT", # flake8-pytest-style
+ "Q", # flake8-quotes
+ "RSE", # flake8-raise
+ "RET", # flake8-return
+ "SIM", # flake8-simplify
+ "TID", # flake8-tidy-imports
+ "ARG", # flake8-unused-arguments
+ "PTH", # flake8-use-pathlib
+ "ERA", # eradicate (commented-out code)
+ "PD", # pandas-vet
+ "PGH", # pygrep-hooks
+ "PL", # Pylint
+ "TRY", # tryceratops
+ "NPY", # NumPy-specific rules
+ "RUF", # Ruff-specific rules
+]
+ignore = [
+ "E501", # line too long (handled by formatter)
+ "PLR0913", # too-many-arguments (can be too strict)
+ "PLR2004", # magic-value-used-in-comparison (too strict for constants)
+]
+
+[tool.ruff.lint.per-file-ignores]
+"__init__.py" = ["F401"] # Allow unused imports in __init__.py
+"test_*.py" = ["ARG", "S101", "PLR2004"] # Allow unused arguments, assert, and magic values in tests
+"conftest.py" = ["ARG", "F401"] # Allow unused arguments and imports in conftest
+
+[tool.ruff.format]
+quote-style = "double"
+indent-style = "space"
+skip-magic-trailing-comma = false
+line-ending = "auto"
diff --git a/test_handelsregister.py b/test_handelsregister.py
index fa1951a6..7e54bc54 100644
--- a/test_handelsregister.py
+++ b/test_handelsregister.py
@@ -1,57 +1,1190 @@
-import pytest
-from handelsregister import get_companies_in_searchresults,HandelsRegister
+"""Tests for the handelsregister module.
+
+Unit tests run without network access and use mocked responses.
+Integration tests hit the live API and are marked with @pytest.mark.integration.
+
+â ïž CRITICAL RATE LIMIT WARNING:
+ The integration tests make exactly 60 API requests (the rate limit per hour).
+ Each test makes 3 requests: open_startpage + navigate + submit_search.
+ With 20 tests total (16 parametrized + 4 others), this hits the limit exactly.
+
+ The ratelimit library will automatically sleep when the limit is reached, but:
+ - Tests will be very slow (potentially >1 hour)
+ - No safety margin for retries or re-runs
+ - Risk of hitting limit if tests run multiple times
+
+ Recommendations:
+ - Run only a subset: pytest -m integration -k "test_search_function"
+ - Remove force_refresh=True to use cached results
+ - Reduce parametrized test cases (currently 16, could be 8-10)
+ - Add delays between test runs
+ - Consider using a shared test cache
+"""
+
import argparse
+import time
+
+import pytest
+
+from handelsregister import (
+ BASE_URL,
+ DEFAULT_CACHE_TTL_SECONDS,
+ REGISTER_TYPES,
+ STATE_CODES,
+ SUFFIX_MAP,
+ Address,
+ CacheEntry,
+ Company,
+ CompanyDetails,
+ DetailsParser,
+ HandelsRegister,
+ HistoryEntry,
+ Owner,
+ Representative,
+ SearchCache,
+ SearchOptions,
+ Settings,
+ build_url,
+ get_companies_in_searchresults,
+ get_details,
+ search,
+ settings,
+)
+
+# =============================================================================
+# Test Fixtures
+# =============================================================================
+
+
+@pytest.fixture
+def sample_search_html():
+ """Sample HTML response from a search result."""
+ return ''
+
+
+@pytest.fixture
+def mock_args():
+ """Create mock arguments for HandelsRegister."""
+ return argparse.Namespace(
+ debug=False, force=False, schlagwoerter="Test Company", schlagwortOptionen="all", json=False
+ )
+
+
+@pytest.fixture
+def temp_cache_dir(tmp_path):
+ """Create a temporary cache directory."""
+ cache_dir = tmp_path / "handelsregister_cache"
+ cache_dir.mkdir()
+ return cache_dir
+
+
+# =============================================================================
+# Unit Tests - Parsing
+# =============================================================================
+
+
+class TestParseSearchResults:
+ """Unit tests for HTML parsing functions."""
+
+ def test_parse_search_result_gasag(self, sample_search_html):
+ """Test parsing a search result for GASAG AG."""
+ result = get_companies_in_searchresults(sample_search_html)
+
+ assert len(result) == 1
+ company = result[0]
+
+ assert company.name == "GASAG AG"
+ assert company.state == "Berlin"
+ assert company.register_num == "HRB 44343 B"
+ assert company.status == "currently registered"
+ assert company.status_normalized == "CURRENTLY_REGISTERED"
+ assert len(company.history) == 1
+ assert company.history[0].name == "1.) Gasag Berliner Gaswerke Aktiengesellschaft"
+ assert company.history[0].location == "1.) Berlin"
+
+ def test_parse_empty_html(self):
+ """Test parsing empty HTML returns empty list."""
+ result = get_companies_in_searchresults("")
+ assert result == []
+
+ def test_parse_no_grid_table(self):
+ """Test parsing HTML without grid table returns empty list."""
+ html = ""
+ result = get_companies_in_searchresults(html)
+ assert result == []
+
+
+class TestDetailsParser:
+ """Unit tests for DetailsParser (SI/AD/UT parsing)."""
+
+ @pytest.fixture
+ def sample_si_html(self):
+ """Sample HTML from structured register content (SI)."""
+ return """
+
+
+
+ | Firma: | GASAG AG |
+ | Rechtsform: | Aktiengesellschaft |
+ | Sitz: | Berlin |
+ | GeschÀftsanschrift: | GASAG-Platz 1, 10965 Berlin |
+ | Stammkapital: | 307.200.000,00 EUR |
+ | Gegenstand: | Versorgung der Bevölkerung mit Gas und anderen Energien |
+ | Registernummer: | HRB 44343 B |
+
+ Vorstand: Dr. Gerhard Holtmeier (Berlin)
+
+
+ """
+
+ @pytest.fixture
+ def sample_si_gmbh_html(self):
+ """Sample HTML for a GmbH."""
+ return """
+
+
+
+ | Firma: | Test GmbH |
+ | Rechtsform: | Gesellschaft mit beschrÀnkter Haftung |
+ | Stammkapital: | 25.000 EUR |
+
+ GeschĂ€ftsfĂŒhrer: Max Mustermann
+
+
+ """
+
+ def test_parse_si_basic(self, sample_si_html):
+ """Test parsing basic SI content."""
+ details = DetailsParser.parse_si(sample_si_html)
+
+ assert details.name == "GASAG AG"
+ assert details.legal_form == "Aktiengesellschaft"
+ assert "307.200.000" in details.capital
+ assert details.currency == "EUR"
+
+ def test_parse_si_with_base_info(self, sample_si_html):
+ """Test parsing SI with base company info."""
+ base_info = {
+ "name": "GASAG AG",
+ "register_num": "HRB 44343 B",
+ "court": "Amtsgericht Berlin",
+ "state": "Berlin",
+ "status": "aktuell",
+ }
+ details = DetailsParser.parse_si(sample_si_html, base_info)
+
+ assert details.court == "Amtsgericht Berlin"
+ assert details.state == "Berlin"
+ assert details.status == "aktuell"
+
+ def test_parse_si_address(self, sample_si_html):
+ """Test parsing address from SI."""
+ details = DetailsParser.parse_si(sample_si_html)
+
+ assert details.address is not None
+ assert details.address.street == "GASAG-Platz 1"
+ assert details.address.postal_code == "10965"
+ assert details.address.city == "Berlin"
+
+ def test_parse_si_purpose(self, sample_si_html):
+ """Test parsing company purpose from SI."""
+ details = DetailsParser.parse_si(sample_si_html)
+
+ assert details.purpose is not None
+ assert "Versorgung" in details.purpose
+ assert "Gas" in details.purpose
+
+ def test_parse_si_representatives(self, sample_si_html):
+ """Test parsing representatives from SI."""
+ details = DetailsParser.parse_si(sample_si_html)
+
+ assert len(details.representatives) >= 1
+ vorstand = next((r for r in details.representatives if r.role == "Vorstand"), None)
+ assert vorstand is not None
+ assert "Holtmeier" in vorstand.name
+
+ def test_parse_si_gmbh(self, sample_si_gmbh_html):
+ """Test parsing GmbH company."""
+ details = DetailsParser.parse_si(sample_si_gmbh_html)
+
+ assert details.name == "Test GmbH"
+ assert details.legal_form == "Gesellschaft mit beschrÀnkter Haftung"
+ assert "25.000" in details.capital
+ assert details.currency == "EUR"
+
+ def test_parse_si_gmbh_geschaeftsfuehrer(self, sample_si_gmbh_html):
+ """Test parsing GeschĂ€ftsfĂŒhrer from GmbH."""
+ details = DetailsParser.parse_si(sample_si_gmbh_html)
+
+ gf = next((r for r in details.representatives if r.role == "GeschĂ€ftsfĂŒhrer"), None)
+ assert gf is not None
+ assert "Mustermann" in gf.name
+
+ def test_parse_address_full(self):
+ """Test _parse_address with full address."""
+ addr = DetailsParser._parse_address("MusterstraĂe 123, 10115 Berlin")
+
+ assert addr.street == "MusterstraĂe 123"
+ assert addr.postal_code == "10115"
+ assert addr.city == "Berlin"
+
+ def test_parse_address_city_only(self):
+ """Test _parse_address with city only."""
+ addr = DetailsParser._parse_address("Hamburg")
+
+ assert addr.city == "Hamburg"
+ assert addr.street is None
+ assert addr.postal_code is None
+
+ def test_extract_legal_form_ag(self):
+ """Test extracting Aktiengesellschaft."""
+ result = DetailsParser._extract_legal_form("Eine Aktiengesellschaft")
+ assert result == "Aktiengesellschaft"
+
+ def test_extract_legal_form_gmbh(self):
+ """Test extracting GmbH."""
+ result = DetailsParser._extract_legal_form("Test GmbH")
+ assert result == "Gesellschaft mit beschrÀnkter Haftung"
+
+ def test_extract_legal_form_kg(self):
+ """Test extracting Kommanditgesellschaft."""
+ result = DetailsParser._extract_legal_form("Muster GmbH & Co. KG")
+ assert result == "GmbH & Co. KG"
+
+ def test_extract_legal_form_none(self):
+ """Test no legal form found."""
+ result = DetailsParser._extract_legal_form("Some random text")
+ assert result is None
+
+ def test_parse_date_german_format(self):
+ """Test parsing German date format DD.MM.YYYY."""
+ result = DetailsParser.parse_date("15.03.2024")
+ assert result == "15.03.2024"
+
+ def test_parse_date_in_text(self):
+ """Test parsing date embedded in text."""
+ result = DetailsParser.parse_date("Eingetragen am 01.01.2020 in Berlin")
+ assert result == "01.01.2020"
+
+ def test_parse_date_invalid(self):
+ """Test that invalid text returns None."""
+ result = DetailsParser.parse_date("No date here")
+ assert result is None
+
+ def test_parse_date_various_formats(self):
+ """Test that various German dates are parsed correctly."""
+ # Day first format
+ result = DetailsParser.parse_date("5.1.2023")
+ assert result == "05.01.2023"
+
+ # Full date
+ result = DetailsParser.parse_date("31.12.2022")
+ assert result == "31.12.2022"
+
+ def test_parse_empty_html(self):
+ """Test parsing empty HTML."""
+ details = DetailsParser.parse_si("")
+
+ assert details.name == ""
+ assert details.capital is None
+ assert details.representatives == []
+
+
+# =============================================================================
+# Unit Tests - Data Classes
+# =============================================================================
+
+
+class TestDataClasses:
+ """Unit tests for dataclass functionality."""
+
+ def test_history_entry_creation(self):
+ """Test creating a HistoryEntry."""
+ entry = HistoryEntry(name="Old Name", location="Old Location")
+ assert entry.name == "Old Name"
+ assert entry.location == "Old Location"
+
+ def test_company_to_dict(self):
+ """Test Company.to_dict() method."""
+ company = Company(
+ court="Test Court",
+ name="Test Company",
+ state="Berlin",
+ status="active",
+ status_normalized="ACTIVE",
+ documents="AD",
+ register_num="HRB 12345",
+ history=[HistoryEntry(name="Old", location="Berlin")],
+ )
+
+ d = company.to_dict()
+
+ assert d["court"] == "Test Court"
+ assert d["name"] == "Test Company"
+ assert d["state"] == "Berlin"
+ assert d["register_num"] == "HRB 12345"
+ assert d["statusCurrent"] == "ACTIVE"
+ assert d["history"] == [("Old", "Berlin")]
+
+ def test_cache_entry_not_expired(self):
+ """Test CacheEntry.is_expired() returns False for fresh entry."""
+ entry = CacheEntry(query="test", options="all", timestamp=time.time(), html="")
+ assert not entry.is_expired()
+
+ def test_cache_entry_expired(self):
+ """Test CacheEntry.is_expired() returns True for old entry."""
+ entry = CacheEntry(
+ query="test",
+ options="all",
+ timestamp=time.time() - DEFAULT_CACHE_TTL_SECONDS - 1,
+ html="",
+ )
+ assert entry.is_expired()
+
+ def test_cache_entry_serialization(self):
+ """Test CacheEntry to_dict and from_dict."""
+ original = CacheEntry(
+ query="test query", options="exact", timestamp=1234567890.0, html="test"
+ )
+
+ serialized = original.to_dict()
+ restored = CacheEntry.from_dict(serialized)
+
+ assert restored.query == original.query
+ assert restored.options == original.options
+ assert restored.timestamp == original.timestamp
+ assert restored.html == original.html
+
+ def test_search_options_cache_key(self):
+ """Test SearchOptions.cache_key() generates unique keys."""
+ opts1 = SearchOptions(keywords="test", keyword_option="all")
+ opts2 = SearchOptions(keywords="test", keyword_option="all")
+ opts3 = SearchOptions(keywords="test", keyword_option="exact")
+ opts4 = SearchOptions(keywords="test", keyword_option="all", states=["BE"])
+
+ assert opts1.cache_key() == opts2.cache_key()
+ assert opts1.cache_key() != opts3.cache_key()
+ assert opts1.cache_key() != opts4.cache_key()
+
+ def test_search_options_defaults(self):
+ """Test SearchOptions default values."""
+ opts = SearchOptions(keywords="test")
+
+ assert opts.keyword_option == "all"
+ assert opts.states is None
+ assert opts.register_type is None
+ assert opts.register_number is None
+ assert opts.include_deleted is False
+ assert opts.similar_sounding is False
+ assert opts.results_per_page == 100
+
+
+class TestPydanticValidation:
+ """Unit tests for Pydantic validation on models."""
+
+ def test_search_options_keyword_validation(self):
+ """Test that empty keywords are rejected."""
+ import pytest
+ from pydantic import ValidationError
+
+ with pytest.raises(ValidationError):
+ SearchOptions(keywords="")
+
+ def test_search_options_keyword_option_validation(self):
+ """Test that invalid keyword_option values are rejected."""
+ import pytest
+ from pydantic import ValidationError
+
+ with pytest.raises(ValidationError):
+ SearchOptions(keywords="test", keyword_option="invalid")
+
+ def test_search_options_states_validation(self):
+ """Test that invalid state codes are rejected."""
+ import pytest
+ from pydantic import ValidationError
+
+ with pytest.raises(ValidationError):
+ SearchOptions(keywords="test", states=["INVALID"])
+
+ def test_search_options_states_normalized(self):
+ """Test that state codes are normalized to uppercase."""
+ opts = SearchOptions(keywords="test", states=["be", "hh"])
+ assert opts.states == ["BE", "HH"]
+
+ def test_search_options_register_type_validation(self):
+ """Test that invalid register types are rejected."""
+ import pytest
+ from pydantic import ValidationError
+
+ with pytest.raises(ValidationError):
+ SearchOptions(keywords="test", register_type="INVALID")
+
+ def test_search_options_results_per_page_validation(self):
+ """Test that invalid results_per_page values are rejected."""
+ import pytest
+ from pydantic import ValidationError
+
+ with pytest.raises(ValidationError):
+ SearchOptions(keywords="test", results_per_page=999)
+
+ def test_representative_name_required(self):
+ """Test that Representative requires a name."""
+ import pytest
+ from pydantic import ValidationError
+
+ with pytest.raises(ValidationError):
+ Representative(name="", role="GeschĂ€ftsfĂŒhrer")
+
+ def test_owner_name_required(self):
+ """Test that Owner requires a name."""
+ import pytest
+ from pydantic import ValidationError
+
+ with pytest.raises(ValidationError):
+ Owner(name="")
+
+ def test_company_details_from_dict(self):
+ """Test creating CompanyDetails from model_validate."""
+ data = {
+ "name": "Test GmbH",
+ "register_num": "HRB 12345",
+ "court": "Berlin",
+ "state": "Berlin",
+ "status": "active",
+ }
+ details = CompanyDetails.model_validate(data)
+ assert details.name == "Test GmbH"
+ assert details.register_num == "HRB 12345"
+
+ def test_address_model_dump(self):
+ """Test Address model_dump for JSON serialization."""
+ addr = Address(street="Test 1", postal_code="12345", city="Berlin")
+ dumped = addr.model_dump()
+ assert dumped["street"] == "Test 1"
+ assert dumped["postal_code"] == "12345"
+ assert dumped["city"] == "Berlin"
+
+
+class TestAddress:
+ """Unit tests for Address dataclass."""
+
+ def test_address_str_full(self):
+ """Test Address.__str__() with all fields."""
+ addr = Address(
+ street="MusterstraĂe 123", postal_code="10115", city="Berlin", country="Deutschland"
+ )
+ assert str(addr) == "MusterstraĂe 123, 10115 Berlin"
+
+ def test_address_str_minimal(self):
+ """Test Address.__str__() with minimal fields."""
+ addr = Address(city="Hamburg")
+ assert str(addr) == "Hamburg"
+
+ def test_address_str_empty(self):
+ """Test Address.__str__() with no fields."""
+ addr = Address()
+ assert str(addr) == ""
+
+ def test_address_str_foreign(self):
+ """Test Address.__str__() with foreign country."""
+ addr = Address(city="Wien", country="Ăsterreich")
+ assert str(addr) == "Wien, Ăsterreich"
+
+ def test_address_to_dict(self):
+ """Test Address.to_dict()."""
+ addr = Address(street="Test 1", postal_code="12345", city="Berlin")
+ d = addr.to_dict()
+ assert d["street"] == "Test 1"
+ assert d["postal_code"] == "12345"
+ assert d["city"] == "Berlin"
+ assert d["country"] == "Deutschland"
+
+
+class TestRepresentative:
+ """Unit tests for Representative dataclass."""
+
+ def test_representative_creation(self):
+ """Test creating a Representative."""
+ rep = Representative(
+ name="Max Mustermann",
+ role="GeschĂ€ftsfĂŒhrer",
+ location="Berlin",
+ restrictions="einzelvertretungsberechtigt",
+ )
+ assert rep.name == "Max Mustermann"
+ assert rep.role == "GeschĂ€ftsfĂŒhrer"
+ assert rep.location == "Berlin"
+ assert rep.restrictions == "einzelvertretungsberechtigt"
+
+ def test_representative_to_dict(self):
+ """Test Representative.to_dict()."""
+ rep = Representative(name="Test", role="Vorstand")
+ d = rep.to_dict()
+ assert d["name"] == "Test"
+ assert d["role"] == "Vorstand"
+ assert d["location"] is None
+
+
+class TestOwner:
+ """Unit tests for Owner dataclass."""
+
+ def test_owner_creation(self):
+ """Test creating an Owner."""
+ owner = Owner(
+ name="Holding GmbH", share="100%", owner_type="Gesellschafter", location="MĂŒnchen"
+ )
+ assert owner.name == "Holding GmbH"
+ assert owner.share == "100%"
+ assert owner.owner_type == "Gesellschafter"
+
+ def test_owner_to_dict(self):
+ """Test Owner.to_dict()."""
+ owner = Owner(name="Test GmbH", share="50.000 EUR")
+ d = owner.to_dict()
+ assert d["name"] == "Test GmbH"
+ assert d["share"] == "50.000 EUR"
+
+
+class TestCompanyDetails:
+ """Unit tests for CompanyDetails dataclass."""
+
+ def test_company_details_creation(self):
+ """Test creating CompanyDetails with all fields."""
+ details = CompanyDetails(
+ name="GASAG AG",
+ register_num="HRB 44343 B",
+ court="Amtsgericht Berlin (Charlottenburg)",
+ state="Berlin",
+ status="aktuell",
+ legal_form="Aktiengesellschaft",
+ capital="307.200.000",
+ currency="EUR",
+ address=Address(street="GASAG-Platz 1", postal_code="10965", city="Berlin"),
+ purpose="Versorgung mit Energie",
+ representatives=[Representative(name="Dr. Gerhard Holtmeier", role="Vorstand")],
+ )
+ assert details.name == "GASAG AG"
+ assert details.legal_form == "Aktiengesellschaft"
+ assert details.capital == "307.200.000"
+ assert len(details.representatives) == 1
+
+ def test_company_details_to_dict(self):
+ """Test CompanyDetails.to_dict()."""
+ details = CompanyDetails(
+ name="Test GmbH",
+ register_num="HRB 12345",
+ court="Amtsgericht Berlin",
+ state="Berlin",
+ status="aktuell",
+ legal_form="GmbH",
+ capital="25.000",
+ currency="EUR",
+ )
+ d = details.to_dict()
+ assert d["name"] == "Test GmbH"
+ assert d["legal_form"] == "GmbH"
+ assert d["capital"] == "25.000"
+ assert d["representatives"] == []
+ assert d["owners"] == []
+
+ def test_company_details_from_company(self):
+ """Test CompanyDetails.from_company() class method."""
+ company = {
+ "name": "GASAG AG",
+ "register_num": "HRB 44343 B",
+ "court": "Berlin Amtsgericht",
+ "state": "Berlin",
+ "status": "aktuell",
+ }
+ details = CompanyDetails.from_company(company)
+ assert details.name == "GASAG AG"
+ assert details.register_num == "HRB 44343 B"
+ assert details.legal_form is None # Not set from basic company
+
+ def test_company_details_defaults(self):
+ """Test CompanyDetails default values."""
+ details = CompanyDetails(
+ name="Test",
+ register_num="HRB 1",
+ court="AG Berlin",
+ state="Berlin",
+ status="aktuell",
+ )
+ assert details.legal_form is None
+ assert details.capital is None
+ assert details.address is None
+ assert details.representatives == []
+ assert details.owners == []
+
+
+# =============================================================================
+# Unit Tests - Configuration
+# =============================================================================
+
+
+class TestConfiguration:
+ """Unit tests for configuration constants."""
+
+ def test_state_codes_complete(self):
+ """Test that all 16 German states are defined."""
+ assert len(STATE_CODES) == 16
+ assert "BE" in STATE_CODES # Berlin
+ assert "BY" in STATE_CODES # Bayern
+ assert "NW" in STATE_CODES # Nordrhein-Westfalen
+
+ def test_register_types(self):
+ """Test that all register types are defined."""
+ assert "HRA" in REGISTER_TYPES
+ assert "HRB" in REGISTER_TYPES
+ assert "GnR" in REGISTER_TYPES
+ assert "VR" in REGISTER_TYPES
+ assert "PR" in REGISTER_TYPES
+
+
+class TestSettings:
+ """Unit tests for pydantic-settings configuration."""
+
+ def test_settings_instance_exists(self):
+ """Test that global settings instance exists."""
+ assert settings is not None
+ assert isinstance(settings, Settings)
+
+ def test_settings_default_values(self):
+ """Test that default settings values are correct."""
+ s = Settings()
+ assert s.cache_ttl_seconds == 3600
+ assert s.details_ttl_seconds == 86400
+ assert s.request_timeout == 10
+ assert s.max_retries == 3
+ assert s.rate_limit_calls == 60
+ assert s.rate_limit_period == 3600
+ assert s.debug is False
+
+ def test_settings_base_url_parsed(self):
+ """Test that base_url_parsed returns a yarl URL."""
+ from yarl import URL
+
+ s = Settings()
+ assert isinstance(s.base_url_parsed, URL)
+ assert str(s.base_url_parsed) == "https://www.handelsregister.de"
+
+ def test_settings_env_prefix(self):
+ """Test that settings uses correct env prefix."""
+ assert Settings.model_config.get("env_prefix") == "HRG_"
+
+ def test_settings_custom_values(self):
+ """Test creating Settings with custom values."""
+ s = Settings(
+ cache_ttl_seconds=7200,
+ max_retries=5,
+ debug=True,
+ )
+ assert s.cache_ttl_seconds == 7200
+ assert s.max_retries == 5
+ assert s.debug is True
+
+ def test_settings_cache_dir(self):
+ """Test that cache_dir defaults to None."""
+ s = Settings()
+ assert s.cache_dir is None
+
+
+class TestURLHandling:
+ """Unit tests for URL construction using yarl."""
+
+ def test_base_url_is_yarl_url(self):
+ """Test that BASE_URL is a yarl URL object."""
+ from yarl import URL
+
+ assert isinstance(BASE_URL, URL)
+ assert str(BASE_URL) == "https://www.handelsregister.de"
+
+ def test_build_url_basic(self):
+ """Test build_url with just a path."""
+ url = build_url("rp_web/search")
+ assert str(url) == "https://www.handelsregister.de/rp_web/search"
+
+ def test_build_url_with_query_params(self):
+ """Test build_url with query parameters."""
+ url = build_url("rp_web/search", q="Bank", page="1")
+ url_str = str(url)
+ assert "https://www.handelsregister.de/rp_web/search" in url_str
+ assert "q=Bank" in url_str
+ assert "page=1" in url_str
+
+ def test_build_url_empty_path(self):
+ """Test build_url with empty path returns base URL."""
+ url = build_url()
+ assert str(url) == "https://www.handelsregister.de"
+
+
+# =============================================================================
+# Unit Tests - Cache
+# =============================================================================
+
+
+class TestCache:
+ """Unit tests for caching functionality."""
+
+ def test_cache_key_generation(self, temp_cache_dir):
+ """Test that cache keys are deterministic."""
+ cache = SearchCache(cache_dir=temp_cache_dir)
+
+ key1 = cache._get_cache_key("Test", "all")
+ key2 = cache._get_cache_key("Test", "all")
+ key3 = cache._get_cache_key("Test", "exact")
+
+ assert key1 == key2 # Same inputs = same key
+ assert key1 != key3 # Different options = different key
+ cache.close()
+
+ def test_cache_key_is_hash(self, temp_cache_dir):
+ """Test that cache keys are valid hex hashes."""
+ cache = SearchCache(cache_dir=temp_cache_dir)
+
+ key = cache._get_cache_key("Company with spaces / special chars!", "all")
+
+ # Should be a 64-character hex string (SHA-256)
+ assert len(key) == 64
+ assert all(c in "0123456789abcdef" for c in key)
+ cache.close()
+
+ def test_cache_get_set(self, temp_cache_dir):
+ """Test cache get/set operations."""
+ cache = SearchCache(cache_dir=temp_cache_dir)
+
+ # Initially empty
+ assert cache.get("test", "all") is None
+
+ # Set value
+ cache.set("test", "all", "cached")
+
+ # Get returns the value
+ assert cache.get("test", "all") == "cached"
+ cache.close()
+
+ def test_cache_ttl_expiration(self, temp_cache_dir):
+ """Test that expired cache entries are not returned."""
+ cache = SearchCache(cache_dir=temp_cache_dir, ttl_seconds=0)
+
+ cache.set("test", "all", "cached")
+ time.sleep(0.1) # Wait for expiration
+
+ # Expired entry should return None
+ assert cache.get("test", "all") is None
+ cache.close()
+
+ def test_cache_details_ttl(self, temp_cache_dir):
+ """Test that details cache uses longer TTL."""
+ cache = SearchCache(
+ cache_dir=temp_cache_dir,
+ ttl_seconds=0, # Search TTL expired
+ details_ttl_seconds=3600, # Details TTL not expired
+ )
+
+ # Set a details cache entry
+ cache.set("details:SI:HRB123", "", "details")
+ time.sleep(0.1)
+
+ # Details should still be available (longer TTL)
+ assert cache.get("details:SI:HRB123", "") == "details"
+
+ # But search cache would be expired
+ cache.set("search", "all", "search")
+ time.sleep(0.1)
+ assert cache.get("search", "all") is None
+ cache.close()
+
+ def test_cache_clear(self, temp_cache_dir):
+ """Test clearing the cache."""
+ cache = SearchCache(cache_dir=temp_cache_dir)
+
+ # Add some entries
+ cache.set("search1", "all", "1")
+ cache.set("search2", "all", "2")
+ cache.set("details:SI:HRB1", "", "d1")
+
+ # Clear all
+ count = cache.clear()
+ assert count == 3
+
+ # Verify all cleared
+ assert cache.get("search1", "all") is None
+ assert cache.get("details:SI:HRB1", "") is None
+
+ cache.close()
+
+ def test_cache_clear_details_only(self, temp_cache_dir):
+ """Test clearing only details cache.
+
+ Note: With DiskCache, details_only=True clears all entries
+ since we can't efficiently filter by key content after hashing.
+ """
+ cache = SearchCache(cache_dir=temp_cache_dir)
+
+ # Add entries
+ cache.set("search1", "all", "search")
+ cache.set("details:SI:HRB1", "", "details")
+
+ # Clear details only - with DiskCache this clears all entries
+ count = cache.clear(details_only=True)
+ assert count >= 1 # At least some entries cleared
+
+ # With DiskCache, all entries are cleared when details_only=True
+ # This is a limitation of the hash-based key storage
+ cache.close()
+
+ def test_cache_stats(self, temp_cache_dir):
+ """Test cache statistics."""
+ cache = SearchCache(cache_dir=temp_cache_dir)
+
+ # Add entries
+ cache.set("search1", "all", "search")
+ cache.set("details:SI:HRB1", "", "details")
+ cache.set("details:AD:HRB2", "", "details2")
+
+ stats = cache.get_stats()
+
+ assert stats["total_files"] == 3
+ # Note: DiskCache doesn't distinguish between search and details entries
+ assert stats["search_files"] == 3 # All counted as search with DiskCache
+ assert stats["details_files"] == 0 # DiskCache doesn't track this
+ assert stats["total_size_bytes"] > 0
+
+ cache.close()
+
+
+# =============================================================================
+# Unit Tests - Suffix Map
+# =============================================================================
+
+
+class TestSuffixMap:
+ """Unit tests for register number suffix handling."""
+
+ def test_berlin_suffix(self):
+ """Test Berlin HRB suffix mapping."""
+ assert SUFFIX_MAP["Berlin"]["HRB"] == " B"
+
+ def test_bremen_suffix(self):
+ """Test Bremen suffix mapping."""
+ assert SUFFIX_MAP["Bremen"]["HRB"] == " HB"
+ assert SUFFIX_MAP["Bremen"]["HRA"] == " HB"
+ assert SUFFIX_MAP["Bremen"]["VR"] == " HB"
+
+
+# =============================================================================
+# Unit Tests - Public API
+# =============================================================================
+
+
+class TestPublicAPI:
+ """Unit tests for the public search() function."""
+
+ def test_search_function_exists(self):
+ """Test that the search function is importable."""
+ assert callable(search)
+
+ def test_search_options_from_parameters(self):
+ """Test that SearchOptions can be created with all parameters."""
+ opts = SearchOptions(
+ keywords="Test",
+ keyword_option="exact",
+ states=["BE", "HH"],
+ register_type="HRB",
+ register_number="12345",
+ include_deleted=True,
+ similar_sounding=True,
+ results_per_page=50,
+ )
+
+ assert opts.keywords == "Test"
+ assert opts.keyword_option == "exact"
+ assert opts.states == ["BE", "HH"]
+ assert opts.register_type == "HRB"
+ assert opts.register_number == "12345"
+ assert opts.include_deleted is True
+ assert opts.similar_sounding is True
+ assert opts.results_per_page == 50
+
+
+class TestHandelsRegisterClass:
+ """Unit tests for HandelsRegister class initialization."""
+
+ def test_init_without_args(self):
+ """Test that HandelsRegister can be initialized without args."""
+ hr = HandelsRegister(debug=False)
+ assert hr.args is None
+ assert hr.cache is not None
+ assert hr.browser is not None
+
+ def test_init_with_debug(self):
+ """Test that debug flag is stored correctly."""
+ hr = HandelsRegister(debug=True)
+ assert hr._debug is True
+
+ def test_init_with_custom_cache(self, temp_cache_dir):
+ """Test that custom cache is used."""
+ cache = SearchCache(cache_dir=temp_cache_dir)
+ hr = HandelsRegister(cache=cache)
+ assert hr.cache is cache
+
+ def test_from_options_classmethod(self):
+ """Test the from_options class method."""
+ opts = SearchOptions(keywords="Test")
+ hr = HandelsRegister.from_options(opts, debug=True)
+
+ assert hr._debug is True
+ assert hasattr(hr, "_default_options")
+ assert hr._default_options.keywords == "Test"
+
+ def test_search_company_requires_args(self):
+ """Test that search_company raises error without args."""
+ hr = HandelsRegister()
+
+ with pytest.raises(ValueError, match="benötigt args"):
+ hr.search_company()
+
+ def test_get_company_details_invalid_type(self):
+ """Test that invalid detail_type raises ValueError."""
+ hr = HandelsRegister(debug=False)
+ company = {"name": "Test", "register_num": "HRB 123"}
+
+ with pytest.raises(ValueError, match="Invalid detail_type"):
+ hr.get_company_details(company, detail_type="INVALID")
+
+
+class TestDetailsParserAD:
+ """Unit tests for DetailsParser AD (Aktueller Abdruck) parsing."""
+
+ @pytest.fixture
+ def sample_ad_html(self):
+ """Sample HTML from current printout (AD)."""
+ return """
+
+
+
+
Aktueller Abdruck
+
Firma: Test GmbH
+
Rechtsform: Gesellschaft mit beschrÀnkter Haftung
+
Sitz: Berlin
+
Stammkapital: 50.000,00 EUR
+
Gegenstand des Unternehmens: Entwicklung von Software
+
GeschĂ€ftsfĂŒhrer: Hans Schmidt (Berlin), einzelvertretungsberechtigt
+
+
+
+ """
+
+ def test_parse_ad_basic(self, sample_ad_html):
+ """Test parsing AD content."""
+ base_info = {
+ "name": "Test GmbH",
+ "register_num": "HRB 12345",
+ "court": "AG Berlin",
+ "state": "Berlin",
+ "status": "aktuell",
+ }
+ details = DetailsParser.parse_ad(sample_ad_html, base_info)
+
+ assert details.name == "Test GmbH"
+ assert details.legal_form == "Gesellschaft mit beschrÀnkter Haftung"
+
+ def test_parse_ad_capital(self, sample_ad_html):
+ """Test parsing capital from AD."""
+ details = DetailsParser.parse_ad(sample_ad_html)
+
+ assert details.capital is not None
+ assert "50.000" in details.capital
+
+ def test_parse_ad_representatives(self, sample_ad_html):
+ """Test parsing representatives from AD."""
+ details = DetailsParser.parse_ad(sample_ad_html)
+
+ gf = next((r for r in details.representatives if r.role == "GeschĂ€ftsfĂŒhrer"), None)
+ assert gf is not None
+ assert "Schmidt" in gf.name
+
+
+class TestDetailsParserUT:
+ """Unit tests for DetailsParser UT (UnternehmenstrÀger) parsing."""
+
+ @pytest.fixture
+ def sample_ut_html(self):
+ """Sample HTML from company owners view (UT)."""
+ return """
+
+
+
+
UnternehmenstrÀger
+
Gesellschafter: Holding AG, Anteil: 100%
+
GeschĂ€ftsfĂŒhrer: Maria MĂŒller
+
+
+
+ """
+
+ def test_parse_ut_owners(self, sample_ut_html):
+ """Test parsing owners from UT."""
+ base_info = {
+ "name": "Test GmbH",
+ "register_num": "HRB 12345",
+ "court": "AG Berlin",
+ "state": "Berlin",
+ "status": "aktuell",
+ }
+ details = DetailsParser.parse_ut(sample_ut_html, base_info)
+
+ assert len(details.owners) >= 1
+
+ def test_parse_ut_representatives(self, sample_ut_html):
+ """Test parsing representatives from UT."""
+ details = DetailsParser.parse_ut(sample_ut_html)
+
+ gf = next((r for r in details.representatives if r.role == "GeschĂ€ftsfĂŒhrer"), None)
+ assert gf is not None
+ assert "MĂŒller" in gf.name
+
+
+class TestPublicAPIGetDetails:
+ """Unit tests for the public get_details() function."""
+
+ def test_get_details_function_exists(self):
+ """Test that get_details function is importable."""
+ assert callable(get_details)
+
+
+# =============================================================================
+# Integration Tests - Live API
+# =============================================================================
+
+
+@pytest.mark.integration
+@pytest.mark.slow
+class TestLiveAPI:
+ """Integration tests that hit the live Handelsregister API.
+
+ These tests are marked with @pytest.mark.integration and @pytest.mark.slow.
+ Run with: pytest -m integration
+ Skip with: pytest -m "not integration"
+ """
+
+ @pytest.mark.parametrize(
+ ("company", "expected_state"),
+ [
+ # Reduced from 16 to 10 states to stay under rate limit
+ # Selected representative states from different regions
+ ("Hafen Hamburg", "Hamburg"),
+ ("Bayerische Motoren Werke", "Bayern"),
+ ("Daimler Truck", "Baden-WĂŒrttemberg"),
+ ("Volkswagen", "Niedersachsen"),
+ ("RWE", "Nordrhein-Westfalen"),
+ ("Fraport", "Hessen"),
+ ("Vattenfall", "Berlin"),
+ ("Bremen", "Bremen"),
+ ("Kiel", "Schleswig-Holstein"),
+ ("Potsdam", "Brandenburg"),
+ ],
+ )
+ def test_search_by_state_company(self, company, expected_state, shared_hr_client):
+ """Test searching for companies in different German states."""
+ # Use shared client if available, otherwise create new one
+ if shared_hr_client:
+ # Create search options directly for shared client
+ from handelsregister import SearchOptions
+
+ opts = SearchOptions(
+ keywords=company,
+ keyword_option="all",
+ )
+ companies = shared_hr_client.search_with_options(opts, force_refresh=False)
+ else:
+ # Fallback: create new client (shouldn't happen with fixture)
+ args = argparse.Namespace(
+ debug=False,
+ force=False, # Use cache to reduce API calls
+ schlagwoerter=company,
+ schlagwortOptionen="all",
+ json=False,
+ )
+ hr = HandelsRegister(args)
+ hr.open_startpage()
+ companies = hr.search_company()
+
+ assert companies is not None
+ assert len(companies) > 0
+
+ def test_haus_anker_b_suffix(self, shared_hr_client):
+ """Test that Berlin companies get the B suffix."""
+ if shared_hr_client:
+ # Use shared client with SearchOptions
+ opts = SearchOptions(
+ keywords="Haus-Anker Verwaltungs GmbH",
+ keyword_option="exact",
+ )
+ companies = shared_hr_client.search_with_options(opts, force_refresh=False)
+ else:
+ # Fallback: create new client
+ args = argparse.Namespace(
+ debug=False,
+ force=False, # Use cache to reduce API calls
+ schlagwoerter="Haus-Anker Verwaltungs GmbH",
+ schlagwortOptionen="exact",
+ json=False,
+ )
+ hr = HandelsRegister(args)
+ hr.open_startpage()
+ companies = hr.search_company()
+
+ assert companies is not None
+
+ target = next((c for c in companies if "138434" in (c.get("register_num") or "")), None)
+
+ assert target is not None, "Haus-Anker Verwaltungs GmbH with expected number not found"
+ assert target["register_num"] == "HRB 138434 B"
+
+ def test_search_function_simple(self):
+ """Test the simple search() function API."""
+ results = search("GASAG AG", keyword_option="exact", force_refresh=False) # Use cache
+
+ assert results is not None
+ assert len(results) > 0
+ assert any("GASAG" in r.get("name", "") for r in results)
+
+ def test_search_function_with_states(self):
+ """Test search() with state filtering."""
+ results = search(
+ "Bank",
+ states=["BE"],
+ register_type="HRB",
+ force_refresh=False, # Use cache
+ )
+
+ assert results is not None
+ assert len(results) > 0
+
+ # Count how many results include Berlin
+ # The website filter isn't 100% precise, but most results should be from Berlin
+ berlin_count = sum(1 for r in results if r.get("state") and "Berlin" in r["state"])
+
+ # At least 50% of results should be from Berlin to confirm filter is working
+ assert berlin_count > 0, "No results from Berlin found"
+ assert (
+ berlin_count >= len(results) // 2
+ ), f"Expected most results from Berlin, got {berlin_count}/{len(results)}"
+
+ def test_search_with_options_method(self, shared_hr_client):
+ """Test HandelsRegister.search_with_options() method."""
+ opts = SearchOptions(
+ keywords="Deutsche Bahn",
+ keyword_option="all",
+ )
+
+ # Use shared client if available
+ hr = shared_hr_client or HandelsRegister(debug=False)
+ if not shared_hr_client:
+ hr.open_startpage()
+ results = hr.search_with_options(opts, force_refresh=False) # Use cache
-def test_parse_search_result():
- html = '%s' % """"""
- res = get_companies_in_searchresults(html)
- assert res == [{
- 'court':'Berlin District court Berlin (Charlottenburg) HRB 44343',
- 'register_num': 'HRB 44343 B',
- 'name':'GASAG AG',
- 'state':'Berlin',
- 'status':'currently registered', # Original value for backward compatibility
- 'statusCurrent':'CURRENTLY_REGISTERED', # Transformed value
- 'documents': 'ADCDHDDKUTVĂSI',
- 'history':[('1.) Gasag Berliner Gaswerke Aktiengesellschaft', '1.) Berlin')]
- },]
-
-
-@pytest.mark.parametrize("company, state_id", [
- ("Hafen Hamburg", "Hamburg"),
- ("Bayerische Motoren Werke", "Bayern"),
- ("Daimler Truck", "Baden-WĂŒrttemberg"),
- ("Volkswagen", "Niedersachsen"),
- ("RWE", "Nordrhein-Westfalen"),
- ("Fraport", "Hessen"),
- ("Saarstahl", "Saarland"),
- ("Mainz", "Rheinland-Pfalz"),
- ("Nordex", "Mecklenburg-Vorpommern"),
- ("Jenoptik", "ThĂŒringen"),
- ("Vattenfall", "Berlin"),
- ("Bremen", "Bremen"),
- ("Sachsen", "Sachsen"),
- ("Magdeburg", "Sachsen-Anhalt"),
- ("Kiel", "Schleswig-Holstein"),
- ("Potsdam", "Brandenburg")
-])
-def test_search_by_state_company(company, state_id):
-
- args = argparse.Namespace(debug=False, force=True, schlagwoerter=company, schlagwortOptionen='all', json=False)
- h = HandelsRegister(args)
- h.open_startpage()
- companies = h.search_company()
- assert companies is not None
- assert len(companies) > 0
-
-def test_haus_anker_b_suffix():
- args = argparse.Namespace(debug=False, force=True, schlagwoerter='Haus-Anker Verwaltungs GmbH', schlagwortOptionen='exact', json=False)
- h = HandelsRegister(args)
- h.open_startpage()
- companies = h.search_company()
- assert companies is not None
-
- target_company = next((c for c in companies if '138434' in c['register_num']), None)
-
- assert target_company is not None, "Haus-Anker Verwaltungs GmbH with expected number not found"
- assert target_company['register_num'] == 'HRB 138434 B'
\ No newline at end of file
+ assert results is not None
+ assert len(results) > 0
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 00000000..e507fe1b
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,1931 @@
+version = 1
+revision = 3
+requires-python = ">=3.9"
+resolution-markers = [
+ "python_full_version >= '3.10'",
+ "python_full_version < '3.10'",
+]
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
+]
+
+[[package]]
+name = "babel"
+version = "2.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
+]
+
+[[package]]
+name = "backrefs"
+version = "6.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962, upload-time = "2025-11-15T14:52:08.323Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059, upload-time = "2025-11-15T14:51:59.758Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854, upload-time = "2025-11-15T14:52:01.194Z" },
+ { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770, upload-time = "2025-11-15T14:52:02.584Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl", hash = "sha256:4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05", size = 400726, upload-time = "2025-11-15T14:52:04.093Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/72/6296bad135bfafd3254ae3648cd152980a424bd6fed64a101af00cc7ba31/backrefs-6.1-py314-none-any.whl", hash = "sha256:13eafbc9ccd5222e9c1f0bec563e6d2a6d21514962f11e7fc79872fd56cbc853", size = 412584, upload-time = "2025-11-15T14:52:05.233Z" },
+ { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" },
+]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.14.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "soupsieve" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2025.11.12"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
+]
+
+[[package]]
+name = "cfgv"
+version = "3.4.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" },
+]
+
+[[package]]
+name = "cfgv"
+version = "3.5.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" },
+ { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" },
+ { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" },
+ { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" },
+ { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" },
+ { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" },
+ { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" },
+ { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" },
+ { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" },
+ { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" },
+ { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" },
+ { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" },
+ { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" },
+ { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" },
+ { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" },
+ { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" },
+ { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" },
+ { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" },
+ { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" },
+ { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
+ { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
+ { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
+ { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
+ { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
+ { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
+ { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
+ { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
+ { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
+ { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
+ { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
+ { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
+ { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
+ { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
+ { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" },
+ { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" },
+ { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" },
+ { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" },
+ { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" },
+ { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" },
+ { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.1.8"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.3.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "diskcache"
+version = "5.6.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" },
+]
+
+[[package]]
+name = "distlib"
+version = "0.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
+]
+
+[[package]]
+name = "filelock"
+version = "3.19.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" },
+]
+
+[[package]]
+name = "filelock"
+version = "3.20.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c1/e0/a75dbe4bca1e7d41307323dad5ea2efdd95408f74ab2de8bd7dba9b51a1a/filelock-3.20.2.tar.gz", hash = "sha256:a2241ff4ddde2a7cebddf78e39832509cb045d18ec1a09d7248d6bfc6bfbbe64", size = 19510, upload-time = "2026-01-02T15:33:32.582Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl", hash = "sha256:fbba7237d6ea277175a32c54bb71ef814a8546d8601269e1bfc388de333974e8", size = 16697, upload-time = "2026-01-02T15:33:31.133Z" },
+]
+
+[[package]]
+name = "ghp-import"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "python-dateutil" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" },
+]
+
+[[package]]
+name = "griffe"
+version = "1.14.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" },
+]
+
+[[package]]
+name = "griffe"
+version = "1.15.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112, upload-time = "2025-11-10T15:03:15.52Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" },
+]
+
+[[package]]
+name = "handelsregister"
+version = "0.3.0"
+source = { editable = "." }
+dependencies = [
+ { name = "beautifulsoup4" },
+ { name = "diskcache" },
+ { name = "mechanize" },
+ { name = "pydantic" },
+ { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "python-dateutil" },
+ { name = "ratelimit" },
+ { name = "tenacity" },
+ { name = "tqdm" },
+ { name = "yarl" },
+]
+
+[package.optional-dependencies]
+dev = [
+ { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pre-commit", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "ruff" },
+]
+docs = [
+ { name = "mkdocs" },
+ { name = "mkdocs-material" },
+ { name = "mkdocs-static-i18n" },
+ { name = "mkdocstrings", version = "0.30.1", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version < '3.10'" },
+ { name = "mkdocstrings", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version >= '3.10'" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pre-commit", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "ruff" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "beautifulsoup4", specifier = ">=4.11.0" },
+ { name = "diskcache", specifier = ">=5.6.0" },
+ { name = "mechanize", specifier = ">=0.4.8" },
+ { name = "mkdocs", marker = "extra == 'docs'", specifier = ">=1.5.0" },
+ { name = "mkdocs-material", marker = "extra == 'docs'", specifier = ">=9.5.0" },
+ { name = "mkdocs-static-i18n", marker = "extra == 'docs'", specifier = ">=1.2.0" },
+ { name = "mkdocstrings", extras = ["python"], marker = "extra == 'docs'", specifier = ">=0.24.0" },
+ { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.0.0" },
+ { name = "pydantic", specifier = ">=2.0.0" },
+ { name = "pydantic-settings", specifier = ">=2.0.0" },
+ { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" },
+ { name = "python-dateutil", specifier = ">=2.8.0" },
+ { name = "ratelimit", specifier = ">=2.2.1" },
+ { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6.0" },
+ { name = "tenacity", specifier = ">=8.2.0" },
+ { name = "tqdm", specifier = ">=4.66.0" },
+ { name = "yarl", specifier = ">=1.9.0" },
+]
+provides-extras = ["dev", "docs"]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pre-commit", specifier = ">=3.0.0" },
+ { name = "pytest", specifier = ">=7.0.0" },
+ { name = "ruff", specifier = ">=0.6.0" },
+]
+
+[[package]]
+name = "html5lib"
+version = "1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+ { name = "webencodings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" },
+]
+
+[[package]]
+name = "identify"
+version = "2.6.15"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "8.7.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "zipp", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
+]
+
+[[package]]
+name = "markdown"
+version = "3.9"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" },
+]
+
+[[package]]
+name = "markdown"
+version = "3.10"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" },
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" },
+ { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" },
+ { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" },
+ { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" },
+ { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" },
+ { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" },
+ { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" },
+ { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" },
+ { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" },
+ { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" },
+ { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" },
+ { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
+ { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
+ { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
+ { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
+ { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
+ { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
+ { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
+ { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
+ { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
+ { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
+ { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
+ { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
+ { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
+ { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
+ { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
+ { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
+ { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
+ { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
+ { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
+ { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
+ { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" },
+ { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" },
+ { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" },
+]
+
+[[package]]
+name = "mechanize"
+version = "0.4.10"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "html5lib" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f5/ce/35d356959be6d8cdd5a3c8b6ea74548281ea9ae71c4d4538c076c4c986a2/mechanize-0.4.10.tar.gz", hash = "sha256:1dea947f9be7ea0ab610f7bbc4a4e36b45d6bfdfceea29ad3d389a88a1957ddf", size = 218291, upload-time = "2024-04-26T01:26:04.501Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/35/fabdeabeb9c0d72f0d21b3022a0f003e5c3722f4f80a13a416b06bc2a0a9/mechanize-0.4.10-py2.py3-none-any.whl", hash = "sha256:246e21aa30a74ca608c2a06a922454e699fcb37edc9b79fcbba0c67712c2ec79", size = 110390, upload-time = "2024-04-26T01:26:02.292Z" },
+]
+
+[[package]]
+name = "mergedeep"
+version = "1.3.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" },
+]
+
+[[package]]
+name = "mkdocs"
+version = "1.6.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "ghp-import" },
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "jinja2" },
+ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "markupsafe" },
+ { name = "mergedeep" },
+ { name = "mkdocs-get-deps" },
+ { name = "packaging" },
+ { name = "pathspec" },
+ { name = "pyyaml" },
+ { name = "pyyaml-env-tag" },
+ { name = "watchdog" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" },
+]
+
+[[package]]
+name = "mkdocs-autorefs"
+version = "1.4.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "markupsafe" },
+ { name = "mkdocs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" },
+]
+
+[[package]]
+name = "mkdocs-get-deps"
+version = "0.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "mergedeep" },
+ { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" },
+]
+
+[[package]]
+name = "mkdocs-material"
+version = "9.7.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "babel" },
+ { name = "backrefs" },
+ { name = "colorama" },
+ { name = "jinja2" },
+ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "mkdocs" },
+ { name = "mkdocs-material-extensions" },
+ { name = "paginate" },
+ { name = "pygments" },
+ { name = "pymdown-extensions" },
+ { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/27/e2/2ffc356cd72f1473d07c7719d82a8f2cbd261666828614ecb95b12169f41/mkdocs_material-9.7.1.tar.gz", hash = "sha256:89601b8f2c3e6c6ee0a918cc3566cb201d40bf37c3cd3c2067e26fadb8cce2b8", size = 4094392, upload-time = "2025-12-18T09:49:00.308Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl", hash = "sha256:3f6100937d7d731f87f1e3e3b021c97f7239666b9ba1151ab476cabb96c60d5c", size = 9297166, upload-time = "2025-12-18T09:48:56.664Z" },
+]
+
+[[package]]
+name = "mkdocs-material-extensions"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" },
+]
+
+[[package]]
+name = "mkdocs-static-i18n"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mkdocs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/03/2b/59652a2550465fde25ae6a009cb6d74d0f7e724d272fc952685807b29ca1/mkdocs_static_i18n-1.3.0.tar.gz", hash = "sha256:65731e1e4ec6d719693e24fee9340f5516460b2b7244d2a89bed4ce3cfa6a173", size = 1370450, upload-time = "2025-01-24T09:03:24.389Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ca/f7/ef222a7a2f96ecf79c7c00bfc9dde3b22cd2cc1bd2b7472c7b204fc64225/mkdocs_static_i18n-1.3.0-py3-none-any.whl", hash = "sha256:7905d52fff71d2c108b6c344fd223e848ca7e39ddf319b70864dfa47dba85d6b", size = 21660, upload-time = "2025-01-24T09:03:22.461Z" },
+]
+
+[[package]]
+name = "mkdocstrings"
+version = "0.30.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
+ { name = "jinja2", marker = "python_full_version < '3.10'" },
+ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markupsafe", marker = "python_full_version < '3.10'" },
+ { name = "mkdocs", marker = "python_full_version < '3.10'" },
+ { name = "mkdocs-autorefs", marker = "python_full_version < '3.10'" },
+ { name = "pymdown-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c5/33/2fa3243439f794e685d3e694590d28469a9b8ea733af4b48c250a3ffc9a0/mkdocstrings-0.30.1.tar.gz", hash = "sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f", size = 106350, upload-time = "2025-09-19T10:49:26.446Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/2c/f0dc4e1ee7f618f5bff7e05898d20bf8b6e7fa612038f768bfa295f136a4/mkdocstrings-0.30.1-py3-none-any.whl", hash = "sha256:41bd71f284ca4d44a668816193e4025c950b002252081e387433656ae9a70a82", size = 36704, upload-time = "2025-09-19T10:49:24.805Z" },
+]
+
+[package.optional-dependencies]
+python = [
+ { name = "mkdocstrings-python", version = "1.18.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+]
+
+[[package]]
+name = "mkdocstrings"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "jinja2", marker = "python_full_version >= '3.10'" },
+ { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "markupsafe", marker = "python_full_version >= '3.10'" },
+ { name = "mkdocs", marker = "python_full_version >= '3.10'" },
+ { name = "mkdocs-autorefs", marker = "python_full_version >= '3.10'" },
+ { name = "pymdown-extensions", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e5/13/10bbf9d56565fd91b91e6f5a8cd9b9d8a2b101c4e8ad6eeafa35a706301d/mkdocstrings-1.0.0.tar.gz", hash = "sha256:351a006dbb27aefce241ade110d3cd040c1145b7a3eb5fd5ac23f03ed67f401a", size = 101086, upload-time = "2025-11-27T15:39:40.534Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/fc/80aa31b79133634721cf7855d37b76ea49773599214896f2ff10be03de2a/mkdocstrings-1.0.0-py3-none-any.whl", hash = "sha256:4c50eb960bff6e05dfc631f6bc00dfabffbcb29c5ff25f676d64daae05ed82fa", size = 35135, upload-time = "2025-11-27T15:39:39.301Z" },
+]
+
+[package.optional-dependencies]
+python = [
+ { name = "mkdocstrings-python", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+
+[[package]]
+name = "mkdocstrings-python"
+version = "1.18.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "mkdocs-autorefs", marker = "python_full_version < '3.10'" },
+ { name = "mkdocstrings", version = "0.30.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/95/ae/58ab2bfbee2792e92a98b97e872f7c003deb903071f75d8d83aa55db28fa/mkdocstrings_python-1.18.2.tar.gz", hash = "sha256:4ad536920a07b6336f50d4c6d5603316fafb1172c5c882370cbbc954770ad323", size = 207972, upload-time = "2025-08-28T16:11:19.847Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d5/8f/ce008599d9adebf33ed144e7736914385e8537f5fc686fdb7cceb8c22431/mkdocstrings_python-1.18.2-py3-none-any.whl", hash = "sha256:944fe6deb8f08f33fa936d538233c4036e9f53e840994f6146e8e94eb71b600d", size = 138215, upload-time = "2025-08-28T16:11:18.176Z" },
+]
+
+[[package]]
+name = "mkdocstrings-python"
+version = "2.0.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "mkdocs-autorefs", marker = "python_full_version >= '3.10'" },
+ { name = "mkdocstrings", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/24/75/d30af27a2906f00eb90143470272376d728521997800f5dce5b340ba35bc/mkdocstrings_python-2.0.1.tar.gz", hash = "sha256:843a562221e6a471fefdd4b45cc6c22d2607ccbad632879234fa9692e9cf7732", size = 199345, upload-time = "2025-12-03T14:26:11.755Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/81/06/c5f8deba7d2cbdfa7967a716ae801aa9ca5f734b8f54fd473ef77a088dbe/mkdocstrings_python-2.0.1-py3-none-any.whl", hash = "sha256:66ecff45c5f8b71bf174e11d49afc845c2dfc7fc0ab17a86b6b337e0f24d8d90", size = 105055, upload-time = "2025-12-03T14:26:10.184Z" },
+]
+
+[[package]]
+name = "multidict"
+version = "6.7.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a9/63/7bdd4adc330abcca54c85728db2327130e49e52e8c3ce685cec44e0f2e9f/multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349", size = 77153, upload-time = "2025-10-06T14:48:26.409Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/bb/b6c35ff175ed1a3142222b78455ee31be71a8396ed3ab5280fbe3ebe4e85/multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e", size = 44993, upload-time = "2025-10-06T14:48:28.4Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/1f/064c77877c5fa6df6d346e68075c0f6998547afe952d6471b4c5f6a7345d/multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3", size = 44607, upload-time = "2025-10-06T14:48:29.581Z" },
+ { url = "https://files.pythonhosted.org/packages/04/7a/bf6aa92065dd47f287690000b3d7d332edfccb2277634cadf6a810463c6a/multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046", size = 241847, upload-time = "2025-10-06T14:48:32.107Z" },
+ { url = "https://files.pythonhosted.org/packages/94/39/297a8de920f76eda343e4ce05f3b489f0ab3f9504f2576dfb37b7c08ca08/multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32", size = 242616, upload-time = "2025-10-06T14:48:34.054Z" },
+ { url = "https://files.pythonhosted.org/packages/39/3a/d0eee2898cfd9d654aea6cb8c4addc2f9756e9a7e09391cfe55541f917f7/multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73", size = 222333, upload-time = "2025-10-06T14:48:35.9Z" },
+ { url = "https://files.pythonhosted.org/packages/05/48/3b328851193c7a4240815b71eea165b49248867bbb6153a0aee227a0bb47/multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc", size = 253239, upload-time = "2025-10-06T14:48:37.302Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/ca/0706a98c8d126a89245413225ca4a3fefc8435014de309cf8b30acb68841/multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62", size = 251618, upload-time = "2025-10-06T14:48:38.963Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/4f/9c7992f245554d8b173f6f0a048ad24b3e645d883f096857ec2c0822b8bd/multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84", size = 241655, upload-time = "2025-10-06T14:48:40.312Z" },
+ { url = "https://files.pythonhosted.org/packages/31/79/26a85991ae67efd1c0b1fc2e0c275b8a6aceeb155a68861f63f87a798f16/multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0", size = 239245, upload-time = "2025-10-06T14:48:41.848Z" },
+ { url = "https://files.pythonhosted.org/packages/14/1e/75fa96394478930b79d0302eaf9a6c69f34005a1a5251ac8b9c336486ec9/multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e", size = 233523, upload-time = "2025-10-06T14:48:43.749Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/5e/085544cb9f9c4ad2b5d97467c15f856df8d9bac410cffd5c43991a5d878b/multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4", size = 243129, upload-time = "2025-10-06T14:48:45.225Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/c3/e9d9e2f20c9474e7a8fcef28f863c5cbd29bb5adce6b70cebe8bdad0039d/multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648", size = 248999, upload-time = "2025-10-06T14:48:46.703Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/3f/df171b6efa3239ae33b97b887e42671cd1d94d460614bfb2c30ffdab3b95/multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111", size = 243711, upload-time = "2025-10-06T14:48:48.146Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/2f/9b5564888c4e14b9af64c54acf149263721a283aaf4aa0ae89b091d5d8c1/multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36", size = 237504, upload-time = "2025-10-06T14:48:49.447Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/3a/0bd6ca0f7d96d790542d591c8c3354c1e1b6bfd2024d4d92dc3d87485ec7/multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85", size = 41422, upload-time = "2025-10-06T14:48:50.789Z" },
+ { url = "https://files.pythonhosted.org/packages/00/35/f6a637ea2c75f0d3b7c7d41b1189189acff0d9deeb8b8f35536bb30f5e33/multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7", size = 46050, upload-time = "2025-10-06T14:48:51.938Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/b8/f7bf8329b39893d02d9d95cf610c75885d12fc0f402b1c894e1c8e01c916/multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0", size = 43153, upload-time = "2025-10-06T14:48:53.146Z" },
+ { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" },
+ { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" },
+ { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" },
+ { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" },
+ { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" },
+ { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" },
+ { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" },
+ { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" },
+ { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" },
+ { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" },
+ { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" },
+ { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" },
+ { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" },
+ { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" },
+ { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" },
+ { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" },
+ { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" },
+ { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" },
+ { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" },
+ { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" },
+ { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" },
+ { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" },
+ { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" },
+ { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" },
+ { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" },
+ { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" },
+ { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" },
+ { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" },
+ { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" },
+ { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" },
+ { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" },
+ { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" },
+ { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" },
+ { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" },
+ { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" },
+ { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" },
+ { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" },
+ { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" },
+ { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" },
+ { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" },
+ { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" },
+ { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" },
+ { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" },
+ { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" },
+ { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" },
+ { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" },
+ { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" },
+ { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" },
+ { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" },
+ { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" },
+ { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" },
+ { url = "https://files.pythonhosted.org/packages/90/d7/4cf84257902265c4250769ac49f4eaab81c182ee9aff8bf59d2714dbb174/multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c", size = 77073, upload-time = "2025-10-06T14:51:57.386Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/51/194e999630a656e76c2965a1590d12faa5cd528170f2abaa04423e09fe8d/multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40", size = 44928, upload-time = "2025-10-06T14:51:58.791Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/6b/2a195373c33068c9158e0941d0b46cfcc9c1d894ca2eb137d1128081dff0/multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851", size = 44581, upload-time = "2025-10-06T14:52:00.174Z" },
+ { url = "https://files.pythonhosted.org/packages/69/7b/7f4f2e644b6978bf011a5fd9a5ebb7c21de3f38523b1f7897d36a1ac1311/multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687", size = 239901, upload-time = "2025-10-06T14:52:02.416Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/b5/952c72786710a031aa204a9adf7db66d7f97a2c6573889d58b9e60fe6702/multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5", size = 240534, upload-time = "2025-10-06T14:52:04.105Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/ef/109fe1f2471e4c458c74242c7e4a833f2d9fc8a6813cd7ee345b0bad18f9/multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb", size = 219545, upload-time = "2025-10-06T14:52:06.208Z" },
+ { url = "https://files.pythonhosted.org/packages/42/bd/327d91288114967f9fe90dc53de70aa3fec1b9073e46aa32c4828f771a87/multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6", size = 251187, upload-time = "2025-10-06T14:52:08.049Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/13/a8b078ebbaceb7819fd28cd004413c33b98f1b70d542a62e6a00b74fb09f/multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e", size = 249379, upload-time = "2025-10-06T14:52:09.831Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/6d/ab12e1246be4d65d1f55de1e6f6aaa9b8120eddcfdd1d290439c7833d5ce/multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e", size = 239241, upload-time = "2025-10-06T14:52:11.561Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/d7/079a93625208c173b8fa756396814397c0fd9fee61ef87b75a748820b86e/multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32", size = 237418, upload-time = "2025-10-06T14:52:13.671Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/29/03777c2212274aa9440918d604dc9d6af0e6b4558c611c32c3dcf1a13870/multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c", size = 232987, upload-time = "2025-10-06T14:52:15.708Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/00/11188b68d85a84e8050ee34724d6ded19ad03975caebe0c8dcb2829b37bf/multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84", size = 240985, upload-time = "2025-10-06T14:52:17.317Z" },
+ { url = "https://files.pythonhosted.org/packages/df/0c/12eef6aeda21859c6cdf7d75bd5516d83be3efe3d8cc45fd1a3037f5b9dc/multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329", size = 246855, upload-time = "2025-10-06T14:52:19.096Z" },
+ { url = "https://files.pythonhosted.org/packages/69/f6/076120fd8bb3975f09228e288e08bff6b9f1bfd5166397c7ba284f622ab2/multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e", size = 241804, upload-time = "2025-10-06T14:52:21.166Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/51/41bb950c81437b88a93e6ddfca1d8763569ae861e638442838c4375f7497/multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4", size = 235321, upload-time = "2025-10-06T14:52:23.208Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/cf/5bbd31f055199d56c1f6b04bbadad3ccb24e6d5d4db75db774fc6d6674b8/multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91", size = 41435, upload-time = "2025-10-06T14:52:24.735Z" },
+ { url = "https://files.pythonhosted.org/packages/af/01/547ffe9c2faec91c26965c152f3fea6cff068b6037401f61d310cc861ff4/multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f", size = 46193, upload-time = "2025-10-06T14:52:26.101Z" },
+ { url = "https://files.pythonhosted.org/packages/27/77/cfa5461d1d2651d6fc24216c92b4a21d4e385a41c46e0d9f3b070675167b/multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546", size = 43118, upload-time = "2025-10-06T14:52:27.876Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" },
+]
+
+[[package]]
+name = "nodeenv"
+version = "1.10.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "25.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
+]
+
+[[package]]
+name = "paginate"
+version = "0.5.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" },
+]
+
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.4.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.5.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "pre-commit"
+version = "4.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "cfgv", version = "3.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "identify", marker = "python_full_version < '3.10'" },
+ { name = "nodeenv", marker = "python_full_version < '3.10'" },
+ { name = "pyyaml", marker = "python_full_version < '3.10'" },
+ { name = "virtualenv", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" },
+]
+
+[[package]]
+name = "pre-commit"
+version = "4.5.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "cfgv", version = "3.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "identify", marker = "python_full_version >= '3.10'" },
+ { name = "nodeenv", marker = "python_full_version >= '3.10'" },
+ { name = "pyyaml", marker = "python_full_version >= '3.10'" },
+ { name = "virtualenv", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" },
+]
+
+[[package]]
+name = "propcache"
+version = "0.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3c/0e/934b541323035566a9af292dba85a195f7b78179114f2c6ebb24551118a9/propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db", size = 79534, upload-time = "2025-10-08T19:46:02.083Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/6b/db0d03d96726d995dc7171286c6ba9d8d14251f37433890f88368951a44e/propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8", size = 45526, upload-time = "2025-10-08T19:46:03.884Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/c3/82728404aea669e1600f304f2609cde9e665c18df5a11cdd57ed73c1dceb/propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925", size = 47263, upload-time = "2025-10-08T19:46:05.405Z" },
+ { url = "https://files.pythonhosted.org/packages/df/1b/39313ddad2bf9187a1432654c38249bab4562ef535ef07f5eb6eb04d0b1b/propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21", size = 201012, upload-time = "2025-10-08T19:46:07.165Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/01/f1d0b57d136f294a142acf97f4ed58c8e5b974c21e543000968357115011/propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5", size = 209491, upload-time = "2025-10-08T19:46:08.909Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/c8/038d909c61c5bb039070b3fb02ad5cccdb1dde0d714792e251cdb17c9c05/propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db", size = 215319, upload-time = "2025-10-08T19:46:10.7Z" },
+ { url = "https://files.pythonhosted.org/packages/08/57/8c87e93142b2c1fa2408e45695205a7ba05fb5db458c0bf5c06ba0e09ea6/propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7", size = 196856, upload-time = "2025-10-08T19:46:12.003Z" },
+ { url = "https://files.pythonhosted.org/packages/42/df/5615fec76aa561987a534759b3686008a288e73107faa49a8ae5795a9f7a/propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4", size = 193241, upload-time = "2025-10-08T19:46:13.495Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/21/62949eb3a7a54afe8327011c90aca7e03547787a88fb8bd9726806482fea/propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60", size = 190552, upload-time = "2025-10-08T19:46:14.938Z" },
+ { url = "https://files.pythonhosted.org/packages/30/ee/ab4d727dd70806e5b4de96a798ae7ac6e4d42516f030ee60522474b6b332/propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f", size = 200113, upload-time = "2025-10-08T19:46:16.695Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/0b/38b46208e6711b016aa8966a3ac793eee0d05c7159d8342aa27fc0bc365e/propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900", size = 200778, upload-time = "2025-10-08T19:46:18.023Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/81/5abec54355ed344476bee711e9f04815d4b00a311ab0535599204eecc257/propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c", size = 193047, upload-time = "2025-10-08T19:46:19.449Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/b6/1f237c04e32063cb034acd5f6ef34ef3a394f75502e72703545631ab1ef6/propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb", size = 38093, upload-time = "2025-10-08T19:46:20.643Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/67/354aac4e0603a15f76439caf0427781bcd6797f370377f75a642133bc954/propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37", size = 41638, upload-time = "2025-10-08T19:46:21.935Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/e1/74e55b9fd1a4c209ff1a9a824bf6c8b3d1fc5a1ac3eabe23462637466785/propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581", size = 38229, upload-time = "2025-10-08T19:46:23.368Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" },
+ { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" },
+ { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" },
+ { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" },
+ { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" },
+ { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" },
+ { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" },
+ { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" },
+ { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" },
+ { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" },
+ { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" },
+ { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" },
+ { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" },
+ { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" },
+ { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" },
+ { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" },
+ { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" },
+ { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" },
+ { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" },
+ { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" },
+ { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" },
+ { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" },
+ { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" },
+ { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" },
+ { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" },
+ { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" },
+ { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" },
+ { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" },
+ { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" },
+ { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" },
+ { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" },
+ { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" },
+ { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" },
+ { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" },
+ { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" },
+ { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" },
+ { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" },
+ { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" },
+ { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" },
+ { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" },
+ { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" },
+ { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/01/0ebaec9003f5d619a7475165961f8e3083cf8644d704b60395df3601632d/propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff", size = 80277, upload-time = "2025-10-08T19:48:36.647Z" },
+ { url = "https://files.pythonhosted.org/packages/34/58/04af97ac586b4ef6b9026c3fd36ee7798b737a832f5d3440a4280dcebd3a/propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb", size = 45865, upload-time = "2025-10-08T19:48:37.859Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/19/b65d98ae21384518b291d9939e24a8aeac4fdb5101b732576f8f7540e834/propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac", size = 47636, upload-time = "2025-10-08T19:48:39.038Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/0f/317048c6d91c356c7154dca5af019e6effeb7ee15fa6a6db327cc19e12b4/propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888", size = 201126, upload-time = "2025-10-08T19:48:40.774Z" },
+ { url = "https://files.pythonhosted.org/packages/71/69/0b2a7a5a6ee83292b4b997dbd80549d8ce7d40b6397c1646c0d9495f5a85/propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc", size = 209837, upload-time = "2025-10-08T19:48:42.167Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/92/c699ac495a6698df6e497fc2de27af4b6ace10d8e76528357ce153722e45/propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a", size = 215578, upload-time = "2025-10-08T19:48:43.56Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/ee/14de81c5eb02c0ee4f500b4e39c4e1bd0677c06e72379e6ab18923c773fc/propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88", size = 197187, upload-time = "2025-10-08T19:48:45.309Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/94/48dce9aaa6d8dd5a0859bad75158ec522546d4ac23f8e2f05fac469477dd/propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00", size = 193478, upload-time = "2025-10-08T19:48:47.743Z" },
+ { url = "https://files.pythonhosted.org/packages/60/b5/0516b563e801e1ace212afde869a0596a0d7115eec0b12d296d75633fb29/propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0", size = 190650, upload-time = "2025-10-08T19:48:49.373Z" },
+ { url = "https://files.pythonhosted.org/packages/24/89/e0f7d4a5978cd56f8cd67735f74052f257dc471ec901694e430f0d1572fe/propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e", size = 200251, upload-time = "2025-10-08T19:48:51.4Z" },
+ { url = "https://files.pythonhosted.org/packages/06/7d/a1fac863d473876ed4406c914f2e14aa82d2f10dd207c9e16fc383cc5a24/propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781", size = 200919, upload-time = "2025-10-08T19:48:53.227Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/4e/f86a256ff24944cf5743e4e6c6994e3526f6acfcfb55e21694c2424f758c/propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183", size = 193211, upload-time = "2025-10-08T19:48:55.027Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/3f/3fbad5f4356b068f1b047d300a6ff2c66614d7030f078cd50be3fec04228/propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19", size = 38314, upload-time = "2025-10-08T19:48:56.792Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/45/d78d136c3a3d215677abb886785aae744da2c3005bcb99e58640c56529b1/propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f", size = 41912, upload-time = "2025-10-08T19:48:57.995Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/2a/b0632941f25139f4e58450b307242951f7c2717a5704977c6d5323a800af/propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938", size = 38450, upload-time = "2025-10-08T19:48:59.349Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" },
+]
+
+[[package]]
+name = "pydantic"
+version = "2.12.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-types" },
+ { name = "pydantic-core" },
+ { name = "typing-extensions" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.41.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" },
+ { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" },
+ { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" },
+ { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" },
+ { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" },
+ { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" },
+ { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" },
+ { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" },
+ { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" },
+ { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" },
+ { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" },
+ { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" },
+ { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" },
+ { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" },
+ { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" },
+ { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" },
+ { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" },
+ { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" },
+ { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
+ { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
+ { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
+ { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
+ { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
+ { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
+ { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
+ { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
+ { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
+ { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
+ { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
+ { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
+ { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
+ { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
+ { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
+ { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
+ { url = "https://files.pythonhosted.org/packages/54/db/160dffb57ed9a3705c4cbcbff0ac03bdae45f1ca7d58ab74645550df3fbd/pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf", size = 2107999, upload-time = "2025-11-04T13:42:03.885Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/7d/88e7de946f60d9263cc84819f32513520b85c0f8322f9b8f6e4afc938383/pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5", size = 1929745, upload-time = "2025-11-04T13:42:06.075Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/c2/aef51e5b283780e85e99ff19db0f05842d2d4a8a8cd15e63b0280029b08f/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d", size = 1920220, upload-time = "2025-11-04T13:42:08.457Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/97/492ab10f9ac8695cd76b2fdb24e9e61f394051df71594e9bcc891c9f586e/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60", size = 2067296, upload-time = "2025-11-04T13:42:10.817Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/23/984149650e5269c59a2a4c41d234a9570adc68ab29981825cfaf4cfad8f4/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82", size = 2231548, upload-time = "2025-11-04T13:42:13.843Z" },
+ { url = "https://files.pythonhosted.org/packages/71/0c/85bcbb885b9732c28bec67a222dbed5ed2d77baee1f8bba2002e8cd00c5c/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5", size = 2362571, upload-time = "2025-11-04T13:42:16.208Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/4a/412d2048be12c334003e9b823a3fa3d038e46cc2d64dd8aab50b31b65499/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3", size = 2068175, upload-time = "2025-11-04T13:42:18.911Z" },
+ { url = "https://files.pythonhosted.org/packages/73/f4/c58b6a776b502d0a5540ad02e232514285513572060f0d78f7832ca3c98b/pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425", size = 2177203, upload-time = "2025-11-04T13:42:22.578Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/ae/f06ea4c7e7a9eead3d165e7623cd2ea0cb788e277e4f935af63fc98fa4e6/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504", size = 2148191, upload-time = "2025-11-04T13:42:24.89Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/57/25a11dcdc656bf5f8b05902c3c2934ac3ea296257cc4a3f79a6319e61856/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5", size = 2343907, upload-time = "2025-11-04T13:42:27.683Z" },
+ { url = "https://files.pythonhosted.org/packages/96/82/e33d5f4933d7a03327c0c43c65d575e5919d4974ffc026bc917a5f7b9f61/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3", size = 2322174, upload-time = "2025-11-04T13:42:30.776Z" },
+ { url = "https://files.pythonhosted.org/packages/81/45/4091be67ce9f469e81656f880f3506f6a5624121ec5eb3eab37d7581897d/pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460", size = 1990353, upload-time = "2025-11-04T13:42:33.111Z" },
+ { url = "https://files.pythonhosted.org/packages/44/8a/a98aede18db6e9cd5d66bcacd8a409fcf8134204cdede2e7de35c5a2c5ef/pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b", size = 2015698, upload-time = "2025-11-04T13:42:35.484Z" },
+ { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" },
+ { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" },
+ { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" },
+ { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" },
+ { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" },
+ { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" },
+ { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" },
+ { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" },
+ { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" },
+]
+
+[[package]]
+name = "pydantic-settings"
+version = "2.11.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "pydantic", marker = "python_full_version < '3.10'" },
+ { name = "python-dotenv", marker = "python_full_version < '3.10'" },
+ { name = "typing-inspection", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/20/c5/dbbc27b814c71676593d1c3f718e6cd7d4f00652cefa24b75f7aa3efb25e/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180", size = 188394, upload-time = "2025-09-24T14:19:11.764Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608, upload-time = "2025-09-24T14:19:10.015Z" },
+]
+
+[[package]]
+name = "pydantic-settings"
+version = "2.12.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "pydantic", marker = "python_full_version >= '3.10'" },
+ { name = "python-dotenv", marker = "python_full_version >= '3.10'" },
+ { name = "typing-inspection", marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+]
+
+[[package]]
+name = "pymdown-extensions"
+version = "10.20"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3e/35/e3814a5b7df295df69d035cfb8aab78b2967cdf11fcfae7faed726b66664/pymdown_extensions-10.20.tar.gz", hash = "sha256:5c73566ab0cf38c6ba084cb7c5ea64a119ae0500cce754ccb682761dfea13a52", size = 852774, upload-time = "2025-12-31T19:59:42.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl", hash = "sha256:ea9e62add865da80a271d00bfa1c0fa085b20d133fb3fc97afdc88e682f60b2f", size = 268733, upload-time = "2025-12-31T19:59:40.652Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "8.4.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.10'" },
+ { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "packaging", marker = "python_full_version < '3.10'" },
+ { name = "pluggy", marker = "python_full_version < '3.10'" },
+ { name = "pygments", marker = "python_full_version < '3.10'" },
+ { name = "tomli", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "9.0.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" },
+ { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "packaging", marker = "python_full_version >= '3.10'" },
+ { name = "pluggy", marker = "python_full_version >= '3.10'" },
+ { name = "pygments", marker = "python_full_version >= '3.10'" },
+ { name = "tomli", marker = "python_full_version == '3.10.*'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
+]
+
+[[package]]
+name = "python-dotenv"
+version = "1.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" },
+ { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" },
+ { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
+ { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
+ { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
+ { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
+ { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
+ { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
+ { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
+ { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
+ { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
+ { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
+ { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
+ { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+ { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+ { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+ { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+ { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+ { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" },
+ { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" },
+ { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" },
+]
+
+[[package]]
+name = "pyyaml-env-tag"
+version = "1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" },
+]
+
+[[package]]
+name = "ratelimit"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ab/38/ff60c8fc9e002d50d48822cc5095deb8ebbc5f91a6b8fdd9731c87a147c9/ratelimit-2.2.1.tar.gz", hash = "sha256:af8a9b64b821529aca09ebaf6d8d279100d766f19e90b5059ac6a718ca6dee42", size = 5251, upload-time = "2018-12-17T18:55:49.675Z" }
+
+[[package]]
+name = "requests"
+version = "2.32.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.14.10"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" },
+ { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" },
+ { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" },
+ { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" },
+ { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" },
+ { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" },
+ { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" },
+ { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" },
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.8.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/89/23/adf3796d740536d63a6fbda113d07e60c734b6ed5d3058d1e47fc0495e47/soupsieve-2.8.1.tar.gz", hash = "sha256:4cf733bc50fa805f5df4b8ef4740fc0e0fa6218cf3006269afd3f9d6d80fd350", size = 117856, upload-time = "2025-12-18T13:50:34.655Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434", size = 36710, upload-time = "2025-12-18T13:50:33.267Z" },
+]
+
+[[package]]
+name = "tenacity"
+version = "9.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" },
+ { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" },
+ { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" },
+ { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" },
+ { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" },
+ { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" },
+ { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" },
+ { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" },
+ { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" },
+ { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" },
+ { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" },
+ { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" },
+ { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" },
+ { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" },
+ { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" },
+ { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" },
+ { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" },
+ { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" },
+ { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" },
+ { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" },
+ { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" },
+ { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" },
+ { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" },
+ { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" },
+ { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" },
+ { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" },
+ { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
+]
+
+[[package]]
+name = "tqdm"
+version = "4.67.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.6.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" },
+]
+
+[[package]]
+name = "virtualenv"
+version = "20.35.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "distlib" },
+ { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "filelock", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" },
+]
+
+[[package]]
+name = "watchdog"
+version = "6.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" },
+ { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" },
+ { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" },
+ { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" },
+ { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" },
+ { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" },
+ { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" },
+ { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" },
+ { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390, upload-time = "2024-11-01T14:06:49.325Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386, upload-time = "2024-11-01T14:06:50.536Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017, upload-time = "2024-11-01T14:06:51.717Z" },
+ { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903, upload-time = "2024-11-01T14:06:57.052Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381, upload-time = "2024-11-01T14:06:58.193Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
+ { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
+ { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
+ { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
+ { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
+]
+
+[[package]]
+name = "webencodings"
+version = "0.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" },
+]
+
+[[package]]
+name = "yarl"
+version = "1.22.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "idna" },
+ { name = "multidict" },
+ { name = "propcache" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/43/a2204825342f37c337f5edb6637040fa14e365b2fcc2346960201d457579/yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e", size = 140517, upload-time = "2025-10-06T14:08:42.494Z" },
+ { url = "https://files.pythonhosted.org/packages/44/6f/674f3e6f02266428c56f704cd2501c22f78e8b2eeb23f153117cc86fb28a/yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f", size = 93495, upload-time = "2025-10-06T14:08:46.2Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/12/5b274d8a0f30c07b91b2f02cba69152600b47830fcfb465c108880fcee9c/yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf", size = 94400, upload-time = "2025-10-06T14:08:47.855Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/7f/df1b6949b1fa1aa9ff6de6e2631876ad4b73c4437822026e85d8acb56bb1/yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a", size = 347545, upload-time = "2025-10-06T14:08:49.683Z" },
+ { url = "https://files.pythonhosted.org/packages/84/09/f92ed93bd6cd77872ab6c3462df45ca45cd058d8f1d0c9b4f54c1704429f/yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c", size = 319598, upload-time = "2025-10-06T14:08:51.215Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/97/ac3f3feae7d522cf7ccec3d340bb0b2b61c56cb9767923df62a135092c6b/yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147", size = 363893, upload-time = "2025-10-06T14:08:53.144Z" },
+ { url = "https://files.pythonhosted.org/packages/06/49/f3219097403b9c84a4d079b1d7bda62dd9b86d0d6e4428c02d46ab2c77fc/yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb", size = 371240, upload-time = "2025-10-06T14:08:55.036Z" },
+ { url = "https://files.pythonhosted.org/packages/35/9f/06b765d45c0e44e8ecf0fe15c9eacbbde342bb5b7561c46944f107bfb6c3/yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6", size = 346965, upload-time = "2025-10-06T14:08:56.722Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/69/599e7cea8d0fcb1694323b0db0dda317fa3162f7b90166faddecf532166f/yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0", size = 342026, upload-time = "2025-10-06T14:08:58.563Z" },
+ { url = "https://files.pythonhosted.org/packages/95/6f/9dfd12c8bc90fea9eab39832ee32ea48f8e53d1256252a77b710c065c89f/yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda", size = 335637, upload-time = "2025-10-06T14:09:00.506Z" },
+ { url = "https://files.pythonhosted.org/packages/57/2e/34c5b4eb9b07e16e873db5b182c71e5f06f9b5af388cdaa97736d79dd9a6/yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc", size = 359082, upload-time = "2025-10-06T14:09:01.936Z" },
+ { url = "https://files.pythonhosted.org/packages/31/71/fa7e10fb772d273aa1f096ecb8ab8594117822f683bab7d2c5a89914c92a/yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737", size = 357811, upload-time = "2025-10-06T14:09:03.445Z" },
+ { url = "https://files.pythonhosted.org/packages/26/da/11374c04e8e1184a6a03cf9c8f5688d3e5cec83ed6f31ad3481b3207f709/yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467", size = 351223, upload-time = "2025-10-06T14:09:05.401Z" },
+ { url = "https://files.pythonhosted.org/packages/82/8f/e2d01f161b0c034a30410e375e191a5d27608c1f8693bab1a08b089ca096/yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea", size = 82118, upload-time = "2025-10-06T14:09:11.148Z" },
+ { url = "https://files.pythonhosted.org/packages/62/46/94c76196642dbeae634c7a61ba3da88cd77bed875bf6e4a8bed037505aa6/yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca", size = 86852, upload-time = "2025-10-06T14:09:12.958Z" },
+ { url = "https://files.pythonhosted.org/packages/af/af/7df4f179d3b1a6dcb9a4bd2ffbc67642746fcafdb62580e66876ce83fff4/yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b", size = 82012, upload-time = "2025-10-06T14:09:14.664Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" },
+ { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" },
+ { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" },
+ { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" },
+ { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" },
+ { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" },
+ { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" },
+ { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" },
+ { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" },
+ { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" },
+ { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" },
+ { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" },
+ { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" },
+ { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" },
+ { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" },
+ { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" },
+ { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" },
+ { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" },
+ { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" },
+ { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" },
+ { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" },
+ { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" },
+ { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" },
+ { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" },
+ { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" },
+ { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" },
+ { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" },
+ { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" },
+ { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" },
+ { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" },
+ { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" },
+ { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" },
+ { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" },
+ { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" },
+ { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" },
+ { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" },
+ { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" },
+ { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" },
+ { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" },
+ { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" },
+ { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" },
+ { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" },
+ { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" },
+ { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" },
+ { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" },
+ { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" },
+ { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" },
+ { url = "https://files.pythonhosted.org/packages/94/fd/6480106702a79bcceda5fd9c63cb19a04a6506bd5ce7fd8d9b63742f0021/yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748", size = 141301, upload-time = "2025-10-06T14:12:19.01Z" },
+ { url = "https://files.pythonhosted.org/packages/42/e1/6d95d21b17a93e793e4ec420a925fe1f6a9342338ca7a563ed21129c0990/yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859", size = 93864, upload-time = "2025-10-06T14:12:21.05Z" },
+ { url = "https://files.pythonhosted.org/packages/32/58/b8055273c203968e89808413ea4c984988b6649baabf10f4522e67c22d2f/yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9", size = 94706, upload-time = "2025-10-06T14:12:23.287Z" },
+ { url = "https://files.pythonhosted.org/packages/18/91/d7bfbc28a88c2895ecd0da6a874def0c147de78afc52c773c28e1aa233a3/yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054", size = 347100, upload-time = "2025-10-06T14:12:28.527Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/e8/37a1e7b99721c0564b1fc7b0a4d1f595ef6fb8060d82ca61775b644185f7/yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b", size = 318902, upload-time = "2025-10-06T14:12:30.528Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/ef/34724449d7ef2db4f22df644f2dac0b8a275d20f585e526937b3ae47b02d/yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60", size = 363302, upload-time = "2025-10-06T14:12:32.295Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/04/88a39a5dad39889f192cce8d66cc4c58dbeca983e83f9b6bf23822a7ed91/yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890", size = 370816, upload-time = "2025-10-06T14:12:34.01Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/1f/5e895e547129413f56c76be2c3ce4b96c797d2d0ff3e16a817d9269b12e6/yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba", size = 346465, upload-time = "2025-10-06T14:12:35.977Z" },
+ { url = "https://files.pythonhosted.org/packages/11/13/a750e9fd6f9cc9ed3a52a70fe58ffe505322f0efe0d48e1fd9ffe53281f5/yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca", size = 341506, upload-time = "2025-10-06T14:12:37.788Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/67/bb6024de76e7186611ebe626aec5b71a2d2ecf9453e795f2dbd80614784c/yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba", size = 335030, upload-time = "2025-10-06T14:12:39.775Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/be/50b38447fd94a7992996a62b8b463d0579323fcfc08c61bdba949eef8a5d/yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b", size = 358560, upload-time = "2025-10-06T14:12:41.547Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/89/c020b6f547578c4e3dbb6335bf918f26e2f34ad0d1e515d72fd33ac0c635/yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e", size = 357290, upload-time = "2025-10-06T14:12:43.861Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/52/c49a619ee35a402fa3a7019a4fa8d26878fec0d1243f6968bbf516789578/yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8", size = 350700, upload-time = "2025-10-06T14:12:46.868Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/c9/f5042d87777bf6968435f04a2bbb15466b2f142e6e47fa4f34d1a3f32f0c/yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b", size = 82323, upload-time = "2025-10-06T14:12:48.633Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/58/d00f7cad9eba20c4eefac2682f34661d1d1b3a942fc0092eb60e78cfb733/yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed", size = 87145, upload-time = "2025-10-06T14:12:50.241Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/a3/70904f365080780d38b919edd42d224b8c4ce224a86950d2eaa2a24366ad/yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2", size = 82173, upload-time = "2025-10-06T14:12:51.869Z" },
+ { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" },
+]
+
+[[package]]
+name = "zipp"
+version = "3.23.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
+]