rusty-validator 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,184 @@
1
+ # This file is autogenerated by maturin v1.6.0
2
+ # To update, run
3
+ #
4
+ # maturin generate-ci github
5
+ #
6
+ name: CI
7
+
8
+ on:
9
+ push:
10
+ branches:
11
+ - main
12
+ - master
13
+ tags:
14
+ - '*'
15
+ pull_request:
16
+ workflow_dispatch:
17
+
18
+ permissions:
19
+ contents: read
20
+
21
+ jobs:
22
+ test:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - uses: astral-sh/setup-uv@v5
27
+ with:
28
+ enable-cache: true
29
+ - uses: dtolnay/rust-toolchain@stable
30
+ - name: Sync deps and build extension
31
+ run: uv sync
32
+ - name: Test
33
+ run: uv run pytest
34
+ - name: Type-check
35
+ run: uv run mypy python
36
+
37
+ linux:
38
+ runs-on: ${{ matrix.platform.runner }}
39
+ strategy:
40
+ matrix:
41
+ platform:
42
+ - runner: ubuntu-latest
43
+ target: x86_64
44
+ - runner: ubuntu-latest
45
+ target: x86
46
+ - runner: ubuntu-latest
47
+ target: aarch64
48
+ - runner: ubuntu-latest
49
+ target: armv7
50
+ - runner: ubuntu-latest
51
+ target: s390x
52
+ - runner: ubuntu-latest
53
+ target: ppc64le
54
+ steps:
55
+ - uses: actions/checkout@v4
56
+ - uses: actions/setup-python@v5
57
+ with:
58
+ python-version: '3.13'
59
+ - name: Build wheels
60
+ uses: PyO3/maturin-action@v1
61
+ with:
62
+ target: ${{ matrix.platform.target }}
63
+ # abi3-py313: one cp313-abi3 wheel per platform, runs on all CPython >=3.13.
64
+ args: --release --out dist -i 3.13
65
+ sccache: 'true'
66
+ manylinux: auto
67
+ - name: Upload wheels
68
+ uses: actions/upload-artifact@v4
69
+ with:
70
+ name: wheels-linux-${{ matrix.platform.target }}
71
+ path: dist
72
+
73
+ musllinux:
74
+ runs-on: ${{ matrix.platform.runner }}
75
+ strategy:
76
+ matrix:
77
+ platform:
78
+ - runner: ubuntu-latest
79
+ target: x86_64
80
+ - runner: ubuntu-latest
81
+ target: x86
82
+ - runner: ubuntu-latest
83
+ target: aarch64
84
+ - runner: ubuntu-latest
85
+ target: armv7
86
+ steps:
87
+ - uses: actions/checkout@v4
88
+ - uses: actions/setup-python@v5
89
+ with:
90
+ python-version: '3.13'
91
+ - name: Build wheels
92
+ uses: PyO3/maturin-action@v1
93
+ with:
94
+ target: ${{ matrix.platform.target }}
95
+ # abi3-py313: one cp313-abi3 wheel per platform, runs on all CPython >=3.13.
96
+ args: --release --out dist -i 3.13
97
+ sccache: 'true'
98
+ manylinux: musllinux_1_2
99
+ - name: Upload wheels
100
+ uses: actions/upload-artifact@v4
101
+ with:
102
+ name: wheels-musllinux-${{ matrix.platform.target }}
103
+ path: dist
104
+
105
+ windows:
106
+ runs-on: ${{ matrix.platform.runner }}
107
+ strategy:
108
+ matrix:
109
+ platform:
110
+ - runner: windows-latest
111
+ target: x64
112
+ - runner: windows-latest
113
+ target: x86
114
+ steps:
115
+ - uses: actions/checkout@v4
116
+ - uses: actions/setup-python@v5
117
+ with:
118
+ python-version: '3.13'
119
+ architecture: ${{ matrix.platform.target }}
120
+ - name: Build wheels
121
+ uses: PyO3/maturin-action@v1
122
+ with:
123
+ target: ${{ matrix.platform.target }}
124
+ # abi3-py313: one cp313-abi3 wheel per platform, runs on all CPython >=3.13.
125
+ args: --release --out dist -i 3.13
126
+ sccache: 'true'
127
+ - name: Upload wheels
128
+ uses: actions/upload-artifact@v4
129
+ with:
130
+ name: wheels-windows-${{ matrix.platform.target }}
131
+ path: dist
132
+
133
+ macos:
134
+ # Single universal2 build on Apple Silicon: Intel macOS runners (macos-13) queue for
135
+ # hours / don't schedule on public repos, while one ARM runner emits a fat x86_64+arm64
136
+ # wheel. abi3 cross-compiles cleanly, so this keeps Intel coverage without an Intel runner.
137
+ runs-on: macos-14
138
+ steps:
139
+ - uses: actions/checkout@v4
140
+ - uses: actions/setup-python@v5
141
+ with:
142
+ python-version: '3.13'
143
+ - name: Build wheels
144
+ uses: PyO3/maturin-action@v1
145
+ with:
146
+ target: universal2-apple-darwin
147
+ # abi3-py313: one cp313-abi3 wheel, runs on all CPython >=3.13.
148
+ args: --release --out dist -i 3.13
149
+ sccache: 'true'
150
+ - name: Upload wheels
151
+ uses: actions/upload-artifact@v4
152
+ with:
153
+ name: wheels-macos-universal2
154
+ path: dist
155
+
156
+ sdist:
157
+ runs-on: ubuntu-latest
158
+ steps:
159
+ - uses: actions/checkout@v4
160
+ - name: Build sdist
161
+ uses: PyO3/maturin-action@v1
162
+ with:
163
+ command: sdist
164
+ args: --out dist
165
+ - name: Upload sdist
166
+ uses: actions/upload-artifact@v4
167
+ with:
168
+ name: wheels-sdist
169
+ path: dist
170
+
171
+ release:
172
+ name: Release
173
+ runs-on: ubuntu-latest
174
+ if: "startsWith(github.ref, 'refs/tags/')"
175
+ needs: [test, linux, musllinux, windows, macos, sdist]
176
+ steps:
177
+ - uses: actions/download-artifact@v4
178
+ - name: Publish to PyPI
179
+ uses: PyO3/maturin-action@v1
180
+ env:
181
+ MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
182
+ with:
183
+ command: upload
184
+ args: --non-interactive --skip-existing wheels-*/*
@@ -0,0 +1,73 @@
1
+ /target
2
+ .claude
3
+ graphify-out/
4
+
5
+ # Byte-compiled / optimized / DLL files
6
+ __pycache__/
7
+ .pytest_cache/
8
+ *.py[cod]
9
+
10
+ # C extensions
11
+ *.so
12
+
13
+ # Distribution / packaging
14
+ .Python
15
+ .venv/
16
+ env/
17
+ bin/
18
+ build/
19
+ develop-eggs/
20
+ dist/
21
+ eggs/
22
+ lib/
23
+ lib64/
24
+ parts/
25
+ sdist/
26
+ var/
27
+ include/
28
+ man/
29
+ venv/
30
+ *.egg-info/
31
+ .installed.cfg
32
+ *.egg
33
+
34
+ # Installer logs
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+ pip-selfcheck.json
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .coverage
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+
47
+ # Translations
48
+ *.mo
49
+
50
+ # Mr Developer
51
+ .mr.developer.cfg
52
+ .project
53
+ .pydevproject
54
+
55
+ # Rope
56
+ .ropeproject
57
+
58
+ # Django stuff:
59
+ *.log
60
+ *.pot
61
+
62
+ .DS_Store
63
+
64
+ # Sphinx documentation
65
+ docs/_build/
66
+
67
+ # PyCharm
68
+ .idea/
69
+
70
+ # VSCode
71
+ .vscode/
72
+
73
+ # Pyenv / uv: .python-version is committed (pins the dev interpreter)
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,170 @@
1
+ # CLAUDE.md
2
+
3
+ Dieser Leitfaden hilft Claude Code (claude.ai/code) bei der Arbeit in diesem Repository.
4
+
5
+ ## Projektüberblick
6
+
7
+ **rusty-validator** ist ein schmales **Python-Binding** der Rust-Crate
8
+ [`validator`](https://github.com/Keats/validator), gebaut mit **PyO3** und
9
+ **maturin**. Es stellt Validierungs-**Prädikate** bereit: Funktionen, die prüfen, ob
10
+ eine Zeichenkette wohlgeformt ist (E-Mail-Adresse, URL, IP-Adresse) und `True`/`False`
11
+ zurückgeben. Die eigentliche Prüf-Logik liefert die Crate — dieses Projekt reicht sie
12
+ nur treu nach Python durch.
13
+
14
+ **Vor Änderungen lesen** — die Domänensprache und die tragenden Entscheidungen sind
15
+ hier festgehalten:
16
+
17
+ - [CONTEXT.md](CONTEXT.md) — Glossar (Validieren, Format-/Constraint-Validator,
18
+ E-Mail-Adresse, URL, IP-Adresse) + Beispieldialog. Reines Glossar, keine
19
+ Implementierungsdetails.
20
+ - [docs/adr/](docs/adr/) — Architektur-Entscheidungen:
21
+ - [0001](docs/adr/0001-treues-binding-der-validator-crate.md) — Treues Binding der
22
+ `validator`-Crate (Crate = Vorgabe, mit dokumentierten Ausnahmen)
23
+ - [0002](docs/adr/0002-reines-praedikat-binding-ergonomie-separat.md) — Reines
24
+ Prädikat-Binding; Ergonomie (Guards/Decorators/Constraint-Objekte) in separatem Paket
25
+ - [0003](docs/adr/0003-pythonische-parameternamen.md) — Pythonische Parameternamen
26
+ - [0004](docs/adr/0004-strikter-typ-vertrag.md) — Strikter Typ-Vertrag: `TypeError`
27
+ bei Typfehler, sonst Prädikat
28
+
29
+ ## Architektur
30
+
31
+ Drei Schichten, von Rust nach Python:
32
+
33
+ 1. **Rust** ([src/lib.rs](src/lib.rs)): Jede Validierung ist eine `#[pyfunction]`
34
+ (`validate_email`/`validate_url`/`validate_ip`), die an die `validator`-Crate
35
+ delegiert. Registriert werden sie im `#[pymodule] _validator`.
36
+ 2. **maturin** baut die Rust-`cdylib` zur nativen Erweiterung `rusty_validator._validator`
37
+ (siehe `[tool.maturin]` in [pyproject.toml](pyproject.toml): `python-source = "python"`,
38
+ `module-name = "rusty_validator._validator"`).
39
+ 3. **Python** ([`python/rusty_validator/__init__.py`](python/rusty_validator/__init__.py))
40
+ re-exportiert die Funktionen aus `._validator`; die Typen stehen im Stub
41
+ [`__init__.pyi`](python/rusty_validator/__init__.pyi).
42
+
43
+ **Leitprinzipien** (Details in den ADRs):
44
+
45
+ - **Treues Binding**: Verhalten = `validator`-Crate. Überraschende Crate-Ergebnisse
46
+ (z. B. `validate_url("ftp://x")` ist `True`, `validate_ip("::1")` ist `True`) sind
47
+ beabsichtigt geerbt — **nicht** „reparieren". Abweichungen sind dokumentierte
48
+ Ausnahmen (ADR 0001).
49
+ - **Reines Prädikat**: Jede Funktion gibt `bool` zurück. Kein „wirf-bei-ungültig"-Stil
50
+ (Guard), keine Constraint-Objekte, keine Decorators — die gehören in ein **separates**
51
+ Ergonomie-Paket (ADR 0002).
52
+ - **Pythonische Parameternamen**: Parametrisierte Validatoren bekommen lesbare Namen
53
+ (z. B. `min_length` statt Crate-`min`) — bewusste Ausnahme zu ADR 0001 (ADR 0003).
54
+ - **Strikter Typ-Vertrag**: Korrekt typisierte, aber ungültige Eingabe → `False`;
55
+ **typ-falsche** Eingabe (z. B. `None`) → `TypeError`, kein stilles `False` (ADR 0004).
56
+
57
+ ## Repository-Struktur
58
+
59
+ ```text
60
+ rusty-validator/
61
+ ├── CLAUDE.md # Diese Datei
62
+ ├── CONTEXT.md # Domänen-Glossar
63
+ ├── README.md # Nutzer-Doku
64
+ ├── Cargo.toml / Cargo.lock # Rust-Crate (Name, Version, Dependencies)
65
+ ├── pyproject.toml # maturin-Build + mypy-Konfiguration
66
+ ├── docs/adr/ # Architektur-Entscheidungen (ADRs)
67
+ ├── src/lib.rs # Rust: #[pyfunction]s + #[pymodule]
68
+ ├── python/rusty_validator/
69
+ │ ├── __init__.py # Re-Export der nativen Funktionen
70
+ │ ├── __init__.pyi # Typ-Stubs der öffentlichen API
71
+ │ └── py.typed # Markiert das Paket als typisiert (PEP 561)
72
+ ├── tests/ # pytest-Suite (parametrisiert)
73
+ └── .github/workflows/CI.yml # Wheels bauen (Multi-Target) + PyPI-Release bei Tags
74
+ ```
75
+
76
+ ## Entwicklungsbefehle
77
+
78
+ [uv](https://docs.astral.sh/uv/) verwaltet venv, Dev-Dependencies (`[dependency-groups]`
79
+ in [pyproject.toml](pyproject.toml)) und die Python-Version ([.python-version](.python-version));
80
+ **maturin bleibt das Build-Backend** (`[build-system]`). Einmal `uv sync` holt die Tools
81
+ ins `.venv`; danach laufen alle Befehle ohne globale Installation via `uv run`. (Ohne uv
82
+ funktionieren die blanken Befehle im aktiven venv weiterhin.)
83
+
84
+ ```bash
85
+ uv sync # .venv + Dev-Tools (pytest, mypy, maturin) aus uv.lock
86
+ ```
87
+
88
+ ### Bauen & in das venv installieren (Entwicklung)
89
+
90
+ ```bash
91
+ uv run maturin develop # kompiliert die Rust-Erweiterung in das venv
92
+ uv run maturin develop --release # optimierter Build (langsamer zu bauen, schneller zur Laufzeit)
93
+ ```
94
+
95
+ Nach jeder Änderung an [src/lib.rs](src/lib.rs) muss `maturin develop` neu laufen, bevor
96
+ die Python-Tests die Änderung sehen — **uv recompiliert kompilierte Projekte nicht
97
+ automatisch**.
98
+
99
+ ### Tests
100
+
101
+ ```bash
102
+ uv run pytest # Python-Tests (benötigt vorher: maturin develop)
103
+ uv run pytest --cov # mit Coverage
104
+ ```
105
+
106
+ Es gibt bewusst **keine** Rust-`#[cfg(test)]`-Tests: das Binding wird über die
107
+ pytest-Suite gegen die echte Extension geprüft (treuer als ein In-Process-Embedded-Test),
108
+ und die `validator`-Crate testen wir nicht nach (ADR 0001).
109
+
110
+ ### Typprüfung (mypy-Config in pyproject.toml)
111
+
112
+ ```bash
113
+ uv run mypy python # prüft Stubs/Python-Quelle (disallow_untyped_defs aktiv)
114
+ ```
115
+
116
+ ### Rust-Standardwerkzeuge
117
+
118
+ ```bash
119
+ cargo fmt # formatieren
120
+ cargo clippy # Lints
121
+ ```
122
+
123
+ ### Wheels bauen
124
+
125
+ ```bash
126
+ uv run maturin build --release # baut ein Wheel nach target/wheels/
127
+ ```
128
+
129
+ ## Einen neuen Validator hinzufügen
130
+
131
+ Ziel des Projekts ist breite Abdeckung der `validator`-Crate (siehe CONTEXT.md). Beim
132
+ Hinzufügen eines Validators die ADR-Prinzipien einhalten:
133
+
134
+ 1. **Rust** ([src/lib.rs](src/lib.rs)): `#[pyfunction] fn validate_x(...) -> PyResult<bool>`
135
+ anlegen, an die passende `validator`-Trait-Methode delegieren, und im
136
+ `#[pymodule] _validator` per `wrap_pyfunction!` registrieren.
137
+ - **Format-Validator** (argumentlos): Signatur `f(value: String) -> PyResult<bool>`.
138
+ - **Constraint-Validator** (parametrisiert): pythonische Keyword-Parameter (ADR 0003);
139
+ Ergebnis bleibt `bool` (ADR 0002).
140
+ 2. **Python-Oberfläche**: Funktion in [`__init__.py`](python/rusty_validator/__init__.py)
141
+ (`__all__`) und im Stub [`__init__.pyi`](python/rusty_validator/__init__.pyi) ergänzen.
142
+ 3. **Tests** ([tests/](tests/)): parametrisierte `pytest`-Fälle (gültig **und** ungültig);
143
+ für den Typ-Vertrag auch einen `TypeError`-Fall (ADR 0004).
144
+ 4. `maturin develop` ausführen, dann `pytest`.
145
+ 5. **`graphify update .`** ausführen, um den Wissensgraphen aktuell zu halten.
146
+
147
+ ## Release
148
+
149
+ - Die veröffentlichte Version steht in [Cargo.toml](Cargo.toml) (`[package].version`);
150
+ Python bezieht sie dynamisch daraus.
151
+ - Ein **Git-Tag** auslösen → [CI.yml](.github/workflows/CI.yml) baut Wheels für alle
152
+ Targets und published zu PyPI (`MATURIN_PYPI_TOKEN`).
153
+
154
+ ## Codebase-Navigation (graphify)
155
+
156
+ Dieses Projekt hat einen Wissensgraphen unter `graphify-out/` (god nodes, Community-
157
+ Struktur, dateiübergreifende Beziehungen).
158
+
159
+ Regeln:
160
+
161
+ - Für Fragen zur Codebasis zuerst `graphify query "<frage>"` ausführen, wenn
162
+ `graphify-out/graph.json` existiert. `graphify path "<A>" "<B>"` für Beziehungen,
163
+ `graphify explain "<konzept>"` für fokussierte Konzepte. Diese liefern einen
164
+ begrenzten Subgraphen, meist deutlich kleiner als `GRAPH_REPORT.md` oder rohe
165
+ grep-Ausgabe.
166
+ - Existiert `graphify-out/wiki/index.md`, dieses zur groben Navigation nutzen statt
167
+ rohem Quelltext-Browsing.
168
+ - `graphify-out/GRAPH_REPORT.md` nur für breite Architektur-Reviews lesen oder wenn
169
+ query/path/explain nicht genug Kontext liefern.
170
+ - Nach Code-Änderungen `graphify update .` ausführen (nur AST, keine API-Kosten).
@@ -0,0 +1,83 @@
1
+ # Rusty Validator
2
+
3
+ Ein schmales Python-Binding der Rust-Crate `validator` (über PyO3). Es prüft, ob
4
+ ein Wert eine bestimmte Regel erfüllt — die Form einer E-Mail/URL/IP oder eine
5
+ Bedingung wie Länge oder Wertebereich — und gibt das Ergebnis als `bool` zurück.
6
+ Eigene Ergonomie (Decorators, Guards, Constraint-Objekte) gehört bewusst in ein
7
+ separates Paket, nicht hierher.
8
+
9
+ ## Language
10
+
11
+ **Validieren / Validierung**:
12
+ Die Prüfung, ob ein Wert eine **definierte Regel** erfüllt. Das Ergebnis ist ein
13
+ **Prädikat**: für korrekt typisierte Eingaben immer ein `bool`, nie ein
14
+ Validierungs-*Fehler* — der „wirf bei ungültig"-Stil (Guard) lebt im separaten
15
+ Ergonomie-Paket, nicht hier. **Typ-falsche** Eingaben (z. B. `None`) sind ein
16
+ Programmierfehler und lösen wie üblich einen `TypeError` aus, kein stilles `False`.
17
+ Invariante: rein lokal — es wird nie geprüft, ob etwas real existiert oder
18
+ erreichbar ist (kein DNS, kein Netzwerk, keine IO).
19
+ *Avoid*: Prüfen auf Existenz, Verifizieren, Zustellbarkeit; Guard/Erzwingen (andere Operation)
20
+
21
+ **Format-Validator**:
22
+ Ein **argumentloser** Validator, der die **Form** einer Zeichenkette prüft:
23
+ `email`, `url`, `ip` (perspektivisch auch `credit_card`, `phone`,
24
+ `non_control_character`). Signatur stets `f(str) -> bool`.
25
+
26
+ **Constraint-Validator**:
27
+ Ein **parametrisierter** Validator, der eine **Bedingung** an einem Wert prüft:
28
+ `length`, `range`, `contains`, `must_match`. Braucht Argumente (z. B. Grenzen) und
29
+ arbeitet nicht zwingend auf Zeichenketten (`range` prüft Zahlen).
30
+ *Avoid*: Format-Validator (die andere Unterart)
31
+
32
+ **E-Mail-Adresse**:
33
+ Eine **einzelne**, wohlgeformte Adresse `lokalteil@domain` im Sinne von
34
+ `validator::ValidateEmail`. Rein syntaktisch — keine Existenz-/MX-Prüfung. **Keine**
35
+ Anzeigenamen-Form (`Max <max@x.de>`) und **keine** Liste mehrerer Adressen.
36
+ *Avoid*: E-Mail (zu unscharf, wenn die Adresse gemeint ist), Mail-Konto
37
+
38
+ **URL**:
39
+ Eine wohlgeformte **absolute** URL im Sinne von `validator::ValidateUrl` (RFC 3986),
40
+ also *mit* Schema und Host. Das Schema ist **nicht** auf Web beschränkt: `http`,
41
+ `https`, `ftp`, `mailto`, `file` und sogar erfundene Schemata gelten als gültig.
42
+ „example.com" (ohne Schema) ist hingegen keine gültige URL. Die Grenze definiert
43
+ allein die Crate — wir engen sie nicht auf http/https ein.
44
+ *Avoid*: URI, Link, Web-Adresse (suggeriert fälschlich „nur http/https")
45
+
46
+ **IP-Adresse**:
47
+ Eine wohlgeformte IP-Adresse im Sinne von `validator::ValidateIp` — **IPv4 oder
48
+ IPv6** sind beide eine IP-Adresse (`127.0.0.1` ebenso wie `::1`). Rein syntaktisch,
49
+ keine Erreichbarkeitsprüfung. **IPv4** und **IPv6** sind unterscheidbare Unterarten,
50
+ die getrennt prüfbar gemacht werden.
51
+ *Avoid*: Host, Hostname (das ist ein Name, keine Adresse)
52
+
53
+ ## Beispieldialog
54
+
55
+ **Entwickler:** `validate_url("ftp://files.example.com")` gibt `True` — ist das ein Bug?
56
+
57
+ **Expertin:** Nein. Eine **URL** ist bei uns jede wohlgeformte absolute URL, egal
58
+ welches Schema. Wir reichen die `validator`-Crate treu durch und engen sie nicht auf
59
+ http/https ein.
60
+
61
+ **Entwickler:** Ich *will* aber nur Web-Adressen zulassen.
62
+
63
+ **Expertin:** Das ist eine eigene **Regel** über der Validierung — die gehört ins
64
+ Ergonomie-Paket, nicht ins Binding. Das Binding sagt nur „wohlgeformt: ja/nein".
65
+
66
+ **Entwickler:** Und `validate_length`? Das ist doch keine Form.
67
+
68
+ **Expertin:** Genau. `email`/`url`/`ip` sind **Format-Validatoren** (argumentlos,
69
+ prüfen die Form). `length`/`range`/`contains` sind **Constraint-Validatoren**
70
+ (parametrisiert, prüfen eine Bedingung). Beides ist **Validierung**, beides liefert
71
+ ein **Prädikat**.
72
+
73
+ **Entwickler:** Und wenn ich `validate_ip(None)` übergebe?
74
+
75
+ **Expertin:** `TypeError` — ein Programmierfehler, kein „ungültig". Ein kaputter
76
+ *String* gibt `False`; ein **falscher Typ** wirft. Und wenn bei Ungültigkeit ein
77
+ Fehler fliegen soll, brauchst du einen **Guard** — den gibt es hier nicht, er lebt
78
+ im Ergonomie-Paket.
79
+
80
+ **Entwickler:** `validate_ip("::1")`?
81
+
82
+ **Expertin:** `True` — eine **IP-Adresse** ist v4 *oder* v6. Willst du gezielt nur
83
+ v6, nimm `validate_ipv6`.