violet-poolController-api 0.0.37__tar.gz → 0.0.38__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.
Files changed (35) hide show
  1. {violet_poolcontroller_api-0.0.37/violet_poolController_api.egg-info → violet_poolcontroller_api-0.0.38}/PKG-INFO +1 -1
  2. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/pyproject.toml +1 -1
  3. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/tests/test_api.py +1 -1
  4. violet_poolcontroller_api-0.0.38/tests/test_language_policy.py +136 -0
  5. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38/violet_poolController_api.egg-info}/PKG-INFO +1 -1
  6. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolController_api.egg-info/SOURCES.txt +1 -0
  7. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/__init__.py +1 -1
  8. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/api.py +1 -1
  9. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/circuit_breaker.py +1 -1
  10. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/const_api.py +1 -1
  11. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/const_devices.py +1 -1
  12. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/parsers.py +1 -1
  13. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/readings.py +1 -1
  14. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/utils_rate_limiter.py +7 -7
  15. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/utils_sanitizer.py +14 -14
  16. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/LICENSE +0 -0
  17. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/README.md +0 -0
  18. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/setup.cfg +0 -0
  19. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/tests/test_api_smoke.py +0 -0
  20. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/tests/test_circuit_breaker.py +0 -0
  21. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/tests/test_mock_server.py +0 -0
  22. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/tests/test_parsers.py +0 -0
  23. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/tests/test_rate_limiter.py +0 -0
  24. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/tests/test_readings.py +0 -0
  25. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/tests/test_sanitizer.py +0 -0
  26. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolController_api.egg-info/dependency_links.txt +0 -0
  27. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolController_api.egg-info/requires.txt +0 -0
  28. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolController_api.egg-info/top_level.txt +0 -0
  29. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/_api_dosing.py +0 -0
  30. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/_api_mixin.py +0 -0
  31. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/_api_model.py +0 -0
  32. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/_api_outputs.py +0 -0
  33. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/_api_readings.py +0 -0
  34. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/_api_system.py +0 -0
  35. {violet_poolcontroller_api-0.0.37 → violet_poolcontroller_api-0.0.38}/violet_poolcontroller_api/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: violet-poolController-api
3
- Version: 0.0.37
3
+ Version: 0.0.38
4
4
  Summary: Asynchronous Python client for the Violet Pool Controller.
5
5
  Author-email: "Basti (Xerolux)" <git@xerolux.de>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "violet-poolController-api"
7
- version = "0.0.37"
7
+ version = "0.0.38"
8
8
  authors = [
9
9
  { name="Basti (Xerolux)", email="git@xerolux.de" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- # violet-poolController-api - API für Violet Pool Controller
1
+ # violet-poolController-api - API for Violet Pool Controller
2
2
  # Copyright (C) 2024-2026 Xerolux
3
3
  #
4
4
  # This program is free software: you can redistribute it and/or modify
@@ -0,0 +1,136 @@
1
+ # violet-poolController-api - API for Violet Pool Controller
2
+ # Copyright (C) 2024-2026 Xerolux
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU Affero General Public License as published
6
+ # by the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU Affero General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Affero General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ """Everything written into this repository is English.
18
+
19
+ The policy lives in AGENTS.md; this file is what makes it hold. Two things are
20
+ deliberately exempt and must stay exempt: the controller's own error strings in
21
+ ``const_api.py`` (and the German payloads the mock server replays), which are
22
+ data rather than prose, and the German half of the bilingual documentation
23
+ under ``docs/de/``.
24
+ """
25
+
26
+ # ruff: noqa: S101
27
+
28
+ from __future__ import annotations
29
+
30
+ import re
31
+ from pathlib import Path
32
+
33
+ import pytest
34
+
35
+ REPO = Path(__file__).parent.parent
36
+ PACKAGE = REPO / "violet_poolcontroller_api"
37
+
38
+ # Text the controller emits, reproduced verbatim. Translating it would break
39
+ # the match with what the device actually says.
40
+ #
41
+ # Matched by file name, not by path: `python -m build` copies the package into
42
+ # `build/lib/`, and an exemption keyed to the original path does not cover the
43
+ # copy. That is not hypothetical - it failed the 0.0.38 release job, because
44
+ # the release builds the wheel before running the checks.
45
+ CONTROLLER_VERBATIM = frozenset({"const_api.py", "mock_server.py"})
46
+
47
+ # This file has to name the German words it looks for.
48
+ SELF = Path(__file__).name
49
+
50
+ # Directories that hold generated or vendored copies of the sources. Scanning
51
+ # them says nothing about what is written in this repository.
52
+ GENERATED_DIRS = frozenset({".git", ".tox", ".venv", "venv", "build", "dist", "__pycache__"})
53
+
54
+ # Words that only appear in German prose. Deliberately not "in", "die" or "der":
55
+ # those collide with English or with identifiers.
56
+ GERMAN_WORDS = (
57
+ "für",
58
+ "über",
59
+ "nicht",
60
+ "wird",
61
+ "werden",
62
+ "wenn",
63
+ "diese",
64
+ "dieser",
65
+ "keine",
66
+ "sollte",
67
+ )
68
+ _GERMAN = re.compile(r"\b(" + "|".join(GERMAN_WORDS) + r")\b", re.IGNORECASE)
69
+
70
+
71
+ def _python_sources() -> list[Path]:
72
+ """Return every Python file the policy applies to."""
73
+ return sorted(
74
+ path
75
+ for path in REPO.rglob("*.py")
76
+ if not (GENERATED_DIRS & set(path.parts))
77
+ and not any(part.endswith(".egg-info") for part in path.parts)
78
+ and path.name not in CONTROLLER_VERBATIM
79
+ and path.name != SELF
80
+ )
81
+
82
+
83
+ @pytest.mark.parametrize("path", _python_sources(), ids=lambda p: p.name)
84
+ def test_python_sources_are_english(path: Path) -> None:
85
+ """A German comment is read by people who do not speak German."""
86
+ offenders = [
87
+ f"{path.relative_to(REPO)}:{number}: {line.strip()}"
88
+ for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1)
89
+ if _GERMAN.search(line)
90
+ ]
91
+
92
+ assert not offenders, "German text outside the exempt files:\n" + "\n".join(offenders)
93
+
94
+
95
+ def test_the_changelog_is_english() -> None:
96
+ """The changelog is what a consumer reads when a version breaks something.
97
+
98
+ Code spans are quotations, not prose: an entry that removes a German string
99
+ has to be able to name the string it removed.
100
+ """
101
+ changelog = (REPO / "CHANGELOG.md").read_text(encoding="utf-8")
102
+ prose = re.sub(r"`[^`]*`", "", changelog)
103
+
104
+ offenders = sorted({match.group(0).lower() for match in _GERMAN.finditer(prose)})
105
+
106
+ assert not offenders, f"German outside code spans in CHANGELOG.md: {offenders}"
107
+
108
+
109
+ def test_the_controller_strings_stay_german() -> None:
110
+ """The exemption is the point, not an oversight - guard it too.
111
+
112
+ If someone "cleans up" the error table into English, the messages stop
113
+ matching what the device reports.
114
+ """
115
+ const_api = (PACKAGE / "const_api.py").read_text(encoding="utf-8")
116
+
117
+ assert "Filterdrucküberwachung (Druck zu niedrig)" in const_api
118
+
119
+
120
+ def test_generated_copies_are_not_scanned() -> None:
121
+ """A built wheel is not source, and scanning its copy fails the release.
122
+
123
+ `python -m build` writes `build/lib/<package>/`, so the release job runs
124
+ the checks with a duplicate of every module on disk.
125
+ """
126
+ scanned = {path.name for path in _python_sources()}
127
+
128
+ assert "const_api.py" not in scanned
129
+ assert not [path for path in _python_sources() if "build" in path.parts]
130
+
131
+
132
+ def test_the_policy_is_written_down() -> None:
133
+ """Without the rule in AGENTS.md this file is just an opinion."""
134
+ agents = (REPO / "AGENTS.md").read_text(encoding="utf-8")
135
+
136
+ assert "## Language Policy" in agents
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: violet-poolController-api
3
- Version: 0.0.37
3
+ Version: 0.0.38
4
4
  Summary: Asynchronous Python client for the Violet Pool Controller.
5
5
  Author-email: "Basti (Xerolux)" <git@xerolux.de>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  tests/test_api.py
5
5
  tests/test_api_smoke.py
6
6
  tests/test_circuit_breaker.py
7
+ tests/test_language_policy.py
7
8
  tests/test_mock_server.py
8
9
  tests/test_parsers.py
9
10
  tests/test_rate_limiter.py
@@ -1,4 +1,4 @@
1
- # violet-poolController-api - API für Violet Pool Controller
1
+ # violet-poolController-api - API for Violet Pool Controller
2
2
  # Copyright (C) 2024-2026 Xerolux
3
3
  #
4
4
  # This program is free software: you can redistribute it and/or modify
@@ -1,4 +1,4 @@
1
- # violet-poolController-api - API für Violet Pool Controller
1
+ # violet-poolController-api - API for Violet Pool Controller
2
2
  # Copyright (C) 2024-2026 Xerolux
3
3
  #
4
4
  # This program is free software: you can redistribute it and/or modify
@@ -1,4 +1,4 @@
1
- # violet-poolController-api - API für Violet Pool Controller
1
+ # violet-poolController-api - API for Violet Pool Controller
2
2
  # Copyright (C) 2024-2026 Xerolux
3
3
  #
4
4
  # This program is free software: you can redistribute it and/or modify
@@ -1,4 +1,4 @@
1
- # violet-poolController-api - API für Violet Pool Controller
1
+ # violet-poolController-api - API for Violet Pool Controller
2
2
  # Copyright (C) 2024-2026 Xerolux
3
3
  #
4
4
  # This program is free software: you can redistribute it and/or modify
@@ -1,4 +1,4 @@
1
- # violet-poolController-api - API für Violet Pool Controller
1
+ # violet-poolController-api - API for Violet Pool Controller
2
2
  # Copyright (C) 2024-2026 Xerolux
3
3
  #
4
4
  # This program is free software: you can redistribute it and/or modify
@@ -1,4 +1,4 @@
1
- # violet-poolController-api - API für Violet Pool Controller
1
+ # violet-poolController-api - API for Violet Pool Controller
2
2
  # Copyright (C) 2024-2026 Xerolux
3
3
  #
4
4
  # This program is free software: you can redistribute it and/or modify
@@ -1,4 +1,4 @@
1
- # violet-poolController-api - API für Violet Pool Controller
1
+ # violet-poolController-api - API for Violet Pool Controller
2
2
  # Copyright (C) 2024-2026 Xerolux
3
3
  #
4
4
  # This program is free software: you can redistribute it and/or modify
@@ -1,4 +1,4 @@
1
- # violet-poolController-api - API für Violet Pool Controller
1
+ # violet-poolController-api - API for Violet Pool Controller
2
2
  # Copyright (C) 2024-2026 Xerolux
3
3
  #
4
4
  # This program is free software: you can redistribute it and/or modify
@@ -14,7 +14,7 @@
14
14
  # You should have received a copy of the GNU Affero General Public License
15
15
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
 
17
- """Rate Limiter für API-Requests - Token Bucket Algorithm."""
17
+ """Rate limiter for API requests - token bucket algorithm."""
18
18
 
19
19
  from __future__ import annotations
20
20
 
@@ -34,8 +34,8 @@ class RateLimiter:
34
34
 
35
35
  Verhindert API-Overload durch:
36
36
  - Maximale Requests pro Zeitfenster
37
- - Burst-Support für kurzzeitige Spitzen
38
- - Priority Queue für kritische Requests
37
+ - Burst support for short spikes
38
+ - Priority queue for critical requests
39
39
  - Graceful Degradation bei Limit-Überschreitung
40
40
  """
41
41
 
@@ -89,7 +89,7 @@ class RateLimiter:
89
89
  "last_minute_reset": time.monotonic(),
90
90
  }
91
91
 
92
- # Lock für Thread-Safety
92
+ # Lock for thread safety
93
93
  self._lock = asyncio.Lock()
94
94
  self._waiters: list[tuple[int, int, asyncio.Event]] = []
95
95
  self._waiter_sequence = 0
@@ -169,7 +169,7 @@ class RateLimiter:
169
169
  timeout: Maximale Wartezeit in Sekunden
170
170
 
171
171
  Raises:
172
- TimeoutError: Wenn Timeout erreicht
172
+ TimeoutError: If the timeout is reached
173
173
 
174
174
  """
175
175
  start_time = time.monotonic()
@@ -279,7 +279,7 @@ class RateLimiter:
279
279
  _LOGGER.debug("Rate Limiter zurückgesetzt")
280
280
 
281
281
 
282
- # Global Rate Limiter-Instanz (kann pro API-Instanz auch separat erstellt werden)
282
+ # Global rate limiter instance (one can also be created per API instance)
283
283
  _global_rate_limiter: RateLimiter | None = None
284
284
 
285
285
 
@@ -1,4 +1,4 @@
1
- # violet-poolController-api - API für Violet Pool Controller
1
+ # violet-poolController-api - API for Violet Pool Controller
2
2
  # Copyright (C) 2024-2026 Xerolux
3
3
  #
4
4
  # This program is free software: you can redistribute it and/or modify
@@ -15,7 +15,7 @@
15
15
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
 
17
17
 
18
- """Input Sanitization Utilities für User-Inputs und API-Parameter."""
18
+ """Input sanitization utilities for user inputs and API parameters."""
19
19
 
20
20
  from __future__ import annotations
21
21
 
@@ -33,11 +33,11 @@ _MAX_API_PARAM_LENGTH = 100
33
33
 
34
34
 
35
35
  class InputSanitizer:
36
- """Input Sanitization für Sicherheit und Datenintegrität.
36
+ """Input sanitization for security and data integrity.
37
37
 
38
38
  Schützt vor:
39
39
  - XSS (Cross-Site Scripting)
40
- - SQL Injection (nicht relevant bei HTTP API, aber defensiv)
40
+ - SQL injection (not relevant for an HTTP API, but defended against)
41
41
  - Command Injection
42
42
  - Path Traversal
43
43
  - Unerwarteten Zeichen
@@ -85,14 +85,14 @@ class InputSanitizer:
85
85
  # Konvertiere zu String
86
86
  str_value = str(value).strip()
87
87
 
88
- # Unicode-Normalisierung (NFKD für Defense-in-Depth)
88
+ # Unicode normalisation (NFKD, defence in depth)
89
89
  # Normalized Form Compatibility Decomposition
90
90
  str_value = unicodedata.normalize("NFKD", str_value)
91
91
 
92
92
  # Längen-Validierung
93
93
  if len(str_value) > max_length:
94
94
  _LOGGER.warning(
95
- "String zu lang (%d > %d), wird gekürzt: %s...",
95
+ "String too long (%d > %d), truncating: %s...",
96
96
  len(str_value),
97
97
  max_length,
98
98
  str_value[:50],
@@ -233,7 +233,7 @@ class InputSanitizer:
233
233
  float_value = float(value)
234
234
  if not math.isfinite(float_value):
235
235
  _LOGGER.warning(
236
- "Nicht-endlicher Float-Wert '%s', verwende default %.2f",
236
+ "Non-finite float value '%s', using default %.2f",
237
237
  value,
238
238
  default,
239
239
  )
@@ -315,7 +315,7 @@ class InputSanitizer:
315
315
 
316
316
  """
317
317
  if not key:
318
- msg = "Device-Key darf nicht leer sein"
318
+ msg = "Device key must not be empty"
319
319
  raise ValueError(msg)
320
320
 
321
321
  # Nur Großbuchstaben, Zahlen und Underscore erlaubt
@@ -351,7 +351,7 @@ class InputSanitizer:
351
351
 
352
352
  """
353
353
  if not param:
354
- msg = "API-Parameter darf nicht leer sein"
354
+ msg = "API parameter must not be empty"
355
355
  raise ValueError(msg)
356
356
 
357
357
  # Prüfe auf Path Traversal VOR der Bereinigung
@@ -458,27 +458,27 @@ class InputSanitizer:
458
458
  )
459
459
 
460
460
 
461
- # Singleton-Instanz für einfachen Zugriff
461
+ # Singleton instance for convenient access
462
462
  _sanitizer = InputSanitizer()
463
463
 
464
464
 
465
465
  def sanitize_string(*args: Any, **kwargs: Any) -> str: # noqa: ANN401
466
- """Shortcut für InputSanitizer.sanitize_string()."""
466
+ """Shortcut for InputSanitizer.sanitize_string()."""
467
467
  return _sanitizer.sanitize_string(*args, **kwargs)
468
468
 
469
469
 
470
470
  def sanitize_integer(*args: Any, **kwargs: Any) -> int: # noqa: ANN401
471
- """Shortcut für InputSanitizer.sanitize_integer()."""
471
+ """Shortcut for InputSanitizer.sanitize_integer()."""
472
472
  return _sanitizer.sanitize_integer(*args, **kwargs)
473
473
 
474
474
 
475
475
  def sanitize_float(*args: Any, **kwargs: Any) -> float: # noqa: ANN401
476
- """Shortcut für InputSanitizer.sanitize_float()."""
476
+ """Shortcut for InputSanitizer.sanitize_float()."""
477
477
  return _sanitizer.sanitize_float(*args, **kwargs)
478
478
 
479
479
 
480
480
  def sanitize_boolean(*args: Any, **kwargs: Any) -> bool: # noqa: ANN401
481
- """Shortcut für InputSanitizer.sanitize_boolean()."""
481
+ """Shortcut for InputSanitizer.sanitize_boolean()."""
482
482
  return _sanitizer.sanitize_boolean(*args, **kwargs)
483
483
 
484
484