simple-module-feature-flags 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_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/PKG-INFO +4 -4
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/module.py +8 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/service.py +4 -3
- simple_module_feature_flags-0.0.39/feature_flags/tenant_cleanup.py +18 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/package.json +1 -1
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/pyproject.toml +4 -4
- simple_module_feature_flags-0.0.39/tests/test_feature_flags_tenant_cleanup.py +34 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/.gitignore +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/LICENSE +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/README.md +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/__init__.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/constants.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/contracts/__init__.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/contracts/schemas.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/deps.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/endpoints/__init__.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/endpoints/api.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/endpoints/views.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/locales/de.json +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/locales/en.json +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/models.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/pages/Browse.tsx +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/pages/components/FlagTable.tsx +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/pages/components/ScopeCard.tsx +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/pages/components/TenantPicker.tsx +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/pages/components/ToggleConfirmDialog.tsx +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/pages/types.ts +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/py.typed +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/tests/conftest.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/tests/test_feature_flags_api.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/tests/test_feature_flags_audit_links.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/tests/test_feature_flags_menu_section.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/tests/test_feature_flags_service.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/tests/test_feature_flags_tenancy.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/tests/test_flag_description_i18n.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/tests/test_store_outage_fallback.py +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/tests-js/feature_flags_Browse.test.tsx +0 -0
- {simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/tsconfig.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simple_module_feature_flags
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.39
|
|
4
4
|
Summary: Simple feature-flag module with per-tenant overrides and a consumer API for simple_module
|
|
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,9 +21,9 @@ 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.
|
|
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
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
29
|
# simple_module_feature_flags
|
{simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/module.py
RENAMED
|
@@ -29,6 +29,7 @@ from feature_flags.constants import (
|
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
|
+
from simple_module_core.events import EventBus
|
|
32
33
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
33
34
|
|
|
34
35
|
_logger = logging.getLogger(__name__)
|
|
@@ -67,6 +68,13 @@ class FeatureFlagsModule(ModuleBase):
|
|
|
67
68
|
i18n_audience="admin",
|
|
68
69
|
)
|
|
69
70
|
|
|
71
|
+
def register_event_handlers(self, bus: EventBus, app: FastAPI | None = None) -> None:
|
|
72
|
+
from simple_module_core.tenant_events import TenantDeleted
|
|
73
|
+
|
|
74
|
+
from feature_flags.tenant_cleanup import purge_tenant_data
|
|
75
|
+
|
|
76
|
+
bus.subscribe_durable(TenantDeleted, purge_tenant_data)
|
|
77
|
+
|
|
70
78
|
def register_routes(self, api_router: APIRouter, view_router: APIRouter) -> None:
|
|
71
79
|
from feature_flags.endpoints.api import router as api
|
|
72
80
|
from feature_flags.endpoints.views import router as views
|
{simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/service.py
RENAMED
|
@@ -12,6 +12,7 @@ from collections.abc import Callable
|
|
|
12
12
|
from simple_module_core.feature_flags import FeatureFlagDefinition, FeatureFlagRegistry
|
|
13
13
|
from sqlalchemy import select
|
|
14
14
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
15
|
+
from sqlmodel import col
|
|
15
16
|
|
|
16
17
|
from feature_flags.constants import SCOPE_SYSTEM, SCOPE_TENANT, SYSTEM_SCOPE_ID
|
|
17
18
|
from feature_flags.contracts.schemas import FeatureFlagOverrideOut, FeatureFlagView
|
|
@@ -151,9 +152,9 @@ class FeatureFlagService:
|
|
|
151
152
|
async def _find(self, scope: str, scope_id: str, name: str) -> FeatureFlagOverride | None:
|
|
152
153
|
result = await self.db.execute(
|
|
153
154
|
select(FeatureFlagOverride).where(
|
|
154
|
-
FeatureFlagOverride.scope == scope,
|
|
155
|
-
FeatureFlagOverride.scope_id == scope_id,
|
|
156
|
-
FeatureFlagOverride.name == name,
|
|
155
|
+
col(FeatureFlagOverride.scope) == scope,
|
|
156
|
+
col(FeatureFlagOverride.scope_id) == scope_id,
|
|
157
|
+
col(FeatureFlagOverride.name) == name,
|
|
157
158
|
)
|
|
158
159
|
)
|
|
159
160
|
return result.scalar_one_or_none()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Durable cleanup of tenant-scoped feature flag overrides."""
|
|
2
|
+
|
|
3
|
+
from simple_module_core.durable_events import DurableContext
|
|
4
|
+
from simple_module_core.tenant_events import TenantDeleted
|
|
5
|
+
from sqlmodel import col, delete
|
|
6
|
+
|
|
7
|
+
from feature_flags.constants import SCOPE_TENANT
|
|
8
|
+
from feature_flags.models import FeatureFlagOverride
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def purge_tenant_data(event: TenantDeleted, ctx: DurableContext) -> None:
|
|
12
|
+
"""Keep system/other tenants' overrides; acknowledge with the worker transaction."""
|
|
13
|
+
await ctx.db.execute(
|
|
14
|
+
delete(FeatureFlagOverride).where(
|
|
15
|
+
col(FeatureFlagOverride.scope) == SCOPE_TENANT,
|
|
16
|
+
col(FeatureFlagOverride.scope_id) == event.tenant_id,
|
|
17
|
+
)
|
|
18
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "simple_module_feature_flags"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.39"
|
|
4
4
|
description = "Simple feature-flag module with per-tenant overrides and a consumer API for simple_module"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -21,9 +21,9 @@ 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.
|
|
24
|
+
"simple_module_core==0.0.39",
|
|
25
|
+
"simple_module_db==0.0.39",
|
|
26
|
+
"simple_module_hosting==0.0.39",
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[project.entry-points.simple_module]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Tenant deletion purges tenant overrides without touching other scopes."""
|
|
2
|
+
|
|
3
|
+
from feature_flags.constants import SCOPE_SYSTEM, SCOPE_TENANT
|
|
4
|
+
from feature_flags.models import FeatureFlagOverride
|
|
5
|
+
from feature_flags.module import FeatureFlagsModule
|
|
6
|
+
from simple_module_core.durable_events import DurableContext, event_key
|
|
7
|
+
from simple_module_core.events import EventBus
|
|
8
|
+
from simple_module_core.tenant_events import TenantDeleted
|
|
9
|
+
from sqlmodel import select
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def test_durable_tenant_cleanup_is_scoped_idempotent_and_transactional(db_session):
|
|
13
|
+
bus = EventBus()
|
|
14
|
+
FeatureFlagsModule().register_event_handlers(bus)
|
|
15
|
+
names = bus.durable.handler_names(TenantDeleted)
|
|
16
|
+
assert len(names) == 1
|
|
17
|
+
subscription = bus.durable.lookup(event_key(TenantDeleted), names[0])
|
|
18
|
+
assert subscription is not None
|
|
19
|
+
scopes = [(SCOPE_TENANT, "deleted"), (SCOPE_TENANT, "other"), (SCOPE_SYSTEM, "deleted")]
|
|
20
|
+
for scope, scope_id in scopes:
|
|
21
|
+
db_session.add(FeatureFlagOverride(scope=scope, scope_id=scope_id, name="test.flag"))
|
|
22
|
+
await db_session.commit()
|
|
23
|
+
event = TenantDeleted(tenant_id="deleted", slug="deleted", name="Deleted")
|
|
24
|
+
ctx = DurableContext(db=db_session, attempt=1)
|
|
25
|
+
await subscription.handler(event, ctx)
|
|
26
|
+
await db_session.rollback()
|
|
27
|
+
assert len((await db_session.execute(select(FeatureFlagOverride))).scalars().all()) == 3
|
|
28
|
+
await subscription.handler(event, ctx)
|
|
29
|
+
await subscription.handler(event, DurableContext(db=db_session, attempt=2))
|
|
30
|
+
rows = (await db_session.execute(select(FeatureFlagOverride))).scalars().all()
|
|
31
|
+
assert {(row.scope, row.scope_id) for row in rows} == {
|
|
32
|
+
(SCOPE_TENANT, "other"),
|
|
33
|
+
(SCOPE_SYSTEM, "deleted"),
|
|
34
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/__init__.py
RENAMED
|
File without changes
|
{simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/constants.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/deps.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_feature_flags-0.0.38 → simple_module_feature_flags-0.0.39}/feature_flags/py.typed
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
|