simple-module-settings 0.0.37__tar.gz → 0.0.39__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_settings-0.0.37 → simple_module_settings-0.0.39}/.gitignore +1 -2
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/PKG-INFO +7 -7
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/README.md +2 -2
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/package.json +1 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/pyproject.toml +5 -5
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/_announce.py +2 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/_field_meta.py +6 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/_listing.py +4 -8
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/_managed_keys.py +2 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/_module_settings.py +14 -9
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/_row_masking.py +5 -4
- simple_module_settings-0.0.39/settings/_secrets.py +17 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/_unique_write.py +3 -2
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/cli.py +4 -4
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/constants.py +25 -33
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/contracts/accessor.py +2 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/contracts/registry.py +3 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/contracts/schemas.py +5 -7
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/deps.py +2 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/endpoints/api.py +1 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/endpoints/module_api.py +5 -4
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/endpoints/views.py +2 -2
- simple_module_settings-0.0.39/settings/exceptions.py +5 -0
- simple_module_settings-0.0.39/settings/hydrate.py +36 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/known_keys.py +1 -1
- simple_module_settings-0.0.39/settings/locales/de.json +152 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/locales/en.json +3 -1
- simple_module_settings-0.0.39/settings/models.py +12 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/module.py +15 -12
- simple_module_settings-0.0.39/settings/module_registry.py +17 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/Browse.tsx +0 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/ModulesEdit.tsx +0 -1
- simple_module_settings-0.0.39/settings/pages/api.generated.ts +1043 -0
- simple_module_settings-0.0.39/settings/pages/api.ts +9 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/ModuleForm.tsx +17 -10
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/TestConnectionButton.tsx +10 -9
- simple_module_settings-0.0.39/settings/registration.py +17 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/reload.py +3 -3
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/service.py +5 -8
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/services.py +6 -4
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/store.py +13 -41
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/tenant_view.py +5 -2
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_clear_route_per_scope.py +1 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_cli_import.py +9 -8
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_duplicate_and_race.py +1 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_env_pinned.py +1 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_hydrate.py +6 -5
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_hydrate_related_fields.py +1 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_known_keys_meta.py +12 -12
- simple_module_settings-0.0.39/tests/test_lenient_related_fields_validators.py +110 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_module_api_json_fields.py +1 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_module_registry.py +1 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_module_settings.py +14 -14
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_registration.py +2 -3
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_reload.py +1 -1
- simple_module_settings-0.0.39/tests/test_settings_domain_errors.py +10 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_env_unprefixed.py +1 -1
- simple_module_settings-0.0.39/tests/test_settings_framework_reexports.py +48 -0
- simple_module_settings-0.0.39/tests/test_settings_group_metadata.py +30 -0
- simple_module_settings-0.0.39/tests/test_settings_host_restart_flags.py +22 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_schemas.py +1 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_secret_json_roundtrip.py +2 -2
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_secret_masking.py +2 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_secret_urls.py +4 -4
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_service.py +1 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_typed_secret_masking.py +2 -2
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_store_filters.py +3 -3
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_store_secret_masking.py +3 -3
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_testable_checks.py +5 -5
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_upload_key_delete.py +1 -1
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests-js/JsonFields.test.tsx +4 -4
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests-js/ModuleForm.test.tsx +20 -19
- simple_module_settings-0.0.39/tests-js/RestartHint.test.tsx +57 -0
- simple_module_settings-0.0.39/tests-js/settings_TestConnectionButton.test.tsx +47 -0
- simple_module_settings-0.0.37/settings/_secrets.py +0 -137
- simple_module_settings-0.0.37/settings/hydrate.py +0 -223
- simple_module_settings-0.0.37/settings/models.py +0 -50
- simple_module_settings-0.0.37/settings/module_registry.py +0 -53
- simple_module_settings-0.0.37/settings/registration.py +0 -40
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/LICENSE +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/__init__.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/_module_settings_props.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/browse_query.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/contracts/__init__.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/contracts/events.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/contracts/invalidation.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/endpoints/__init__.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/endpoints/_create_form.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/endpoints/tenant_api.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/env_vars.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/errors.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/Create.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/Edit.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/FieldInput.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/FieldSource.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/JsonInput.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/KeyField.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/ModuleFieldRow.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/ResolvedValue.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/ScopeTabs.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/StoreCards.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/StoreTable.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/ValueInput.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/last-test.ts +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/validation-errors.test.ts +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/components/validation-errors.ts +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/routes.ts +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/pages/types.ts +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/py.typed +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/settings.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/tenant_scope.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_concurrent_overrides.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_module_api.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_module_settings_render.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_module_settings_serialize.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_accessor.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_api.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_default_clears_override.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_events.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_field_sources.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_module.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_settings_view_authz.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_store.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests/test_tenant_settings.py +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests-js/ModulesEdit.test.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tests-js/ScopeTabs.test.tsx +0 -0
- {simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/tsconfig.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simple_module_settings
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.39
|
|
4
4
|
Summary: Runtime settings UI — modules plug their own settings panels into a shared admin view
|
|
5
5
|
Project-URL: Homepage, https://github.com/antosubash/simple_module_python
|
|
6
6
|
Project-URL: Repository, https://github.com/antosubash/simple_module_python
|
|
@@ -21,10 +21,10 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.12
|
|
24
|
-
Requires-Dist: simple-module-core==0.0.
|
|
25
|
-
Requires-Dist: simple-module-db==0.0.
|
|
26
|
-
Requires-Dist: simple-module-hosting==0.0.
|
|
27
|
-
Requires-Dist: typer
|
|
24
|
+
Requires-Dist: simple-module-core==0.0.39
|
|
25
|
+
Requires-Dist: simple-module-db==0.0.39
|
|
26
|
+
Requires-Dist: simple-module-hosting==0.0.39
|
|
27
|
+
Requires-Dist: typer<0.28,>=0.12
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
|
|
30
30
|
# simple_module_settings
|
|
@@ -55,7 +55,7 @@ class OrdersModule(ModuleBase):
|
|
|
55
55
|
meta = ModuleMeta(name="orders")
|
|
56
56
|
|
|
57
57
|
def register_settings(self, app: FastAPI) -> None:
|
|
58
|
-
from
|
|
58
|
+
from simple_module_core.module_settings import register_module_settings
|
|
59
59
|
from orders.settings import OrdersSettings
|
|
60
60
|
from orders.services import OrdersServices
|
|
61
61
|
|
|
@@ -64,7 +64,7 @@ class OrdersModule(ModuleBase):
|
|
|
64
64
|
)
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
That surfaces the module's settings fields under `/settings
|
|
67
|
+
That surfaces the module's settings fields under `/admin/settings/`, hydrated from the DB store at startup. The settings class is normally a `simple_module_core.settings_base.DbBackedSettings` (DB is the only source). Registration and hydration live in the framework (#500); this module adds the admin editor, hot reload and the key/value API. `settings.registration`, `settings.module_registry` and `settings.hydrate` are deprecated re-exports.
|
|
68
68
|
|
|
69
69
|
## Depends on
|
|
70
70
|
|
|
@@ -26,7 +26,7 @@ class OrdersModule(ModuleBase):
|
|
|
26
26
|
meta = ModuleMeta(name="orders")
|
|
27
27
|
|
|
28
28
|
def register_settings(self, app: FastAPI) -> None:
|
|
29
|
-
from
|
|
29
|
+
from simple_module_core.module_settings import register_module_settings
|
|
30
30
|
from orders.settings import OrdersSettings
|
|
31
31
|
from orders.services import OrdersServices
|
|
32
32
|
|
|
@@ -35,7 +35,7 @@ class OrdersModule(ModuleBase):
|
|
|
35
35
|
)
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
That surfaces the module's settings fields under `/settings
|
|
38
|
+
That surfaces the module's settings fields under `/admin/settings/`, hydrated from the DB store at startup. The settings class is normally a `simple_module_core.settings_base.DbBackedSettings` (DB is the only source). Registration and hydration live in the framework (#500); this module adds the admin editor, hot reload and the key/value API. `settings.registration`, `settings.module_registry` and `settings.hydrate` are deprecated re-exports.
|
|
39
39
|
|
|
40
40
|
## Depends on
|
|
41
41
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "simple_module_settings"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.39"
|
|
4
4
|
description = "Runtime settings UI — modules plug their own settings panels into a shared admin view"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -21,10 +21,10 @@ classifiers = [
|
|
|
21
21
|
"Typing :: Typed",
|
|
22
22
|
]
|
|
23
23
|
dependencies = [
|
|
24
|
-
"simple_module_core==0.0.
|
|
25
|
-
"simple_module_db==0.0.
|
|
26
|
-
"simple_module_hosting==0.0.
|
|
27
|
-
"typer>=0.12",
|
|
24
|
+
"simple_module_core==0.0.39",
|
|
25
|
+
"simple_module_db==0.0.39",
|
|
26
|
+
"simple_module_hosting==0.0.39",
|
|
27
|
+
"typer>=0.12,<0.28",
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
[project.entry-points.simple_module]
|
|
@@ -9,8 +9,9 @@ from __future__ import annotations
|
|
|
9
9
|
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
11
|
from simple_module_db.callbacks import register_on_commit
|
|
12
|
+
from simple_module_db.stored_settings import SCOPE_SYSTEM, SCOPE_TENANT
|
|
12
13
|
|
|
13
|
-
from settings.constants import INVALIDATION_CHANNEL
|
|
14
|
+
from settings.constants import INVALIDATION_CHANNEL
|
|
14
15
|
from settings.contracts.invalidation import invalidation_key
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
@@ -8,7 +8,7 @@ this field?" questions live together and are testable without a FastAPI app.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import re
|
|
11
|
-
from typing import Any
|
|
11
|
+
from typing import Any, Literal, get_args, get_origin
|
|
12
12
|
|
|
13
13
|
from pydantic_settings import BaseSettings
|
|
14
14
|
|
|
@@ -71,6 +71,11 @@ def choices_for(info) -> list[str] | None:
|
|
|
71
71
|
if isinstance(declared, (list, tuple)) and declared:
|
|
72
72
|
return [str(choice) for choice in declared]
|
|
73
73
|
|
|
74
|
+
if get_origin(info.annotation) is Literal:
|
|
75
|
+
options = get_args(info.annotation)
|
|
76
|
+
if options and all(isinstance(o, str) for o in options):
|
|
77
|
+
return list(options)
|
|
78
|
+
|
|
74
79
|
for constraint in info.metadata:
|
|
75
80
|
pattern = getattr(constraint, "pattern", None)
|
|
76
81
|
if not isinstance(pattern, str):
|
|
@@ -3,18 +3,14 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from simple_module_db import LIKE_ESCAPE_CHAR, like_contains_pattern
|
|
6
|
+
from simple_module_db.stored_settings import SYSTEM_SCOPE_ID, Setting
|
|
6
7
|
from sqlalchemy import func, select
|
|
7
8
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
9
|
+
from sqlmodel import col
|
|
8
10
|
|
|
9
11
|
from settings._row_masking import out
|
|
10
|
-
from settings.constants import
|
|
11
|
-
ALL_SCOPES,
|
|
12
|
-
DEFAULT_PER_PAGE,
|
|
13
|
-
SCOPE_ALL,
|
|
14
|
-
SYSTEM_SCOPE_ID,
|
|
15
|
-
)
|
|
12
|
+
from settings.constants import ALL_SCOPES, DEFAULT_PER_PAGE, SCOPE_ALL
|
|
16
13
|
from settings.contracts.schemas import SettingOut, SettingScope
|
|
17
|
-
from settings.models import Setting
|
|
18
14
|
|
|
19
15
|
|
|
20
16
|
class SettingListing:
|
|
@@ -116,6 +112,6 @@ class SettingListing:
|
|
|
116
112
|
def _scope_stmt(scope: SettingScope, scope_id: str):
|
|
117
113
|
return (
|
|
118
114
|
select(Setting)
|
|
119
|
-
.where(Setting.scope == scope.value, Setting.scope_id == scope_id)
|
|
115
|
+
.where(col(Setting.scope) == scope.value, col(Setting.scope_id) == scope_id)
|
|
120
116
|
.order_by(Setting.key)
|
|
121
117
|
)
|
|
@@ -25,11 +25,12 @@ from collections.abc import Awaitable, Callable
|
|
|
25
25
|
from settings.constants import ERR_MANAGED_KEY_DELETE
|
|
26
26
|
from settings.contracts.registry import SettingsRegistry, clear_route
|
|
27
27
|
from settings.contracts.schemas import SettingScope
|
|
28
|
+
from settings.exceptions import SettingsError
|
|
28
29
|
|
|
29
30
|
TenantIsLive = Callable[[str], Awaitable[bool]]
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
class ManagedKeyError(
|
|
33
|
+
class ManagedKeyError(SettingsError):
|
|
33
34
|
"""The key is cleared through ``clear_via``, not by deleting its row."""
|
|
34
35
|
|
|
35
36
|
def __init__(self, key: str, clear_via: str) -> None:
|
{simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/_module_settings.py
RENAMED
|
@@ -12,11 +12,10 @@ from typing import Any
|
|
|
12
12
|
|
|
13
13
|
from fastapi import FastAPI
|
|
14
14
|
from pydantic_settings import BaseSettings
|
|
15
|
-
|
|
16
|
-
from settings._field_meta import choices_for, env_readable_var, resolve_default
|
|
15
|
+
from simple_module_core.module_settings import get_module_settings_registry
|
|
17
16
|
|
|
18
17
|
# Re-exported: callers reach for ``SECRET_MASK`` / ``is_secret_field`` here.
|
|
19
|
-
from
|
|
18
|
+
from simple_module_core.secret_fields import ( # noqa: F401
|
|
20
19
|
_NEVER_SECRET_TYPES,
|
|
21
20
|
SECRET_MASK,
|
|
22
21
|
annotation_holds_secret,
|
|
@@ -26,8 +25,14 @@ from settings._secrets import ( # noqa: F401
|
|
|
26
25
|
is_secret_field,
|
|
27
26
|
mask,
|
|
28
27
|
)
|
|
28
|
+
from simple_module_core.settings_hydrate import (
|
|
29
|
+
env_supplied_fields,
|
|
30
|
+
input_type_for_field,
|
|
31
|
+
value_type_for_field,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from settings._field_meta import choices_for, env_readable_var, resolve_default
|
|
29
35
|
from settings.env_vars import env_prefix_for
|
|
30
|
-
from settings.hydrate import env_supplied_fields, input_type_for_field, value_type_for_field
|
|
31
36
|
from settings.service import SettingService
|
|
32
37
|
|
|
33
38
|
|
|
@@ -49,7 +54,7 @@ class ModuleSettingField:
|
|
|
49
54
|
The bundled module settings classes declare no ``env_prefix`` — they are
|
|
50
55
|
constructed from pydantic defaults and hydrated from the DB — so their
|
|
51
56
|
``SM_*`` vars are never consulted. Reporting a leftover
|
|
52
|
-
``
|
|
57
|
+
``SM_MAIL_SMTP_HOST`` as the live source would invert the very question
|
|
53
58
|
this screen answers. Classes that *do* declare one (the host's ``Settings``
|
|
54
59
|
with ``SM_``, and anything from the module scaffold) report ``env`` for
|
|
55
60
|
real, because for them pydantic really does read it.
|
|
@@ -168,6 +173,7 @@ def _field_view(
|
|
|
168
173
|
or named_secret
|
|
169
174
|
or any(embeds_credential(v) for v in (raw_value, default, raw_env))
|
|
170
175
|
)
|
|
176
|
+
group = extra.get("group")
|
|
171
177
|
return ModuleSettingField(
|
|
172
178
|
name=name,
|
|
173
179
|
env_var=env_var,
|
|
@@ -177,7 +183,7 @@ def _field_view(
|
|
|
177
183
|
is_secret=secret,
|
|
178
184
|
type=value_type,
|
|
179
185
|
requires_restart=bool(extra.get("requires_restart", False)),
|
|
180
|
-
group=
|
|
186
|
+
group=group if isinstance(group, str) else None,
|
|
181
187
|
env_set=env_set,
|
|
182
188
|
db_override=name in overridden,
|
|
183
189
|
env_readable=live_env_var is not None,
|
|
@@ -198,7 +204,7 @@ def collect_module_settings(
|
|
|
198
204
|
"""Return a sorted, serializable view of every module's BaseSettings.
|
|
199
205
|
|
|
200
206
|
Folds in both ``app.state.sm.modules`` (plugin modules) and additional
|
|
201
|
-
packages registered
|
|
207
|
+
packages registered in the framework's module-settings registry (e.g.
|
|
202
208
|
``"host"``) that aren't backed by a ``ModuleBase`` instance.
|
|
203
209
|
|
|
204
210
|
``overrides`` maps package -> field names carrying a stored override. It
|
|
@@ -209,8 +215,7 @@ def collect_module_settings(
|
|
|
209
215
|
views: list[ModuleSettingsView] = []
|
|
210
216
|
seen: set[str] = set()
|
|
211
217
|
|
|
212
|
-
|
|
213
|
-
registry = getattr(settings_services, "module_registry", None)
|
|
218
|
+
registry = get_module_settings_registry(app)
|
|
214
219
|
|
|
215
220
|
def _manage_url(package: str) -> str | None:
|
|
216
221
|
return registry.manage_url(package) if registry is not None else None
|
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Split out of ``service`` so the "is this a secret, and is this write the mask
|
|
4
4
|
being echoed back?" question lives in one file, next to the field-level rules in
|
|
5
|
-
``
|
|
5
|
+
``simple_module_core.secret_fields`` that it delegates to. ``service`` imports these; nothing else
|
|
6
6
|
should need to, because the point of funnelling every read through ``out`` is
|
|
7
7
|
that there is no second way to serialize a row.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
-
from
|
|
12
|
+
from simple_module_core.secret_fields import conceals_secret
|
|
13
|
+
from simple_module_db.stored_settings import Setting
|
|
14
|
+
|
|
13
15
|
from settings.constants import SENSITIVE_KEYS, SENSITIVE_PLACEHOLDER
|
|
14
16
|
from settings.contracts.schemas import SettingOut
|
|
15
|
-
from settings.models import Setting
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def is_masked(entity: Setting) -> bool:
|
|
@@ -21,7 +22,7 @@ def is_masked(entity: Setting) -> bool:
|
|
|
21
22
|
:data:`SENSITIVE_KEYS` names the one key the hosting layer owns
|
|
22
23
|
(``host.secret_key``). Everything else is judged by the same name/value
|
|
23
24
|
rule the module editor uses, because the store is the *same data* seen
|
|
24
|
-
through a different screen: an override named ``
|
|
25
|
+
through a different screen: an override named ``mail.smtp_password``, or
|
|
25
26
|
any override holding a ``postgresql://user:pw@host/db``, was rendered in
|
|
26
27
|
clear text in the browse table and pre-filled into the edit form purely
|
|
27
28
|
because the allowlist had a single entry in it.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Re-export of :mod:`simple_module_core.secret_fields` (moved there in #500)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from simple_module_core.secret_fields import ( # noqa: F401
|
|
6
|
+
_NEVER_SECRET_TYPES,
|
|
7
|
+
_SECRET_PATTERNS,
|
|
8
|
+
_TYPED_SECRET_KEYS,
|
|
9
|
+
SECRET_MASK,
|
|
10
|
+
annotation_holds_secret,
|
|
11
|
+
conceals_secret,
|
|
12
|
+
embeds_credential,
|
|
13
|
+
is_named_secret,
|
|
14
|
+
is_secret_field,
|
|
15
|
+
mask,
|
|
16
|
+
register_typed_secrets,
|
|
17
|
+
)
|
|
@@ -8,13 +8,14 @@ savepoint, leaving the request's transaction (and its session) usable.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
from simple_module_db.stored_settings import Setting
|
|
11
12
|
from sqlalchemy.exc import IntegrityError
|
|
12
13
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
13
14
|
|
|
14
|
-
from settings.
|
|
15
|
+
from settings.exceptions import SettingsError
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
class DuplicateSettingError(
|
|
18
|
+
class DuplicateSettingError(SettingsError):
|
|
18
19
|
"""A row with this (scope, scope_id, key) already exists."""
|
|
19
20
|
|
|
20
21
|
|
|
@@ -14,10 +14,10 @@ from collections.abc import Mapping
|
|
|
14
14
|
import typer
|
|
15
15
|
from fastapi import FastAPI
|
|
16
16
|
from simple_module_core.dotenv import find_env_file, parse_dotenv
|
|
17
|
+
from simple_module_core.module_settings import module_settings_registry
|
|
18
|
+
from simple_module_core.settings_hydrate import value_type_for_field
|
|
17
19
|
|
|
18
|
-
from settings.constants import MODULE_PACKAGE
|
|
19
20
|
from settings.env_vars import env_prefix_for
|
|
20
|
-
from settings.hydrate import value_type_for_field
|
|
21
21
|
from settings.store import SettingsStore
|
|
22
22
|
|
|
23
23
|
app = typer.Typer(help="Settings module administration.", no_args_is_help=True)
|
|
@@ -35,7 +35,7 @@ async def import_from_env_impl(
|
|
|
35
35
|
"""
|
|
36
36
|
if environ is None:
|
|
37
37
|
environ = {**parse_dotenv(find_env_file()), **os.environ}
|
|
38
|
-
registry =
|
|
38
|
+
registry = module_settings_registry(app_inst)
|
|
39
39
|
count = 0
|
|
40
40
|
for package, cls in registry.items():
|
|
41
41
|
prefix = env_prefix_for(package)
|
|
@@ -71,7 +71,7 @@ def import_from_env() -> None:
|
|
|
71
71
|
if n == 0 and find_env_file().is_file():
|
|
72
72
|
typer.echo(
|
|
73
73
|
"Hint: variables must be named SM_<MODULE>_<FIELD> "
|
|
74
|
-
"(e.g.
|
|
74
|
+
"(e.g. SM_MAIL_SMTP_PORT); none in the environment or .env matched."
|
|
75
75
|
)
|
|
76
76
|
return 0
|
|
77
77
|
|
|
@@ -10,6 +10,28 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
from typing import Final
|
|
12
12
|
|
|
13
|
+
# Re-exported (#500): these now live with the table in the framework.
|
|
14
|
+
from simple_module_db.stored_settings import ( # noqa: F401
|
|
15
|
+
ALL_VALUE_TYPES,
|
|
16
|
+
DESCRIPTION_MAX_LENGTH,
|
|
17
|
+
KEY_MAX_LENGTH,
|
|
18
|
+
SCOPE_ID_MAX_LENGTH,
|
|
19
|
+
SCOPE_MAX_LENGTH,
|
|
20
|
+
SCOPE_SYSTEM,
|
|
21
|
+
SCOPE_TENANT,
|
|
22
|
+
SCOPE_USER,
|
|
23
|
+
SYSTEM_SCOPE_ID,
|
|
24
|
+
TABLE_SETTING,
|
|
25
|
+
UQ_SCOPE_KEY,
|
|
26
|
+
VALUE_MAX_LENGTH,
|
|
27
|
+
VALUE_TYPE_BOOL,
|
|
28
|
+
VALUE_TYPE_FLOAT,
|
|
29
|
+
VALUE_TYPE_INT,
|
|
30
|
+
VALUE_TYPE_JSON,
|
|
31
|
+
VALUE_TYPE_MAX_LENGTH,
|
|
32
|
+
VALUE_TYPE_STRING,
|
|
33
|
+
)
|
|
34
|
+
|
|
13
35
|
# ── Module identity ──────────────────────────────────────────────────
|
|
14
36
|
MODULE_NAME: Final = "Settings"
|
|
15
37
|
MODULE_PACKAGE: Final = "settings"
|
|
@@ -17,30 +39,10 @@ ENV_PREFIX: Final = "SM_SETTINGS_"
|
|
|
17
39
|
LOCALE_NAMESPACE: Final = MODULE_PACKAGE
|
|
18
40
|
|
|
19
41
|
# ── Scopes ───────────────────────────────────────────────────────────
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
SCOPE_USER: Final = "user"
|
|
42
|
+
# Scope, value-type, table and field-limit constants moved to
|
|
43
|
+
# ``simple_module_db.stored_settings`` with the table itself (#500); they are
|
|
44
|
+
# re-exported from the import block at the top of this file.
|
|
24
45
|
ALL_SCOPES: Final = (SCOPE_SYSTEM, SCOPE_TENANT, SCOPE_USER)
|
|
25
|
-
# scope_id is empty for SYSTEM; empty string (not NULL) so composite unique
|
|
26
|
-
# works uniformly on SQLite and PostgreSQL (NULL breaks uniqueness on PG).
|
|
27
|
-
SYSTEM_SCOPE_ID: Final = ""
|
|
28
|
-
|
|
29
|
-
# ── Value types ──────────────────────────────────────────────────────
|
|
30
|
-
# Values are always stored as strings; ``value_type`` tells consumers how
|
|
31
|
-
# to interpret the bytes and lets the UI pick the right input control.
|
|
32
|
-
VALUE_TYPE_STRING: Final = "string"
|
|
33
|
-
VALUE_TYPE_BOOL: Final = "bool"
|
|
34
|
-
VALUE_TYPE_INT: Final = "int"
|
|
35
|
-
VALUE_TYPE_FLOAT: Final = "float"
|
|
36
|
-
VALUE_TYPE_JSON: Final = "json"
|
|
37
|
-
ALL_VALUE_TYPES: Final = (
|
|
38
|
-
VALUE_TYPE_STRING,
|
|
39
|
-
VALUE_TYPE_BOOL,
|
|
40
|
-
VALUE_TYPE_INT,
|
|
41
|
-
VALUE_TYPE_FLOAT,
|
|
42
|
-
VALUE_TYPE_JSON,
|
|
43
|
-
)
|
|
44
46
|
|
|
45
47
|
# ── Secrets ──────────────────────────────────────────────────────────
|
|
46
48
|
# Keys whose stored value must never leave the settings API or the raw
|
|
@@ -104,16 +106,6 @@ INVALIDATION_CHANNEL: Final = "settings.values"
|
|
|
104
106
|
|
|
105
107
|
# ── Database ─────────────────────────────────────────────────────────
|
|
106
108
|
DB_SCHEMA: Final = MODULE_PACKAGE
|
|
107
|
-
TABLE_SETTING: Final = "settings_setting"
|
|
108
|
-
UQ_SCOPE_KEY: Final = "uq_settings_setting_scope_scope_id_key"
|
|
109
|
-
|
|
110
|
-
# ── Field limits ─────────────────────────────────────────────────────
|
|
111
|
-
KEY_MAX_LENGTH: Final = 200
|
|
112
|
-
VALUE_MAX_LENGTH: Final = 4000
|
|
113
|
-
DESCRIPTION_MAX_LENGTH: Final = 2000
|
|
114
|
-
SCOPE_MAX_LENGTH: Final = 10
|
|
115
|
-
SCOPE_ID_MAX_LENGTH: Final = 255
|
|
116
|
-
VALUE_TYPE_MAX_LENGTH: Final = 10
|
|
117
109
|
|
|
118
110
|
# ── Inertia page component names ─────────────────────────────────────
|
|
119
111
|
PAGE_BROWSE: Final = f"{MODULE_NAME}/Browse"
|
{simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/contracts/accessor.py
RENAMED
|
@@ -23,7 +23,8 @@ import json
|
|
|
23
23
|
from collections.abc import Callable
|
|
24
24
|
from typing import TYPE_CHECKING, Any, Final
|
|
25
25
|
|
|
26
|
-
from
|
|
26
|
+
from simple_module_db.stored_settings import SYSTEM_SCOPE_ID
|
|
27
|
+
|
|
27
28
|
from settings.contracts.registry import SettingsRegistry
|
|
28
29
|
from settings.contracts.schemas import (
|
|
29
30
|
BOOL_LITERALS_FALSE,
|
{simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/contracts/registry.py
RENAMED
|
@@ -58,6 +58,8 @@ class SettingDefinition:
|
|
|
58
58
|
of offering a text box for an opaque id. ``description_key`` is the i18n
|
|
59
59
|
key the tenant settings page translates ``description`` with (the English
|
|
60
60
|
``description`` stays the fallback when the key is missing or unset).
|
|
61
|
+
``label_key`` does the same for the row's title (the raw ``key`` is the
|
|
62
|
+
fallback).
|
|
61
63
|
``clear_via`` names the route that owns clearing the key (e.g. an upload
|
|
62
64
|
route that also reaps the stored file): the generic DELETE routes answer
|
|
63
65
|
422 pointing there while the tenant exists. A platform operator may still
|
|
@@ -76,6 +78,7 @@ class SettingDefinition:
|
|
|
76
78
|
clear_via: str | Mapping[SettingScope, str] = ""
|
|
77
79
|
upload_url: str = ""
|
|
78
80
|
description_key: str = ""
|
|
81
|
+
label_key: str = ""
|
|
79
82
|
|
|
80
83
|
|
|
81
84
|
def clear_route(definition: SettingDefinition, scope: SettingScope | str) -> str:
|
{simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/contracts/schemas.py
RENAMED
|
@@ -7,13 +7,8 @@ from datetime import datetime
|
|
|
7
7
|
from enum import StrEnum
|
|
8
8
|
|
|
9
9
|
from pydantic import ConfigDict, model_validator
|
|
10
|
-
from
|
|
11
|
-
|
|
12
|
-
from settings.constants import (
|
|
10
|
+
from simple_module_db.stored_settings import (
|
|
13
11
|
DESCRIPTION_MAX_LENGTH,
|
|
14
|
-
ERR_SCOPED_REQUIRES_ID,
|
|
15
|
-
ERR_SYSTEM_SCOPE_NO_ID,
|
|
16
|
-
ERR_VALUE_MISMATCH,
|
|
17
12
|
KEY_MAX_LENGTH,
|
|
18
13
|
SCOPE_ID_MAX_LENGTH,
|
|
19
14
|
SCOPE_SYSTEM,
|
|
@@ -27,6 +22,9 @@ from settings.constants import (
|
|
|
27
22
|
VALUE_TYPE_JSON,
|
|
28
23
|
VALUE_TYPE_STRING,
|
|
29
24
|
)
|
|
25
|
+
from sqlmodel import Field, SQLModel
|
|
26
|
+
|
|
27
|
+
from settings.constants import ERR_SCOPED_REQUIRES_ID, ERR_SYSTEM_SCOPE_NO_ID, ERR_VALUE_MISMATCH
|
|
30
28
|
|
|
31
29
|
_BOOL_LITERALS = frozenset(
|
|
32
30
|
{"true", "false", "1", "0", "t", "f", "yes", "no", "y", "n", "on", "off"}
|
|
@@ -92,7 +90,7 @@ def _validate_value_matches_type(value: str, value_type: SettingValueType) -> No
|
|
|
92
90
|
class SettingOut(SQLModel):
|
|
93
91
|
"""A setting returned by the API."""
|
|
94
92
|
|
|
95
|
-
model_config = ConfigDict(from_attributes=True)
|
|
93
|
+
model_config = ConfigDict(from_attributes=True) # ty: ignore[invalid-assignment]
|
|
96
94
|
|
|
97
95
|
id: int
|
|
98
96
|
scope: SettingScope
|
|
@@ -11,6 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
from typing import Annotated
|
|
12
12
|
|
|
13
13
|
from fastapi import Depends, Request
|
|
14
|
+
from simple_module_core.app_state import get_services
|
|
14
15
|
from simple_module_db.deps import get_db
|
|
15
16
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
16
17
|
|
|
@@ -25,7 +26,7 @@ async def get_setting_service(
|
|
|
25
26
|
request: Request,
|
|
26
27
|
db: AsyncSession = Depends(get_db),
|
|
27
28
|
) -> SettingService:
|
|
28
|
-
bus = getattr(
|
|
29
|
+
bus = getattr(get_services(request.app), "invalidation", None)
|
|
29
30
|
|
|
30
31
|
async def tenant_is_live(tenant_id: str) -> bool:
|
|
31
32
|
return await is_known_tenant(request, tenant_id)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
|
6
|
+
from simple_module_db.stored_settings import SYSTEM_SCOPE_ID
|
|
6
7
|
from simple_module_hosting.permissions import RequiresPermission
|
|
7
8
|
|
|
8
9
|
from settings._unique_write import DuplicateSettingError
|
|
@@ -28,7 +29,6 @@ from settings.constants import (
|
|
|
28
29
|
STATUS_NO_CONTENT,
|
|
29
30
|
STATUS_NOT_FOUND,
|
|
30
31
|
STATUS_UNPROCESSABLE,
|
|
31
|
-
SYSTEM_SCOPE_ID,
|
|
32
32
|
)
|
|
33
33
|
from settings.contracts.schemas import (
|
|
34
34
|
SettingCreate,
|
{simple_module_settings-0.0.37 → simple_module_settings-0.0.39}/settings/endpoints/module_api.py
RENAMED
|
@@ -11,6 +11,8 @@ from typing import Any
|
|
|
11
11
|
|
|
12
12
|
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, Response, status
|
|
13
13
|
from pydantic import ValidationError
|
|
14
|
+
from simple_module_core.module_settings import module_settings_registry
|
|
15
|
+
from simple_module_core.settings_hydrate import hydrate_settings
|
|
14
16
|
from simple_module_hosting.i18n_deps import TranslatorDep
|
|
15
17
|
from simple_module_hosting.permissions import RequiresPermission
|
|
16
18
|
|
|
@@ -20,10 +22,9 @@ from settings._module_settings import (
|
|
|
20
22
|
overrides_by_package,
|
|
21
23
|
)
|
|
22
24
|
from settings._module_settings_props import serialize
|
|
23
|
-
from settings.constants import
|
|
25
|
+
from settings.constants import PERM_DELETE, PERM_EDIT, PERM_VIEW
|
|
24
26
|
from settings.contracts.events import SettingsReloaded
|
|
25
27
|
from settings.deps import get_setting_service
|
|
26
|
-
from settings.hydrate import hydrate_settings
|
|
27
28
|
from settings.reload import EnvPinnedError, apply_changes_and_reload
|
|
28
29
|
from settings.service import SettingService
|
|
29
30
|
from settings.store import SettingsStore
|
|
@@ -94,7 +95,7 @@ async def update_module(
|
|
|
94
95
|
t: TranslatorDep,
|
|
95
96
|
service: SettingService = Depends(get_setting_service),
|
|
96
97
|
) -> dict[str, Any]:
|
|
97
|
-
registry =
|
|
98
|
+
registry = module_settings_registry(request.app)
|
|
98
99
|
if registry.get(package) is None:
|
|
99
100
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Unknown module package")
|
|
100
101
|
if registry.manage_url(package) is not None:
|
|
@@ -146,7 +147,7 @@ async def clear_module_field(
|
|
|
146
147
|
request: Request,
|
|
147
148
|
service: SettingService = Depends(get_setting_service),
|
|
148
149
|
) -> Response:
|
|
149
|
-
registry =
|
|
150
|
+
registry = module_settings_registry(request.app)
|
|
150
151
|
cls = registry.get(package)
|
|
151
152
|
if cls is None:
|
|
152
153
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Unknown module package")
|
|
@@ -10,10 +10,10 @@ strings below match what ``pages/*.tsx`` declare.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import asyncio
|
|
13
|
-
import math
|
|
14
13
|
|
|
15
14
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
16
15
|
from pydantic import ValidationError
|
|
16
|
+
from simple_module_db.pagination import clamp_page
|
|
17
17
|
from simple_module_hosting.i18n_deps import TranslatorDep
|
|
18
18
|
from simple_module_hosting.inertia_deps import InertiaDep
|
|
19
19
|
from simple_module_hosting.inertia_utils import redirect_back_with_errors, validation_errors_to_dict
|
|
@@ -107,7 +107,7 @@ async def browse(
|
|
|
107
107
|
# A page past the end (stale ?page= link, or the last row on it was just
|
|
108
108
|
# deleted) is clamped and re-queried — otherwise the client gets an empty
|
|
109
109
|
# list with a nonzero total and renders a blank table.
|
|
110
|
-
number =
|
|
110
|
+
number = clamp_page(query.page, total, query.per_page)
|
|
111
111
|
if number != query.page:
|
|
112
112
|
items, total = await service.list_filtered(
|
|
113
113
|
query.scope_filter, query.q, number, query.per_page
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Deprecated: hydration moved to the framework (#500).
|
|
2
|
+
|
|
3
|
+
``simple_module_core.settings_hydrate`` holds the hydrator and field helpers;
|
|
4
|
+
the sync worker twin is ``simple_module_db.stored_settings.hydrate_settings_sync``.
|
|
5
|
+
Kept as a re-export until 0.1.0; ``simple_module_core.deprecation`` enforces the date.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from simple_module_core.deprecation import deprecated_module
|
|
11
|
+
from simple_module_core.settings_hydrate import (
|
|
12
|
+
RELATED_FIELDS_CTX,
|
|
13
|
+
build_settings,
|
|
14
|
+
env_supplied_fields,
|
|
15
|
+
hydrate_settings,
|
|
16
|
+
input_type_for_field,
|
|
17
|
+
value_type_for_field,
|
|
18
|
+
)
|
|
19
|
+
from simple_module_db.stored_settings import hydrate_settings_sync
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"RELATED_FIELDS_CTX",
|
|
23
|
+
"build_settings",
|
|
24
|
+
"env_supplied_fields",
|
|
25
|
+
"hydrate_settings",
|
|
26
|
+
"hydrate_settings_sync",
|
|
27
|
+
"input_type_for_field",
|
|
28
|
+
"value_type_for_field",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
deprecated_module(
|
|
32
|
+
"settings.hydrate",
|
|
33
|
+
remove_in="0.1.0",
|
|
34
|
+
use="simple_module_core.settings_hydrate (hydrate_settings_sync: "
|
|
35
|
+
"simple_module_db.stored_settings)",
|
|
36
|
+
)
|
|
@@ -17,9 +17,9 @@ from typing import Any
|
|
|
17
17
|
|
|
18
18
|
from fastapi import FastAPI
|
|
19
19
|
from fastapi.encoders import jsonable_encoder
|
|
20
|
+
from simple_module_core.secret_fields import conceals_secret, mask
|
|
20
21
|
|
|
21
22
|
from settings._module_settings import collect_module_settings
|
|
22
|
-
from settings._secrets import conceals_secret, mask
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def _from_field(package: str, field) -> dict[str, Any]:
|