simple-module-core 0.0.38__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_core-0.0.38 → simple_module_core-0.0.39}/PKG-INFO +6 -6
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/pyproject.toml +6 -6
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/_i18n_translator.py +5 -0
- simple_module_core-0.0.39/simple_module_core/app_state.py +199 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/events.py +20 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/invalidation.py +7 -3
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/leases.py +6 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/mail.py +6 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/menu.py +6 -1
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/module.py +6 -1
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/module_meta.py +3 -1
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/tenancy.py +4 -2
- simple_module_core-0.0.39/simple_module_core/tenant_events.py +20 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/versioning.py +11 -0
- simple_module_core-0.0.39/tests/test_core_app_state.py +57 -0
- simple_module_core-0.0.39/tests/test_core_lifecycle_docs.py +20 -0
- simple_module_core-0.0.39/tests/test_events_inline.py +38 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/.gitignore +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/LICENSE +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/README.md +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/__init__.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/__main__.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/_i18n_flatten.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/_permission_grants.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/_request_hooks.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/activity_feed.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/audit_links.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/body_limits.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/correlation.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/cors.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/csp.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/deprecation.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/design_packs.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/__init__.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_coupling.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_css.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_db_extensions.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_dependencies.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_expression_index.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_heavy_task_imports.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_i18n.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_inertia_api.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_js_workspace.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_migration.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_module.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_pages.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_runner.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_spa_mount.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_tenancy.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_types.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/discovery.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/dotenv.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/durable_events.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/environments.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/exceptions.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/feature_flags.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/health.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/i18n.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/i18n_overrides.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/module_settings.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/path_rules.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/permission_denied.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/permissions.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/public_routes.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/py.typed +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/rate_limit.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/redirect_safety.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/resources.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/secret_fields.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/services.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/settings_base.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/settings_hydrate.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/setup_steps.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/spa_mount.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/stored_settings.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/user_directory.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_audit_links.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_auth_provider_selection.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_core_leases.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_core_mail_resolution.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_core_module_settings.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_core_user_directory.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_csp_registry.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_css_diagnostics.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_css_scanner.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_db_extensions_diagnostics.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_deprecation_helper.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_design_packs.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_diagnostics.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_diagnostics_tenancy.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_discovery.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_discovery_missing_deps.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_doctor_cli_overrides.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_dotenv.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_durable_events.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_environments.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_events.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_expression_index_diagnostics.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_feature_flags.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_feature_flags_decorator.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_health_registry.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_heavy_task_imports_diagnostics.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_i18n.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_i18n_diagnostics.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_i18n_overrides.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_invalidation.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_invalidation_handlers.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_menu.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_module_base.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_module_diagnostics.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_path_rule_registries.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_permission_sources.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_permissions.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_permissions_admin_principal.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_public_routes.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_public_routes_rate_precedence.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_redirect_safety.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_services.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_settings_base.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_sm003_page_render.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_sm009_dynamic_coupling.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_tenant_exists.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_tenant_roles.py +0 -0
- {simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_versioning.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simple_module_core
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.39
|
|
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
|
|
@@ -21,12 +21,12 @@ 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: babel
|
|
24
|
+
Requires-Dist: babel<3,>=2.14
|
|
25
25
|
Requires-Dist: fastapi<0.157,>=0.140
|
|
26
|
-
Requires-Dist: packaging
|
|
27
|
-
Requires-Dist: pydantic-settings
|
|
28
|
-
Requires-Dist: pydantic
|
|
29
|
-
Requires-Dist: pyee
|
|
26
|
+
Requires-Dist: packaging<27,>=23.0
|
|
27
|
+
Requires-Dist: pydantic-settings<3,>=2.0
|
|
28
|
+
Requires-Dist: pydantic<3,>=2.0
|
|
29
|
+
Requires-Dist: pyee<15,>=12.0
|
|
30
30
|
Description-Content-Type: text/markdown
|
|
31
31
|
|
|
32
32
|
# simple_module_core
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "simple_module_core"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.39"
|
|
4
4
|
description = "Module-system primitives for the simple_module framework — ModuleBase, discovery, diagnostics, events"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -21,12 +21,12 @@ classifiers = [
|
|
|
21
21
|
"Typing :: Typed",
|
|
22
22
|
]
|
|
23
23
|
dependencies = [
|
|
24
|
-
"babel>=2.14",
|
|
24
|
+
"babel>=2.14,<3",
|
|
25
25
|
"fastapi>=0.140,<0.157",
|
|
26
|
-
"packaging>=23.0",
|
|
27
|
-
"pydantic>=2.0",
|
|
28
|
-
"pydantic-settings>=2.0",
|
|
29
|
-
"pyee>=12.0",
|
|
26
|
+
"packaging>=23.0,<27",
|
|
27
|
+
"pydantic>=2.0,<3",
|
|
28
|
+
"pydantic-settings>=2.0,<3",
|
|
29
|
+
"pyee>=12.0,<15",
|
|
30
30
|
]
|
|
31
31
|
|
|
32
32
|
[project.urls]
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/_i18n_translator.py
RENAMED
|
@@ -14,6 +14,7 @@ if TYPE_CHECKING:
|
|
|
14
14
|
# Logged under the historical ``simple_module_core.i18n`` name so existing log
|
|
15
15
|
# filters keep matching after the split.
|
|
16
16
|
logger = logging.getLogger("simple_module_core.i18n")
|
|
17
|
+
_plural_failure_warned = False
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
@lru_cache(maxsize=64)
|
|
@@ -30,6 +31,10 @@ def _plural_form(locale: str, count: float) -> str:
|
|
|
30
31
|
try:
|
|
31
32
|
rule = _plural_rule(locale)
|
|
32
33
|
except Exception:
|
|
34
|
+
global _plural_failure_warned
|
|
35
|
+
if not _plural_failure_warned:
|
|
36
|
+
_plural_failure_warned = True
|
|
37
|
+
logger.warning("Plural locale parsing failed; using other", exc_info=True)
|
|
33
38
|
return "other"
|
|
34
39
|
return rule(count)
|
|
35
40
|
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Typed reads of optional app services, preserving absent-state fallbacks.
|
|
2
|
+
|
|
3
|
+
Concrete hosting types are imported only for type checking, as in Services:
|
|
4
|
+
core stays usable without hosting or any plugin installed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Protocol
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from asyncio import Lock
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
|
|
15
|
+
from simple_module_hosting._host_services import _HostServices
|
|
16
|
+
from simple_module_hosting._tenant import TenantResolver
|
|
17
|
+
from simple_module_hosting.middleware import PrincipalSerializer
|
|
18
|
+
from simple_module_hosting.shared_props import SharedPropsProvider
|
|
19
|
+
from simple_module_inertia import Inertia
|
|
20
|
+
from starlette.requests import Request
|
|
21
|
+
|
|
22
|
+
from simple_module_core.body_limits import BodyLimitRegistry
|
|
23
|
+
from simple_module_core.design_packs import DesignPackRegistry
|
|
24
|
+
from simple_module_core.module import ModuleBase
|
|
25
|
+
from simple_module_core.public_routes import PublicRouteRegistry
|
|
26
|
+
from simple_module_core.rate_limit import RateLimitRegistry
|
|
27
|
+
from simple_module_core.services import Services
|
|
28
|
+
from simple_module_core.tenancy import TenantExists, TenantMembers
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
#: Attribute ``register_inertia_shared_provider`` writes and the renderer reads.
|
|
32
|
+
INERTIA_SHARED_PROVIDERS_STATE_ATTR = "inertia_shared_providers"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AuthProvider(Protocol):
|
|
36
|
+
"""Framework-facing auth extension; installed by users or keycloak."""
|
|
37
|
+
|
|
38
|
+
async def resolve_user(self, request: Request) -> Any: ...
|
|
39
|
+
|
|
40
|
+
def get_login_url(self, request: Request, next_url: str | None = None) -> str: ...
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BrandingState(Protocol):
|
|
44
|
+
"""Optional branding services consumed by the document renderer."""
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def settings(self) -> object: ...
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def favicon_url(self) -> str | None: ...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_auth_provider(app: Any) -> AuthProvider | None:
|
|
54
|
+
"""Auth.register_settings installs auth; users/keycloak install its provider."""
|
|
55
|
+
return getattr(getattr(app.state, "auth", None), "auth_provider", None)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_services(app: Any) -> Services | None:
|
|
59
|
+
"""create_app installs the framework service container.
|
|
60
|
+
|
|
61
|
+
Returns None when absent.
|
|
62
|
+
"""
|
|
63
|
+
return getattr(getattr(app, "state", None), "sm", None)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_host_services(app: Any) -> _HostServices | None:
|
|
67
|
+
"""Host settings registration installs the host service container.
|
|
68
|
+
|
|
69
|
+
Returns None when absent.
|
|
70
|
+
"""
|
|
71
|
+
return getattr(app.state, "host", None)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_public_routes(app: Any) -> PublicRouteRegistry | None:
|
|
75
|
+
"""create_app installs the public-route registry.
|
|
76
|
+
|
|
77
|
+
Returns None when absent.
|
|
78
|
+
"""
|
|
79
|
+
return getattr(app.state, "public_routes", None)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_body_limits(app: Any) -> BodyLimitRegistry | None:
|
|
83
|
+
"""create_app installs the request-body limit registry.
|
|
84
|
+
|
|
85
|
+
Returns None when absent.
|
|
86
|
+
"""
|
|
87
|
+
return getattr(app.state, "body_limits", None)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_rate_limit_rules(app: Any) -> RateLimitRegistry | None:
|
|
91
|
+
"""create_app installs the per-route rate-limit registry.
|
|
92
|
+
|
|
93
|
+
Returns None when absent.
|
|
94
|
+
"""
|
|
95
|
+
return getattr(app.state, "rate_limit_rules", None)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_design_packs(app: Any) -> DesignPackRegistry | None:
|
|
99
|
+
"""create_app installs the design-pack registry.
|
|
100
|
+
|
|
101
|
+
Returns None when absent.
|
|
102
|
+
"""
|
|
103
|
+
return getattr(app.state, "design_packs", None)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_migration_status(app: Any) -> dict[str, Any] | None:
|
|
107
|
+
"""The hosting lifespan and setup wizard install the migration snapshot.
|
|
108
|
+
|
|
109
|
+
Returns None when absent.
|
|
110
|
+
"""
|
|
111
|
+
return getattr(app.state, "migration", None)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_openapi_url(app: Any) -> str | None:
|
|
115
|
+
"""Hosting boot records the served OpenAPI URL, or None when disabled.
|
|
116
|
+
|
|
117
|
+
Returns None when absent.
|
|
118
|
+
"""
|
|
119
|
+
return getattr(app.state, "openapi_url", None)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_spa_prefixes(app: Any) -> tuple[str, ...]:
|
|
123
|
+
"""SPA mounting records the mounted prefixes; absent means no mounts.
|
|
124
|
+
|
|
125
|
+
Returns () when absent.
|
|
126
|
+
"""
|
|
127
|
+
return getattr(app.state, "spa_prefixes", ())
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_deferred_startup(app: Any) -> list[ModuleBase] | None:
|
|
131
|
+
"""The hosting lifespan records modules awaiting migrations.
|
|
132
|
+
|
|
133
|
+
Returns None when absent.
|
|
134
|
+
"""
|
|
135
|
+
return getattr(app.state, "deferred_startup", None)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def get_deferred_startup_lock(app: Any) -> Lock | None:
|
|
139
|
+
"""Deferred startup replay lazily installs its per-app lock.
|
|
140
|
+
|
|
141
|
+
Returns None when absent.
|
|
142
|
+
"""
|
|
143
|
+
return getattr(app.state, "deferred_startup_lock", None)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_principal_serializer(app: Any) -> PrincipalSerializer | None:
|
|
147
|
+
"""The auth module installs the principal-to-Inertia serializer.
|
|
148
|
+
|
|
149
|
+
Returns None when absent.
|
|
150
|
+
"""
|
|
151
|
+
return getattr(app.state, "principal_serializer", None)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def get_tenant_resolver(app: Any) -> TenantResolver | None:
|
|
155
|
+
"""The tenants module installs request tenant resolution.
|
|
156
|
+
|
|
157
|
+
Returns None when absent.
|
|
158
|
+
"""
|
|
159
|
+
return getattr(getattr(app, "state", None), "tenant_resolver", None)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def get_tenant_exists(app: Any) -> TenantExists | None:
|
|
163
|
+
"""The tenants module installs the tenant-existence callback.
|
|
164
|
+
|
|
165
|
+
Returns None when absent.
|
|
166
|
+
"""
|
|
167
|
+
return getattr(app.state, "tenant_exists", None)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def get_tenant_members(app: Any) -> TenantMembers | None:
|
|
171
|
+
"""The tenants module installs the membership lookup callback.
|
|
172
|
+
|
|
173
|
+
Returns None when absent.
|
|
174
|
+
"""
|
|
175
|
+
return getattr(app.state, "tenant_members", None)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def get_inertia_dependency(app: Any) -> Callable[..., Inertia] | None:
|
|
179
|
+
"""Hosting Inertia setup installs the configured dependency factory.
|
|
180
|
+
|
|
181
|
+
Returns None when absent.
|
|
182
|
+
"""
|
|
183
|
+
return getattr(app.state, "inertia_dependency", None)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def get_inertia_shared_providers(app: Any) -> list[SharedPropsProvider] | None:
|
|
187
|
+
"""register_inertia_shared_provider lazily installs the provider list.
|
|
188
|
+
|
|
189
|
+
Returns None when absent.
|
|
190
|
+
"""
|
|
191
|
+
return getattr(app.state, INERTIA_SHARED_PROVIDERS_STATE_ATTR, None)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def get_branding(app: Any) -> BrandingState | None:
|
|
195
|
+
"""The branding module installs the optional application theme services.
|
|
196
|
+
|
|
197
|
+
Returns None when absent.
|
|
198
|
+
"""
|
|
199
|
+
return getattr(app.state, "branding", None)
|
|
@@ -11,6 +11,7 @@ from typing import Any
|
|
|
11
11
|
from pyee.asyncio import AsyncIOEventEmitter
|
|
12
12
|
|
|
13
13
|
from simple_module_core.durable_events import (
|
|
14
|
+
DurableContext,
|
|
14
15
|
DurableDeliveryUnavailable,
|
|
15
16
|
DurableHandler,
|
|
16
17
|
DurableRegistry,
|
|
@@ -141,3 +142,22 @@ class EventBus:
|
|
|
141
142
|
if not names:
|
|
142
143
|
return
|
|
143
144
|
await transport.enqueue(event, names, db)
|
|
145
|
+
|
|
146
|
+
async def publish_durable_or_inline(self, event: Event, db: Any) -> None:
|
|
147
|
+
"""Enqueue with a transport, otherwise run durable handlers in *db*.
|
|
148
|
+
|
|
149
|
+
Inline handlers run sequentially in subscription order on the caller's
|
|
150
|
+
transaction with attempt=1. They must flush rather than commit; errors
|
|
151
|
+
propagate so the caller can roll back. Ordinary subscribers are not run.
|
|
152
|
+
External effects must be deferred to the caller's post-commit hooks.
|
|
153
|
+
"""
|
|
154
|
+
if self.durable_transport is not None:
|
|
155
|
+
await self.publish_durable(event, db)
|
|
156
|
+
return
|
|
157
|
+
ctx = DurableContext(db=db, attempt=1)
|
|
158
|
+
key = self._event_key(type(event))
|
|
159
|
+
for name in self.durable.handler_names(type(event)):
|
|
160
|
+
subscription = self.durable.lookup(key, name)
|
|
161
|
+
if subscription is None:
|
|
162
|
+
continue
|
|
163
|
+
await subscription.handler(event, ctx)
|
|
@@ -48,6 +48,7 @@ from dataclasses import dataclass
|
|
|
48
48
|
from typing import Protocol, runtime_checkable
|
|
49
49
|
|
|
50
50
|
logger = logging.getLogger(__name__)
|
|
51
|
+
_decode_failure_warned = False
|
|
51
52
|
|
|
52
53
|
__all__ = [
|
|
53
54
|
"WIRE_VERSION",
|
|
@@ -108,8 +109,8 @@ class Invalidation:
|
|
|
108
109
|
routinely shared — with another app, with a stray ``redis-cli publish``,
|
|
109
110
|
with the previous version of this code — and a listener loop that dies
|
|
110
111
|
on one malformed payload takes cross-process invalidation down for the
|
|
111
|
-
life of the process.
|
|
112
|
-
|
|
112
|
+
life of the process. Decode exceptions warn once per process with a
|
|
113
|
+
traceback; unsupported shapes remain ordinary shared-database traffic.
|
|
113
114
|
"""
|
|
114
115
|
try:
|
|
115
116
|
data = json.loads(message)
|
|
@@ -123,7 +124,10 @@ class Invalidation:
|
|
|
123
124
|
return None
|
|
124
125
|
return cls(channel=channel, key=key, origin=str(data.get("origin") or ""))
|
|
125
126
|
except Exception:
|
|
126
|
-
|
|
127
|
+
global _decode_failure_warned
|
|
128
|
+
if not _decode_failure_warned:
|
|
129
|
+
_decode_failure_warned = True
|
|
130
|
+
logger.warning("Ignoring undecodable invalidation message", exc_info=True)
|
|
127
131
|
return None
|
|
128
132
|
|
|
129
133
|
|
|
@@ -43,6 +43,7 @@ __all__ = [
|
|
|
43
43
|
"configure_leases",
|
|
44
44
|
"get_leases",
|
|
45
45
|
"lease",
|
|
46
|
+
"reset_lease_backend",
|
|
46
47
|
"semaphore",
|
|
47
48
|
]
|
|
48
49
|
|
|
@@ -153,6 +154,11 @@ def configure_leases(backend: LeaseBackend | None) -> None:
|
|
|
153
154
|
_backend = backend if backend is not None else InProcessLeaseBackend()
|
|
154
155
|
|
|
155
156
|
|
|
157
|
+
def reset_lease_backend() -> None:
|
|
158
|
+
"""Discard process lease configuration and held in-process leases."""
|
|
159
|
+
configure_leases(None)
|
|
160
|
+
|
|
161
|
+
|
|
156
162
|
def get_leases() -> LeaseBackend:
|
|
157
163
|
"""The process-wide backend :func:`lease` and :func:`semaphore` use."""
|
|
158
164
|
return _backend
|
|
@@ -93,6 +93,12 @@ def install_mail_resolver(resolver: MailSenderResolver | None) -> None:
|
|
|
93
93
|
_resolver = resolver
|
|
94
94
|
|
|
95
95
|
|
|
96
|
+
def reset_mail_sender() -> None:
|
|
97
|
+
"""Clear the process sender and resolver between isolated app/test runs."""
|
|
98
|
+
install_mail_sender(None)
|
|
99
|
+
install_mail_resolver(None)
|
|
100
|
+
|
|
101
|
+
|
|
96
102
|
def mail_transport_installed() -> bool:
|
|
97
103
|
"""Whether a transport (sender or resolver) is installed in this process.
|
|
98
104
|
|
|
@@ -85,6 +85,7 @@ class MenuRegistry:
|
|
|
85
85
|
self._items: list[MenuItem] = []
|
|
86
86
|
self._sorted: list[MenuItem] | None = None
|
|
87
87
|
self._providers: list[MenuProvider] = []
|
|
88
|
+
self._failed_providers: set[int] = set()
|
|
88
89
|
|
|
89
90
|
def _invalidate(self) -> None:
|
|
90
91
|
self._sorted = None
|
|
@@ -125,7 +126,11 @@ class MenuRegistry:
|
|
|
125
126
|
# Materialise first: a generator failing midway must add nothing.
|
|
126
127
|
items.extend(list(result))
|
|
127
128
|
except Exception:
|
|
128
|
-
|
|
129
|
+
if id(provider) not in self._failed_providers:
|
|
130
|
+
self._failed_providers.add(id(provider))
|
|
131
|
+
logger.warning(
|
|
132
|
+
"Menu provider %r failed; contributing nothing", provider, exc_info=True
|
|
133
|
+
)
|
|
129
134
|
return items
|
|
130
135
|
|
|
131
136
|
@property
|
|
@@ -25,6 +25,9 @@ if TYPE_CHECKING:
|
|
|
25
25
|
from simple_module_core.spa_mount import SpaMount
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
EVENT_HANDLERS_OPTIONAL_APP_REMOVE_IN = "0.1.0"
|
|
29
|
+
|
|
30
|
+
|
|
28
31
|
class ModuleBase(RequestHooksMixin, ABC):
|
|
29
32
|
"""Base class for all modules.
|
|
30
33
|
|
|
@@ -75,7 +78,9 @@ class ModuleBase(RequestHooksMixin, ABC):
|
|
|
75
78
|
def register_event_handlers(self, bus: EventBus, app: FastAPI | None = None) -> None:
|
|
76
79
|
"""Subscribe to events published by other modules.
|
|
77
80
|
|
|
78
|
-
``app``
|
|
81
|
+
The optional ``app=None`` compatibility default has
|
|
82
|
+
``remove_in="0.1.0"``. Overrides own the call, so the base class cannot
|
|
83
|
+
reliably warn when callers omit it. Pass ``app`` through to handlers
|
|
79
84
|
that need ``app.state.sm.db.session_factory`` to persist on the
|
|
80
85
|
framework's engine instead of building their own.
|
|
81
86
|
"""
|
|
@@ -50,7 +50,9 @@ class ModuleMeta:
|
|
|
50
50
|
|
|
51
51
|
Example: ``">=1.0,<2.0"``. When set, the module is rejected at boot if the
|
|
52
52
|
installed ``simple_module_core.FRAMEWORK_API_VERSION`` does not satisfy it.
|
|
53
|
-
When ``None``, no compatibility check is performed (legacy modules)
|
|
53
|
+
When ``None``, no compatibility check is performed (legacy modules);
|
|
54
|
+
this bypass is deprecated with ``remove_in="0.1.0"`` (tracked by the
|
|
55
|
+
deprecation-expiry test; it does not warn at boot).
|
|
54
56
|
"""
|
|
55
57
|
i18n_audience: str = "public"
|
|
56
58
|
"""Who this module's locale catalog is shipped to: ``"public"`` or ``"admin"``.
|
|
@@ -17,6 +17,8 @@ from collections.abc import Awaitable, Callable, Collection
|
|
|
17
17
|
from enum import StrEnum
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
|
+
from simple_module_core.app_state import get_tenant_exists, get_tenant_members
|
|
21
|
+
|
|
20
22
|
TENANT_ROLE_PREFIX = "tenant:"
|
|
21
23
|
|
|
22
24
|
|
|
@@ -72,7 +74,7 @@ async def tenant_exists(app: Any, tenant_id: str) -> bool | None:
|
|
|
72
74
|
URL ask through this, so they can refuse a typo without importing that
|
|
73
75
|
module; with none installed the answer is ``None`` and they accept the id.
|
|
74
76
|
"""
|
|
75
|
-
check: TenantExists | None =
|
|
77
|
+
check: TenantExists | None = get_tenant_exists(app)
|
|
76
78
|
if check is None:
|
|
77
79
|
return None
|
|
78
80
|
return bool(await check(tenant_id))
|
|
@@ -97,7 +99,7 @@ async def tenant_member_ids(
|
|
|
97
99
|
no membership store is installed; callers on a multi-tenant app treat it
|
|
98
100
|
as "nobody" (fail closed).
|
|
99
101
|
"""
|
|
100
|
-
check: TenantMembers | None =
|
|
102
|
+
check: TenantMembers | None = get_tenant_members(app)
|
|
101
103
|
if check is None:
|
|
102
104
|
return None
|
|
103
105
|
if not user_ids:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Tenant lifecycle contracts shared without a dependency on a tenant provider."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from simple_module_core.events import Event
|
|
6
|
+
|
|
7
|
+
TENANT_DELETED_CHANNEL = "tenants.deleted"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class TenantDeleted(Event):
|
|
12
|
+
"""The tenant registry row is gone; owners must durably tear down its data.
|
|
13
|
+
|
|
14
|
+
Delivery is at least once. Retention stores may keep historical rows,
|
|
15
|
+
identified by their original tenant id, but must never expose live resources.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
tenant_id: str
|
|
19
|
+
slug: str
|
|
20
|
+
name: str
|
|
@@ -8,6 +8,7 @@ from collections.abc import Sequence
|
|
|
8
8
|
from packaging.specifiers import InvalidSpecifier, SpecifierSet
|
|
9
9
|
from packaging.version import Version
|
|
10
10
|
|
|
11
|
+
from simple_module_core.deprecation import deprecate
|
|
11
12
|
from simple_module_core.exceptions import FrameworkVersionError
|
|
12
13
|
from simple_module_core.module import ModuleBase
|
|
13
14
|
|
|
@@ -28,6 +29,13 @@ registries, the event bus, ``create_module_base``, or the model mixins.
|
|
|
28
29
|
_FRAMEWORK_VERSION = Version(FRAMEWORK_API_VERSION)
|
|
29
30
|
|
|
30
31
|
|
|
32
|
+
_UNVERSIONED_MODULE = deprecate(
|
|
33
|
+
"ModuleMeta.requires_framework=None compatibility bypass",
|
|
34
|
+
remove_in="0.1.0",
|
|
35
|
+
use="ModuleMeta(requires_framework=...) with an explicit compatible range",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
31
39
|
def check_framework_compatibility(
|
|
32
40
|
modules: Sequence[ModuleBase],
|
|
33
41
|
framework_version: str = FRAMEWORK_API_VERSION,
|
|
@@ -48,6 +56,9 @@ def check_framework_compatibility(
|
|
|
48
56
|
for mod in modules:
|
|
49
57
|
spec_str = mod.meta.requires_framework
|
|
50
58
|
if spec_str is None:
|
|
59
|
+
# Tracked for removal (``_UNVERSIONED_MODULE``) but not warned per
|
|
60
|
+
# module: every unversioned module, in-repo ones included, would
|
|
61
|
+
# warn on each boot.
|
|
51
62
|
continue
|
|
52
63
|
try:
|
|
53
64
|
spec = SpecifierSet(spec_str)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Optional app services retain their defaults and installed object identity."""
|
|
2
|
+
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from fastapi import FastAPI
|
|
7
|
+
from simple_module_core import app_state
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.parametrize(
|
|
11
|
+
("attribute", "accessor_name", "default"),
|
|
12
|
+
[
|
|
13
|
+
("sm", "get_services", None),
|
|
14
|
+
("host", "get_host_services", None),
|
|
15
|
+
("public_routes", "get_public_routes", None),
|
|
16
|
+
("body_limits", "get_body_limits", None),
|
|
17
|
+
("rate_limit_rules", "get_rate_limit_rules", None),
|
|
18
|
+
("design_packs", "get_design_packs", None),
|
|
19
|
+
("migration", "get_migration_status", None),
|
|
20
|
+
("openapi_url", "get_openapi_url", None),
|
|
21
|
+
("spa_prefixes", "get_spa_prefixes", ()),
|
|
22
|
+
("deferred_startup", "get_deferred_startup", None),
|
|
23
|
+
("deferred_startup_lock", "get_deferred_startup_lock", None),
|
|
24
|
+
("principal_serializer", "get_principal_serializer", None),
|
|
25
|
+
("tenant_resolver", "get_tenant_resolver", None),
|
|
26
|
+
("tenant_exists", "get_tenant_exists", None),
|
|
27
|
+
("tenant_members", "get_tenant_members", None),
|
|
28
|
+
("inertia_dependency", "get_inertia_dependency", None),
|
|
29
|
+
("inertia_shared_providers", "get_inertia_shared_providers", None),
|
|
30
|
+
("branding", "get_branding", None),
|
|
31
|
+
("auth", "get_auth_provider", None),
|
|
32
|
+
],
|
|
33
|
+
)
|
|
34
|
+
def test_core_optional_app_service(attribute, accessor_name, default):
|
|
35
|
+
app = FastAPI()
|
|
36
|
+
accessor = getattr(app_state, accessor_name)
|
|
37
|
+
assert accessor(app) == default
|
|
38
|
+
|
|
39
|
+
installed = object()
|
|
40
|
+
value = SimpleNamespace(auth_provider=installed) if attribute == "auth" else installed
|
|
41
|
+
setattr(app.state, attribute, value)
|
|
42
|
+
assert accessor(app) is installed
|
|
43
|
+
|
|
44
|
+
setattr(app.state, attribute, None)
|
|
45
|
+
assert accessor(app) is None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_core_partial_auth_state_has_no_provider():
|
|
49
|
+
app = FastAPI()
|
|
50
|
+
app.state.auth = SimpleNamespace()
|
|
51
|
+
assert app_state.get_auth_provider(app) is None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_core_optional_scope_app_can_lack_state():
|
|
55
|
+
assert app_state.get_services(None) is None
|
|
56
|
+
assert app_state.get_services(SimpleNamespace()) is None
|
|
57
|
+
assert app_state.get_tenant_resolver(None) is None
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Keep the lifecycle reference complete as ModuleBase gains hooks."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from simple_module_core.module import ModuleBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_every_public_registration_hook_is_documented():
|
|
11
|
+
root = Path(__file__).resolve().parents[3]
|
|
12
|
+
document = (root / "docs/framework/lifecycle.md").read_text(encoding="utf-8")
|
|
13
|
+
hooks = {
|
|
14
|
+
name
|
|
15
|
+
for name, _ in inspect.getmembers(ModuleBase, predicate=inspect.isfunction)
|
|
16
|
+
if name.startswith("register_")
|
|
17
|
+
}
|
|
18
|
+
documented = set(re.findall(r"\bregister_\w+\b", document))
|
|
19
|
+
|
|
20
|
+
assert not (missing := hooks - documented), f"Undocumented lifecycle hooks: {sorted(missing)}"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Explicit inline fallback preserves transaction ownership and error propagation."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import AsyncMock
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from simple_module_core.durable_events import DurableContext
|
|
7
|
+
from simple_module_core.events import EventBus
|
|
8
|
+
from simple_module_core.tenant_events import TenantDeleted
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.parametrize("transport", [False, True])
|
|
12
|
+
async def test_durable_or_inline_dispatch(transport):
|
|
13
|
+
bus = EventBus()
|
|
14
|
+
db = object()
|
|
15
|
+
event = TenantDeleted("id", "slug", "Name")
|
|
16
|
+
handlers = [AsyncMock(), AsyncMock()]
|
|
17
|
+
for index, handler in enumerate(handlers):
|
|
18
|
+
bus.subscribe_durable(TenantDeleted, handler, name=str(index))
|
|
19
|
+
ordinary = AsyncMock()
|
|
20
|
+
bus.subscribe(TenantDeleted, ordinary)
|
|
21
|
+
if transport:
|
|
22
|
+
bus.durable_transport = AsyncMock()
|
|
23
|
+
await bus.publish_durable_or_inline(event, db)
|
|
24
|
+
for handler in handlers:
|
|
25
|
+
if transport:
|
|
26
|
+
handler.assert_not_awaited()
|
|
27
|
+
else:
|
|
28
|
+
handler.assert_awaited_once_with(event, DurableContext(db=db, attempt=1))
|
|
29
|
+
ordinary.assert_not_awaited()
|
|
30
|
+
if transport:
|
|
31
|
+
bus.durable_transport.enqueue.assert_awaited_once_with(event, ["0", "1"], db)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def test_inline_failure_propagates():
|
|
35
|
+
bus = EventBus()
|
|
36
|
+
bus.subscribe_durable(TenantDeleted, AsyncMock(side_effect=ValueError("failed")), name="fail")
|
|
37
|
+
with pytest.raises(ValueError, match="failed"):
|
|
38
|
+
await bus.publish_durable_or_inline(TenantDeleted("id", "slug", "Name"), object())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/_permission_grants.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/_request_hooks.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
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/__init__.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_coupling.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_css.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_i18n.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_migration.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_module.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_pages.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_runner.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_spa_mount.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_tenancy.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/diagnostics/_types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/durable_events.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/i18n_overrides.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/module_settings.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/permission_denied.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/redirect_safety.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/settings_hydrate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/stored_settings.py
RENAMED
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/simple_module_core/user_directory.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_auth_provider_selection.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
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_db_extensions_diagnostics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_discovery_missing_deps.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_expression_index_diagnostics.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_feature_flags_decorator.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_heavy_task_imports_diagnostics.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
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_permissions_admin_principal.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_public_routes_rate_precedence.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_core-0.0.38 → simple_module_core-0.0.39}/tests/test_sm009_dynamic_coupling.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|