simple-module-feature-flags 0.0.39__tar.gz → 0.0.41__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.39 → simple_module_feature_flags-0.0.41}/PKG-INFO +4 -4
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/deps.py +2 -1
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/endpoints/api.py +4 -0
- simple_module_feature_flags-0.0.41/feature_flags/invalidation.py +17 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/module.py +13 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/service.py +29 -9
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/package.json +1 -1
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/pyproject.toml +4 -4
- simple_module_feature_flags-0.0.41/tests/test_feature_flags_commit_invalidation.py +78 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/tests/test_feature_flags_service.py +10 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/.gitignore +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/LICENSE +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/README.md +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/__init__.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/constants.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/contracts/__init__.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/contracts/schemas.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/endpoints/__init__.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/endpoints/views.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/locales/de.json +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/locales/en.json +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/models.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/pages/Browse.tsx +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/pages/components/FlagTable.tsx +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/pages/components/ScopeCard.tsx +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/pages/components/TenantPicker.tsx +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/pages/components/ToggleConfirmDialog.tsx +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/pages/types.ts +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/py.typed +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/tenant_cleanup.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/tests/conftest.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/tests/test_feature_flags_api.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/tests/test_feature_flags_audit_links.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/tests/test_feature_flags_menu_section.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/tests/test_feature_flags_tenancy.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/tests/test_feature_flags_tenant_cleanup.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/tests/test_flag_description_i18n.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/tests/test_store_outage_fallback.py +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/tests-js/feature_flags_Browse.test.tsx +0 -0
- {simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/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.41
|
|
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.41
|
|
25
|
+
Requires-Dist: simple-module-db==0.0.41
|
|
26
|
+
Requires-Dist: simple-module-hosting==0.0.41
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
29
|
# simple_module_feature_flags
|
{simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/deps.py
RENAMED
|
@@ -14,9 +14,10 @@ from feature_flags.service import FeatureFlagService
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
async def get_feature_flag_service(
|
|
17
|
+
request: Request,
|
|
17
18
|
db: AsyncSession = Depends(get_db),
|
|
18
19
|
) -> FeatureFlagService:
|
|
19
|
-
return FeatureFlagService(db)
|
|
20
|
+
return FeatureFlagService(db, app=request.app)
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
async def get_feature_flag_registry(request: Request) -> FeatureFlagRegistry:
|
|
@@ -70,6 +70,8 @@ async def set_override(
|
|
|
70
70
|
await service.set_override(name, body.enabled, registry=registry)
|
|
71
71
|
view = service.build_view(registry, name)
|
|
72
72
|
assert view is not None # _ensure_registered already proved it exists
|
|
73
|
+
view.enabled = body.enabled
|
|
74
|
+
view.overridden = True
|
|
73
75
|
return view
|
|
74
76
|
|
|
75
77
|
|
|
@@ -125,6 +127,8 @@ async def set_tenant_override(
|
|
|
125
127
|
)
|
|
126
128
|
view = service.build_view(registry, name, tenant_id=tenant_id)
|
|
127
129
|
assert view is not None # _ensure_registered already proved it exists
|
|
130
|
+
view.enabled = body.enabled
|
|
131
|
+
view.overridden = True
|
|
128
132
|
return view
|
|
129
133
|
|
|
130
134
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Re-read flag overrides after commit or a remote invalidation."""
|
|
2
|
+
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
from simple_module_core.invalidation import InvalidationBus
|
|
5
|
+
from simple_module_core.invalidation_refresh import InvalidationRefresh
|
|
6
|
+
|
|
7
|
+
from feature_flags.service import FeatureFlagService
|
|
8
|
+
|
|
9
|
+
CHANNEL = "feature_flags.overrides"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def subscribe(bus: InvalidationBus, app: FastAPI) -> InvalidationRefresh:
|
|
13
|
+
async def reload(_key: str | None) -> None:
|
|
14
|
+
async with app.state.sm.db.session_factory() as db:
|
|
15
|
+
await FeatureFlagService(db).hydrate_registry(app.state.sm.feature_flags)
|
|
16
|
+
|
|
17
|
+
return InvalidationRefresh(bus, CHANNEL, reload)
|
{simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/module.py
RENAMED
|
@@ -30,6 +30,7 @@ from feature_flags.constants import (
|
|
|
30
30
|
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
32
|
from simple_module_core.events import EventBus
|
|
33
|
+
from simple_module_core.invalidation import InvalidationBus
|
|
33
34
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
34
35
|
|
|
35
36
|
_logger = logging.getLogger(__name__)
|
|
@@ -75,6 +76,11 @@ class FeatureFlagsModule(ModuleBase):
|
|
|
75
76
|
|
|
76
77
|
bus.subscribe_durable(TenantDeleted, purge_tenant_data)
|
|
77
78
|
|
|
79
|
+
def register_invalidations(self, bus: InvalidationBus, app: FastAPI) -> None:
|
|
80
|
+
from feature_flags.invalidation import subscribe
|
|
81
|
+
|
|
82
|
+
self._refresh = subscribe(bus, app)
|
|
83
|
+
|
|
78
84
|
def register_routes(self, api_router: APIRouter, view_router: APIRouter) -> None:
|
|
79
85
|
from feature_flags.endpoints.api import router as api
|
|
80
86
|
from feature_flags.endpoints.views import router as views
|
|
@@ -157,3 +163,10 @@ class FeatureFlagsModule(ModuleBase):
|
|
|
157
163
|
await service.hydrate_registry(sm.feature_flags)
|
|
158
164
|
except Exception:
|
|
159
165
|
_logger.exception("feature_flags.hydrate_failed — continuing with registry defaults")
|
|
166
|
+
|
|
167
|
+
if hasattr(self, "_refresh"):
|
|
168
|
+
self._refresh.start()
|
|
169
|
+
|
|
170
|
+
async def on_shutdown(self, app: FastAPI) -> None:
|
|
171
|
+
if hasattr(self, "_refresh"):
|
|
172
|
+
await self._refresh.stop()
|
{simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/service.py
RENAMED
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
from collections.abc import Callable
|
|
11
11
|
|
|
12
|
+
from fastapi import FastAPI
|
|
12
13
|
from simple_module_core.feature_flags import FeatureFlagDefinition, FeatureFlagRegistry
|
|
13
14
|
from sqlalchemy import select
|
|
14
15
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
@@ -60,8 +61,9 @@ class FeatureFlagService:
|
|
|
60
61
|
extra DB hit per request.
|
|
61
62
|
"""
|
|
62
63
|
|
|
63
|
-
def __init__(self, db: AsyncSession) -> None:
|
|
64
|
+
def __init__(self, db: AsyncSession, app: FastAPI | None = None) -> None:
|
|
64
65
|
self.db = db
|
|
66
|
+
self.app = app
|
|
65
67
|
|
|
66
68
|
async def list_overrides(self) -> list[FeatureFlagOverrideOut]:
|
|
67
69
|
"""Every persisted override across all scopes, ordered for stable display."""
|
|
@@ -179,7 +181,7 @@ class FeatureFlagService:
|
|
|
179
181
|
existing.enabled = enabled
|
|
180
182
|
await self.db.flush()
|
|
181
183
|
if registry is not None:
|
|
182
|
-
|
|
184
|
+
self._on_commit(registry, name, enabled, scope, scope_id)
|
|
183
185
|
return FeatureFlagOverrideOut.model_validate(existing)
|
|
184
186
|
|
|
185
187
|
async def clear_override(
|
|
@@ -195,16 +197,34 @@ class FeatureFlagService:
|
|
|
195
197
|
return False
|
|
196
198
|
await self.db.delete(existing)
|
|
197
199
|
if registry is not None:
|
|
198
|
-
|
|
200
|
+
self._on_commit(registry, name, None, scope, scope_id)
|
|
199
201
|
return True
|
|
200
202
|
|
|
201
203
|
async def hydrate_registry(self, registry: FeatureFlagRegistry) -> int:
|
|
202
204
|
"""Load every persisted override (system + tenant) into the registry at boot."""
|
|
203
205
|
overrides = await self.list_overrides()
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
tenant_id=_registry_tenant_id(ovr.scope, ovr.scope_id),
|
|
209
|
-
)
|
|
206
|
+
registry.replace_overrides(
|
|
207
|
+
{o.name: o.enabled for o in overrides if o.scope == SCOPE_SYSTEM},
|
|
208
|
+
{(o.name, o.scope_id): o.enabled for o in overrides if o.scope == SCOPE_TENANT},
|
|
209
|
+
)
|
|
210
210
|
return len(overrides)
|
|
211
|
+
|
|
212
|
+
def _on_commit(
|
|
213
|
+
self,
|
|
214
|
+
registry: FeatureFlagRegistry,
|
|
215
|
+
name: str,
|
|
216
|
+
enabled: bool | None,
|
|
217
|
+
scope: str,
|
|
218
|
+
scope_id: str,
|
|
219
|
+
) -> None:
|
|
220
|
+
async def committed() -> None:
|
|
221
|
+
if self.app is not None:
|
|
222
|
+
from feature_flags.invalidation import CHANNEL
|
|
223
|
+
|
|
224
|
+
await self.app.state.sm.invalidation.publish(CHANNEL, key=name)
|
|
225
|
+
elif enabled is None:
|
|
226
|
+
registry.clear_override(name, tenant_id=_registry_tenant_id(scope, scope_id))
|
|
227
|
+
else:
|
|
228
|
+
registry.set_override(name, enabled, tenant_id=_registry_tenant_id(scope, scope_id))
|
|
229
|
+
|
|
230
|
+
self.db.on_commit(committed)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "simple_module_feature_flags"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.41"
|
|
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.41",
|
|
25
|
+
"simple_module_db==0.0.41",
|
|
26
|
+
"simple_module_hosting==0.0.41",
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[project.entry-points.simple_module]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Flag changes must survive commit before reaching any worker's registry."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from feature_flags.models import FeatureFlagOverride
|
|
5
|
+
from feature_flags.service import FeatureFlagService
|
|
6
|
+
from simple_module_core.feature_flags import FeatureFlagDefinition, FeatureFlagRegistry
|
|
7
|
+
from simple_module_core.invalidation import Invalidation
|
|
8
|
+
from simple_module_db.transaction import finalize_session, rollback_session
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.parametrize("commit", [False, True])
|
|
12
|
+
@pytest.mark.parametrize("scope,scope_id", [("system", ""), ("tenant", "acme")])
|
|
13
|
+
@pytest.mark.parametrize("clear", [False, True])
|
|
14
|
+
async def test_flag_mutation_waits_for_commit(db_session, commit, scope, scope_id, clear):
|
|
15
|
+
registry = FeatureFlagRegistry()
|
|
16
|
+
registry.add(FeatureFlagDefinition("switch"))
|
|
17
|
+
tenant_id = scope_id or None
|
|
18
|
+
if clear:
|
|
19
|
+
db_session.add(
|
|
20
|
+
FeatureFlagOverride(scope=scope, scope_id=scope_id, name="switch", enabled=True)
|
|
21
|
+
)
|
|
22
|
+
await db_session.commit()
|
|
23
|
+
registry.set_override("switch", True, tenant_id=tenant_id)
|
|
24
|
+
service = FeatureFlagService(db_session)
|
|
25
|
+
if clear:
|
|
26
|
+
await service.clear_override("switch", registry, scope, scope_id)
|
|
27
|
+
else:
|
|
28
|
+
await service.set_override("switch", True, registry, scope, scope_id)
|
|
29
|
+
assert registry.is_enabled("switch", tenant_id) is clear
|
|
30
|
+
if commit:
|
|
31
|
+
await finalize_session(db_session)
|
|
32
|
+
else:
|
|
33
|
+
await rollback_session(db_session)
|
|
34
|
+
assert registry.is_enabled("switch", tenant_id) is (clear != commit)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.parametrize("scope,scope_id", [("system", ""), ("tenant", "acme")])
|
|
38
|
+
async def test_remote_flag_changes_and_deletions_reload(app, scope, scope_id):
|
|
39
|
+
registry = app.state.sm.feature_flags
|
|
40
|
+
registry.add(FeatureFlagDefinition("switch"))
|
|
41
|
+
factory = app.state.sm.db.session_factory
|
|
42
|
+
async with factory() as db:
|
|
43
|
+
row = FeatureFlagOverride(scope=scope, scope_id=scope_id, name="switch", enabled=True)
|
|
44
|
+
db.add(row)
|
|
45
|
+
await db.commit()
|
|
46
|
+
row_id = row.id
|
|
47
|
+
bus = app.state.sm.invalidation
|
|
48
|
+
message = Invalidation(channel="feature_flags.overrides", origin="other")
|
|
49
|
+
await bus.deliver(message.to_wire())
|
|
50
|
+
assert registry.is_enabled("switch", scope_id or None) is True
|
|
51
|
+
async with factory() as db:
|
|
52
|
+
await db.delete(await db.get(FeatureFlagOverride, row_id))
|
|
53
|
+
await db.commit()
|
|
54
|
+
await bus.deliver(message.to_wire())
|
|
55
|
+
assert registry.is_enabled("switch", scope_id or None) is False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.parametrize("commit", [False, True])
|
|
59
|
+
@pytest.mark.parametrize("scope,scope_id", [("system", ""), ("tenant", "acme")])
|
|
60
|
+
async def test_flag_request_publishes_only_after_commit(app, commit, scope, scope_id):
|
|
61
|
+
from feature_flags.deps import get_feature_flag_service
|
|
62
|
+
from starlette.requests import Request
|
|
63
|
+
|
|
64
|
+
registry = app.state.sm.feature_flags
|
|
65
|
+
registry.add(FeatureFlagDefinition("switch"))
|
|
66
|
+
messages = []
|
|
67
|
+
app.state.sm.invalidation.subscribe("feature_flags.overrides", messages.append)
|
|
68
|
+
async with app.state.sm.db.session_factory() as db:
|
|
69
|
+
service = await get_feature_flag_service(Request({"type": "http", "app": app}), db)
|
|
70
|
+
await service.set_override("switch", True, registry, scope, scope_id)
|
|
71
|
+
assert registry.is_enabled("switch", scope_id or None) is False
|
|
72
|
+
assert messages == []
|
|
73
|
+
if commit:
|
|
74
|
+
await finalize_session(db)
|
|
75
|
+
else:
|
|
76
|
+
await rollback_session(db)
|
|
77
|
+
assert registry.is_enabled("switch", scope_id or None) is commit
|
|
78
|
+
assert len(messages) == int(commit)
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from feature_flags.constants import SCOPE_TENANT
|
|
6
6
|
from feature_flags.service import FeatureFlagService
|
|
7
7
|
from simple_module_core.feature_flags import FeatureFlagDefinition, FeatureFlagRegistry
|
|
8
|
+
from simple_module_db.transaction import finalize_session
|
|
8
9
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
9
10
|
|
|
10
11
|
|
|
@@ -25,6 +26,7 @@ class TestFeatureFlagServiceSystem:
|
|
|
25
26
|
assert result.name == "beta_ui"
|
|
26
27
|
assert result.scope == "system"
|
|
27
28
|
assert result.enabled is True
|
|
29
|
+
await finalize_session(db_session)
|
|
28
30
|
assert reg.is_enabled("beta_ui") is True
|
|
29
31
|
|
|
30
32
|
async def test_set_override_twice_updates_in_place(self, db_session: AsyncSession):
|
|
@@ -37,6 +39,7 @@ class TestFeatureFlagServiceSystem:
|
|
|
37
39
|
rows = await svc.list_overrides()
|
|
38
40
|
assert len(rows) == 1
|
|
39
41
|
assert rows[0].enabled is False
|
|
42
|
+
await finalize_session(db_session)
|
|
40
43
|
assert reg.is_enabled("beta_ui") is False
|
|
41
44
|
|
|
42
45
|
async def test_clear_override_removes_row_and_reverts_registry(self, db_session: AsyncSession):
|
|
@@ -48,6 +51,7 @@ class TestFeatureFlagServiceSystem:
|
|
|
48
51
|
|
|
49
52
|
assert cleared is True
|
|
50
53
|
assert await svc.list_overrides() == []
|
|
54
|
+
await finalize_session(db_session)
|
|
51
55
|
assert reg.is_enabled("beta_ui") is False # back to default
|
|
52
56
|
|
|
53
57
|
async def test_clear_override_unknown_flag_is_noop(self, db_session: AsyncSession):
|
|
@@ -62,6 +66,7 @@ class TestFeatureFlagServiceSystem:
|
|
|
62
66
|
)
|
|
63
67
|
await svc.set_override("a.flag", True, registry=reg)
|
|
64
68
|
|
|
69
|
+
await finalize_session(db_session)
|
|
65
70
|
views = await svc.list_flags(reg)
|
|
66
71
|
|
|
67
72
|
by_name = {v.name: v for v in views}
|
|
@@ -82,6 +87,7 @@ class TestFeatureFlagServiceSystem:
|
|
|
82
87
|
await svc.set_override("legacy", True, registry=reg_old)
|
|
83
88
|
|
|
84
89
|
reg_new = _registry_with(("current", "", False))
|
|
90
|
+
await finalize_session(db_session)
|
|
85
91
|
views = await svc.list_flags(reg_new)
|
|
86
92
|
|
|
87
93
|
assert [v.name for v in views] == ["current"]
|
|
@@ -98,6 +104,7 @@ class TestFeatureFlagServiceTenant:
|
|
|
98
104
|
rows = await svc.list_overrides()
|
|
99
105
|
assert len(rows) == 2
|
|
100
106
|
# tenant row beats system for that tenant
|
|
107
|
+
await finalize_session(db_session)
|
|
101
108
|
assert reg.is_enabled("beta_ui", tenant_id="acme") is False
|
|
102
109
|
# other tenants fall back to system
|
|
103
110
|
assert reg.is_enabled("beta_ui", tenant_id="other") is True
|
|
@@ -115,6 +122,7 @@ class TestFeatureFlagServiceTenant:
|
|
|
115
122
|
)
|
|
116
123
|
|
|
117
124
|
assert cleared is True
|
|
125
|
+
await finalize_session(db_session)
|
|
118
126
|
assert reg.is_enabled("beta_ui", tenant_id="acme") is False
|
|
119
127
|
assert reg.is_enabled("beta_ui", tenant_id="globex") is True
|
|
120
128
|
|
|
@@ -124,6 +132,7 @@ class TestFeatureFlagServiceTenant:
|
|
|
124
132
|
await svc.set_override("beta_ui", True, registry=reg) # system on
|
|
125
133
|
await svc.set_override("beta_ui", False, registry=reg, scope=SCOPE_TENANT, scope_id="acme")
|
|
126
134
|
|
|
135
|
+
await finalize_session(db_session)
|
|
127
136
|
views = await svc.list_flags(reg, tenant_id="acme")
|
|
128
137
|
|
|
129
138
|
view = next(v for v in views if v.name == "beta_ui")
|
|
@@ -138,6 +147,7 @@ class TestFeatureFlagServiceTenant:
|
|
|
138
147
|
reg = _registry_with(("beta_ui", "", False))
|
|
139
148
|
await svc.set_override("beta_ui", True, registry=reg) # system on
|
|
140
149
|
|
|
150
|
+
await finalize_session(db_session)
|
|
141
151
|
views = await svc.list_flags(reg, tenant_id="acme")
|
|
142
152
|
|
|
143
153
|
view = next(v for v in views if v.name == "beta_ui")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/__init__.py
RENAMED
|
File without changes
|
{simple_module_feature_flags-0.0.39 → simple_module_feature_flags-0.0.41}/feature_flags/constants.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.39 → simple_module_feature_flags-0.0.41}/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.39 → simple_module_feature_flags-0.0.41}/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
|
|
File without changes
|