simple-module-core 0.0.12__tar.gz → 0.0.14__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.
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/PKG-INFO +1 -1
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/pyproject.toml +1 -1
- simple_module_core-0.0.14/tests/test_dotenv.py +127 -0
- simple_module_core-0.0.14/tests/test_environments.py +30 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/.gitignore +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/LICENSE +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/README.md +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/__init__.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/__main__.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/__init__.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_coupling.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_i18n.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_inertia_api.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_js_workspace.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_migration.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_module.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_pages.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_runner.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_types.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/discovery.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/dotenv.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/environments.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/events.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/exceptions.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/feature_flags.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/health.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/i18n.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/menu.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/module.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/permissions.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/py.typed +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/services.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/versioning.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_diagnostics.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_discovery.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_events.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_feature_flags.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_feature_flags_decorator.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_health_registry.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_i18n.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_i18n_diagnostics.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_menu.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_module_base.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_module_diagnostics.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_permissions.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_services.py +0 -0
- {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_versioning.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simple_module_core
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.14
|
|
4
4
|
Summary: Module-system primitives for the simple_module framework — ModuleBase, discovery, diagnostics, events
|
|
5
5
|
Project-URL: Homepage, https://github.com/antosubash/simple_module_python
|
|
6
6
|
Project-URL: Repository, https://github.com/antosubash/simple_module_python
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Unit tests for the dependency-free ``.env`` parser.
|
|
2
|
+
|
|
3
|
+
``parse_dotenv`` is invoked by the diagnostics CLI, the users-module
|
|
4
|
+
bootstrap, and every worker entrypoint before settings construction — a bug
|
|
5
|
+
here is hard to debug because it manifests as "the setting just isn't there".
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from simple_module_core.dotenv import (
|
|
12
|
+
env_bool,
|
|
13
|
+
env_str,
|
|
14
|
+
load_dotenv_into_environ,
|
|
15
|
+
parse_dotenv,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestParseDotenv:
|
|
20
|
+
def test_missing_file_returns_empty(self, tmp_path):
|
|
21
|
+
assert parse_dotenv(tmp_path / "absent.env") == {}
|
|
22
|
+
|
|
23
|
+
def test_basic_keys(self, tmp_path):
|
|
24
|
+
env = tmp_path / ".env"
|
|
25
|
+
env.write_text("FOO=bar\nBAZ=qux\n", encoding="utf-8")
|
|
26
|
+
assert parse_dotenv(env) == {"FOO": "bar", "BAZ": "qux"}
|
|
27
|
+
|
|
28
|
+
def test_blank_lines_and_comments_ignored(self, tmp_path):
|
|
29
|
+
env = tmp_path / ".env"
|
|
30
|
+
env.write_text(
|
|
31
|
+
"# leading comment\n\nFOO=bar\n \n# inline-style # not stripped\nBAZ=qux\n",
|
|
32
|
+
encoding="utf-8",
|
|
33
|
+
)
|
|
34
|
+
assert parse_dotenv(env) == {"FOO": "bar", "BAZ": "qux"}
|
|
35
|
+
|
|
36
|
+
def test_quotes_stripped_matching_pairs(self, tmp_path):
|
|
37
|
+
env = tmp_path / ".env"
|
|
38
|
+
env.write_text(
|
|
39
|
+
"A=\"double\"\nB='single'\nC=plain\n",
|
|
40
|
+
encoding="utf-8",
|
|
41
|
+
)
|
|
42
|
+
assert parse_dotenv(env) == {"A": "double", "B": "single", "C": "plain"}
|
|
43
|
+
|
|
44
|
+
def test_value_with_equals_keeps_remainder(self, tmp_path):
|
|
45
|
+
"""KEY=foo=bar=baz must parse as KEY -> "foo=bar=baz" (first ``=`` splits).
|
|
46
|
+
|
|
47
|
+
Tokens, JWTs and database URLs frequently contain ``=`` — losing them
|
|
48
|
+
would silently break SMTP/JWT configuration in prod.
|
|
49
|
+
"""
|
|
50
|
+
env = tmp_path / ".env"
|
|
51
|
+
env.write_text("URL=postgresql://u:p=raw@h/db\n", encoding="utf-8")
|
|
52
|
+
assert parse_dotenv(env) == {"URL": "postgresql://u:p=raw@h/db"}
|
|
53
|
+
|
|
54
|
+
def test_whitespace_around_key_and_value_trimmed(self, tmp_path):
|
|
55
|
+
env = tmp_path / ".env"
|
|
56
|
+
env.write_text(" KEY = value \n", encoding="utf-8")
|
|
57
|
+
assert parse_dotenv(env) == {"KEY": "value"}
|
|
58
|
+
|
|
59
|
+
def test_no_equals_line_skipped(self, tmp_path):
|
|
60
|
+
env = tmp_path / ".env"
|
|
61
|
+
env.write_text("VALID=1\nbroken line without equals\nANOTHER=2\n", encoding="utf-8")
|
|
62
|
+
assert parse_dotenv(env) == {"VALID": "1", "ANOTHER": "2"}
|
|
63
|
+
|
|
64
|
+
def test_default_path_uses_sm_project_root(self, tmp_path, monkeypatch):
|
|
65
|
+
(tmp_path / ".env").write_text("ROOTED=yes\n", encoding="utf-8")
|
|
66
|
+
monkeypatch.setenv("SM_PROJECT_ROOT", str(tmp_path))
|
|
67
|
+
assert parse_dotenv() == {"ROOTED": "yes"}
|
|
68
|
+
|
|
69
|
+
def test_default_path_falls_back_to_cwd(self, tmp_path, monkeypatch):
|
|
70
|
+
(tmp_path / ".env").write_text("CWD_KEY=present\n", encoding="utf-8")
|
|
71
|
+
monkeypatch.delenv("SM_PROJECT_ROOT", raising=False)
|
|
72
|
+
monkeypatch.chdir(tmp_path)
|
|
73
|
+
assert parse_dotenv() == {"CWD_KEY": "present"}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TestLoadDotenvIntoEnviron:
|
|
77
|
+
def test_setdefault_semantics_preserves_existing_env(self, tmp_path, monkeypatch):
|
|
78
|
+
"""Real ``os.environ`` wins over file values — same precedence as uvicorn."""
|
|
79
|
+
(tmp_path / ".env").write_text("KEY=from_file\n", encoding="utf-8")
|
|
80
|
+
monkeypatch.setenv("KEY", "from_shell")
|
|
81
|
+
load_dotenv_into_environ(tmp_path / ".env")
|
|
82
|
+
import os
|
|
83
|
+
|
|
84
|
+
assert os.environ["KEY"] == "from_shell"
|
|
85
|
+
|
|
86
|
+
def test_loads_missing_keys(self, tmp_path, monkeypatch):
|
|
87
|
+
(tmp_path / ".env").write_text("NEW_KEY_FOR_LOAD_TEST=picked_up\n", encoding="utf-8")
|
|
88
|
+
monkeypatch.delenv("NEW_KEY_FOR_LOAD_TEST", raising=False)
|
|
89
|
+
load_dotenv_into_environ(tmp_path / ".env")
|
|
90
|
+
import os
|
|
91
|
+
|
|
92
|
+
assert os.environ["NEW_KEY_FOR_LOAD_TEST"] == "picked_up"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TestEnvStr:
|
|
96
|
+
def test_returns_value(self, monkeypatch):
|
|
97
|
+
monkeypatch.setenv("X", "ok")
|
|
98
|
+
assert env_str("X", "default") == "ok"
|
|
99
|
+
|
|
100
|
+
def test_returns_default_when_unset(self, monkeypatch):
|
|
101
|
+
monkeypatch.delenv("X", raising=False)
|
|
102
|
+
assert env_str("X", "default") == "default"
|
|
103
|
+
|
|
104
|
+
def test_returns_default_for_whitespace_only(self, monkeypatch):
|
|
105
|
+
monkeypatch.setenv("X", " ")
|
|
106
|
+
assert env_str("X", "default") == "default"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class TestEnvBool:
|
|
110
|
+
@pytest.mark.parametrize("raw", ["1", "true", "TRUE", "yes", "y", "on", " T "])
|
|
111
|
+
def test_truthy(self, raw, monkeypatch):
|
|
112
|
+
monkeypatch.setenv("X", raw)
|
|
113
|
+
assert env_bool("X", default=False) is True
|
|
114
|
+
|
|
115
|
+
@pytest.mark.parametrize("raw", ["0", "false", "FALSE", "no", "n", "off"])
|
|
116
|
+
def test_falsy(self, raw, monkeypatch):
|
|
117
|
+
monkeypatch.setenv("X", raw)
|
|
118
|
+
assert env_bool("X", default=True) is False
|
|
119
|
+
|
|
120
|
+
def test_unset_uses_default(self, monkeypatch):
|
|
121
|
+
monkeypatch.delenv("X", raising=False)
|
|
122
|
+
assert env_bool("X", default=True) is True
|
|
123
|
+
assert env_bool("X", default=False) is False
|
|
124
|
+
|
|
125
|
+
def test_unparseable_uses_default(self, monkeypatch):
|
|
126
|
+
monkeypatch.setenv("X", "definitely-not-a-bool")
|
|
127
|
+
assert env_bool("X", default=True) is True
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Sanity check for the shared NON_PROD_ENVIRONMENTS constant.
|
|
2
|
+
|
|
3
|
+
A single source of truth is the whole point of this module — both host and
|
|
4
|
+
module settings validators read this. If anyone reverts to a duplicated
|
|
5
|
+
literal, the production placeholder-secret check could diverge between host
|
|
6
|
+
and modules and silently let an insecure deployment boot.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from simple_module_core.environments import NON_PROD_ENVIRONMENTS
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_contains_development_and_testing():
|
|
15
|
+
assert "development" in NON_PROD_ENVIRONMENTS
|
|
16
|
+
assert "testing" in NON_PROD_ENVIRONMENTS
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_does_not_contain_production_aliases():
|
|
20
|
+
"""Nothing prod-like should be treated as non-prod."""
|
|
21
|
+
for name in ("production", "prod", "staging", "live", ""):
|
|
22
|
+
assert name not in NON_PROD_ENVIRONMENTS
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_is_frozenset():
|
|
26
|
+
"""Freezing makes it tamper-proof — code that does
|
|
27
|
+
``NON_PROD_ENVIRONMENTS.add("staging")`` to "fix" their deployment will
|
|
28
|
+
blow up at import time instead of widening the security envelope.
|
|
29
|
+
"""
|
|
30
|
+
assert isinstance(NON_PROD_ENVIRONMENTS, frozenset)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/__init__.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_coupling.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_i18n.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_migration.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_module.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_pages.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_runner.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_feature_flags_decorator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|