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.
Files changed (47) hide show
  1. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/PKG-INFO +1 -1
  2. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/pyproject.toml +1 -1
  3. simple_module_core-0.0.14/tests/test_dotenv.py +127 -0
  4. simple_module_core-0.0.14/tests/test_environments.py +30 -0
  5. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/.gitignore +0 -0
  6. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/LICENSE +0 -0
  7. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/README.md +0 -0
  8. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/__init__.py +0 -0
  9. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/__main__.py +0 -0
  10. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/__init__.py +0 -0
  11. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_coupling.py +0 -0
  12. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_i18n.py +0 -0
  13. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_inertia_api.py +0 -0
  14. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_js_workspace.py +0 -0
  15. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_migration.py +0 -0
  16. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_module.py +0 -0
  17. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_pages.py +0 -0
  18. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_runner.py +0 -0
  19. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/diagnostics/_types.py +0 -0
  20. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/discovery.py +0 -0
  21. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/dotenv.py +0 -0
  22. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/environments.py +0 -0
  23. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/events.py +0 -0
  24. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/exceptions.py +0 -0
  25. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/feature_flags.py +0 -0
  26. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/health.py +0 -0
  27. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/i18n.py +0 -0
  28. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/menu.py +0 -0
  29. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/module.py +0 -0
  30. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/permissions.py +0 -0
  31. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/py.typed +0 -0
  32. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/services.py +0 -0
  33. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/simple_module_core/versioning.py +0 -0
  34. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_diagnostics.py +0 -0
  35. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_discovery.py +0 -0
  36. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_events.py +0 -0
  37. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_feature_flags.py +0 -0
  38. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_feature_flags_decorator.py +0 -0
  39. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_health_registry.py +0 -0
  40. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_i18n.py +0 -0
  41. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_i18n_diagnostics.py +0 -0
  42. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_menu.py +0 -0
  43. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_module_base.py +0 -0
  44. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_module_diagnostics.py +0 -0
  45. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_permissions.py +0 -0
  46. {simple_module_core-0.0.12 → simple_module_core-0.0.14}/tests/test_services.py +0 -0
  47. {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.12
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "simple_module_core"
3
- version = "0.0.12"
3
+ version = "0.0.14"
4
4
  description = "Module-system primitives for the simple_module framework — ModuleBase, discovery, diagnostics, events"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -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)