Skip to content

Commit c6cbf0b

Browse files
committed
improve feature flag support
1 parent 588850d commit c6cbf0b

File tree

8 files changed

+394
-10
lines changed

8 files changed

+394
-10
lines changed

src/workos/async_client.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from workos.connect import AsyncConnect
88
from workos.directory_sync import AsyncDirectorySync
99
from workos.events import AsyncEvents
10+
from workos.feature_flags import AsyncFeatureFlags
1011
from workos.fga import FGAModule
1112
from workos.mfa import MFAModule
1213
from workos.organizations import AsyncOrganizations
@@ -95,6 +96,12 @@ def events(self) -> AsyncEvents:
9596
self._events = AsyncEvents(self._http_client)
9697
return self._events
9798

99+
@property
100+
def feature_flags(self) -> AsyncFeatureFlags:
101+
if not getattr(self, "_feature_flags", None):
102+
self._feature_flags = AsyncFeatureFlags(self._http_client)
103+
return self._feature_flags
104+
98105
@property
99106
def fga(self) -> FGAModule:
100107
raise NotImplementedError("FGA APIs are not yet supported in the async client.")

src/workos/client.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from workos.authorization import Authorization
77
from workos.connect import Connect
88
from workos.directory_sync import DirectorySync
9+
from workos.feature_flags import FeatureFlags
910
from workos.fga import FGA
1011
from workos.organizations import Organizations
1112
from workos.organization_domains import OrganizationDomains
@@ -93,6 +94,12 @@ def events(self) -> Events:
9394
self._events = Events(self._http_client)
9495
return self._events
9596

97+
@property
98+
def feature_flags(self) -> FeatureFlags:
99+
if not getattr(self, "_feature_flags", None):
100+
self._feature_flags = FeatureFlags(self._http_client)
101+
return self._feature_flags
102+
96103
@property
97104
def fga(self) -> FGA:
98105
if not getattr(self, "_fga", None):

src/workos/feature_flags.py

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
from typing import Optional, Protocol
2+
3+
from workos.types.feature_flags import FeatureFlag
4+
from workos.types.feature_flags.list_filters import FeatureFlagListFilters
5+
from workos.types.list_resource import ListMetadata, ListPage, WorkOSListResource
6+
from workos.typing.sync_or_async import SyncOrAsync
7+
from workos.utils.http_client import AsyncHTTPClient, SyncHTTPClient
8+
from workos.utils.pagination_order import PaginationOrder
9+
from workos.utils.request_helper import (
10+
DEFAULT_LIST_RESPONSE_LIMIT,
11+
REQUEST_METHOD_DELETE,
12+
REQUEST_METHOD_GET,
13+
REQUEST_METHOD_POST,
14+
REQUEST_METHOD_PUT,
15+
)
16+
17+
FEATURE_FLAGS_PATH = "feature-flags"
18+
19+
FeatureFlagsListResource = WorkOSListResource[
20+
FeatureFlag, FeatureFlagListFilters, ListMetadata
21+
]
22+
23+
24+
class FeatureFlagsModule(Protocol):
25+
"""Offers methods through the WorkOS Feature Flags service."""
26+
27+
def list_feature_flags(
28+
self,
29+
*,
30+
limit: int = DEFAULT_LIST_RESPONSE_LIMIT,
31+
before: Optional[str] = None,
32+
after: Optional[str] = None,
33+
order: PaginationOrder = "desc",
34+
) -> SyncOrAsync[FeatureFlagsListResource]:
35+
"""Retrieve a list of feature flags.
36+
37+
Kwargs:
38+
limit (int): Maximum number of records to return. (Optional)
39+
before (str): Pagination cursor to receive records before a provided ID. (Optional)
40+
after (str): Pagination cursor to receive records after a provided ID. (Optional)
41+
order (Literal["asc","desc"]): Sort records in either ascending or descending order. (Optional)
42+
43+
Returns:
44+
FeatureFlagsListResource: Feature flags list response from WorkOS.
45+
"""
46+
...
47+
48+
def get_feature_flag(self, slug: str) -> SyncOrAsync[FeatureFlag]:
49+
"""Gets details for a single feature flag.
50+
51+
Args:
52+
slug (str): The unique slug identifier of the feature flag.
53+
54+
Returns:
55+
FeatureFlag: Feature flag response from WorkOS.
56+
"""
57+
...
58+
59+
def enable_feature_flag(self, slug: str) -> SyncOrAsync[FeatureFlag]:
60+
"""Enable a feature flag.
61+
62+
Args:
63+
slug (str): The unique slug identifier of the feature flag.
64+
65+
Returns:
66+
FeatureFlag: Updated feature flag response from WorkOS.
67+
"""
68+
...
69+
70+
def disable_feature_flag(self, slug: str) -> SyncOrAsync[FeatureFlag]:
71+
"""Disable a feature flag.
72+
73+
Args:
74+
slug (str): The unique slug identifier of the feature flag.
75+
76+
Returns:
77+
FeatureFlag: Updated feature flag response from WorkOS.
78+
"""
79+
...
80+
81+
def add_feature_flag_target(self, slug: str, resource_id: str) -> SyncOrAsync[None]:
82+
"""Add a target to a feature flag.
83+
84+
Args:
85+
slug (str): The unique slug identifier of the feature flag.
86+
resource_id (str): Resource ID in format user_<id> or org_<id>.
87+
88+
Returns:
89+
None
90+
"""
91+
...
92+
93+
def remove_feature_flag_target(
94+
self, slug: str, resource_id: str
95+
) -> SyncOrAsync[None]:
96+
"""Remove a target from a feature flag.
97+
98+
Args:
99+
slug (str): The unique slug identifier of the feature flag.
100+
resource_id (str): Resource ID in format user_<id> or org_<id>.
101+
102+
Returns:
103+
None
104+
"""
105+
...
106+
107+
108+
class FeatureFlags(FeatureFlagsModule):
109+
_http_client: SyncHTTPClient
110+
111+
def __init__(self, http_client: SyncHTTPClient):
112+
self._http_client = http_client
113+
114+
def list_feature_flags(
115+
self,
116+
*,
117+
limit: int = DEFAULT_LIST_RESPONSE_LIMIT,
118+
before: Optional[str] = None,
119+
after: Optional[str] = None,
120+
order: PaginationOrder = "desc",
121+
) -> FeatureFlagsListResource:
122+
list_params: FeatureFlagListFilters = {
123+
"limit": limit,
124+
"before": before,
125+
"after": after,
126+
"order": order,
127+
}
128+
129+
response = self._http_client.request(
130+
FEATURE_FLAGS_PATH,
131+
method=REQUEST_METHOD_GET,
132+
params=list_params,
133+
)
134+
135+
return WorkOSListResource[FeatureFlag, FeatureFlagListFilters, ListMetadata](
136+
list_method=self.list_feature_flags,
137+
list_args=list_params,
138+
**ListPage[FeatureFlag](**response).model_dump(),
139+
)
140+
141+
def get_feature_flag(self, slug: str) -> FeatureFlag:
142+
response = self._http_client.request(
143+
f"{FEATURE_FLAGS_PATH}/{slug}",
144+
method=REQUEST_METHOD_GET,
145+
)
146+
147+
return FeatureFlag.model_validate(response)
148+
149+
def enable_feature_flag(self, slug: str) -> FeatureFlag:
150+
response = self._http_client.request(
151+
f"{FEATURE_FLAGS_PATH}/{slug}/enable",
152+
method=REQUEST_METHOD_PUT,
153+
json={},
154+
)
155+
156+
return FeatureFlag.model_validate(response)
157+
158+
def disable_feature_flag(self, slug: str) -> FeatureFlag:
159+
response = self._http_client.request(
160+
f"{FEATURE_FLAGS_PATH}/{slug}/disable",
161+
method=REQUEST_METHOD_PUT,
162+
json={},
163+
)
164+
165+
return FeatureFlag.model_validate(response)
166+
167+
def add_feature_flag_target(self, slug: str, resource_id: str) -> None:
168+
self._http_client.request(
169+
f"{FEATURE_FLAGS_PATH}/{slug}/targets/{resource_id}",
170+
method=REQUEST_METHOD_POST,
171+
json={},
172+
)
173+
174+
def remove_feature_flag_target(self, slug: str, resource_id: str) -> None:
175+
self._http_client.request(
176+
f"{FEATURE_FLAGS_PATH}/{slug}/targets/{resource_id}",
177+
method=REQUEST_METHOD_DELETE,
178+
)
179+
180+
181+
class AsyncFeatureFlags(FeatureFlagsModule):
182+
_http_client: AsyncHTTPClient
183+
184+
def __init__(self, http_client: AsyncHTTPClient):
185+
self._http_client = http_client
186+
187+
async def list_feature_flags(
188+
self,
189+
*,
190+
limit: int = DEFAULT_LIST_RESPONSE_LIMIT,
191+
before: Optional[str] = None,
192+
after: Optional[str] = None,
193+
order: PaginationOrder = "desc",
194+
) -> FeatureFlagsListResource:
195+
list_params: FeatureFlagListFilters = {
196+
"limit": limit,
197+
"before": before,
198+
"after": after,
199+
"order": order,
200+
}
201+
202+
response = await self._http_client.request(
203+
FEATURE_FLAGS_PATH,
204+
method=REQUEST_METHOD_GET,
205+
params=list_params,
206+
)
207+
208+
return WorkOSListResource[FeatureFlag, FeatureFlagListFilters, ListMetadata](
209+
list_method=self.list_feature_flags,
210+
list_args=list_params,
211+
**ListPage[FeatureFlag](**response).model_dump(),
212+
)
213+
214+
async def get_feature_flag(self, slug: str) -> FeatureFlag:
215+
response = await self._http_client.request(
216+
f"{FEATURE_FLAGS_PATH}/{slug}",
217+
method=REQUEST_METHOD_GET,
218+
)
219+
220+
return FeatureFlag.model_validate(response)
221+
222+
async def enable_feature_flag(self, slug: str) -> FeatureFlag:
223+
response = await self._http_client.request(
224+
f"{FEATURE_FLAGS_PATH}/{slug}/enable",
225+
method=REQUEST_METHOD_PUT,
226+
json={},
227+
)
228+
229+
return FeatureFlag.model_validate(response)
230+
231+
async def disable_feature_flag(self, slug: str) -> FeatureFlag:
232+
response = await self._http_client.request(
233+
f"{FEATURE_FLAGS_PATH}/{slug}/disable",
234+
method=REQUEST_METHOD_PUT,
235+
json={},
236+
)
237+
238+
return FeatureFlag.model_validate(response)
239+
240+
async def add_feature_flag_target(self, slug: str, resource_id: str) -> None:
241+
await self._http_client.request(
242+
f"{FEATURE_FLAGS_PATH}/{slug}/targets/{resource_id}",
243+
method=REQUEST_METHOD_POST,
244+
json={},
245+
)
246+
247+
async def remove_feature_flag_target(self, slug: str, resource_id: str) -> None:
248+
await self._http_client.request(
249+
f"{FEATURE_FLAGS_PATH}/{slug}/targets/{resource_id}",
250+
method=REQUEST_METHOD_DELETE,
251+
)

src/workos/organizations.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from workos.types.api_keys import ApiKey, ApiKeyWithValue
55
from workos.types.api_keys.list_filters import ApiKeyListFilters
6+
from workos.feature_flags import FeatureFlagsListResource
67
from workos.types.feature_flags import FeatureFlag
78
from workos.types.feature_flags.list_filters import FeatureFlagListFilters
89
from workos.types.metadata import Metadata
@@ -29,10 +30,6 @@
2930
Organization, OrganizationListFilters, ListMetadata
3031
]
3132

32-
FeatureFlagsListResource = WorkOSListResource[
33-
FeatureFlag, FeatureFlagListFilters, ListMetadata
34-
]
35-
3633
ApiKeysListResource = WorkOSListResource[ApiKey, ApiKeyListFilters, ListMetadata]
3734

3835

src/workos/types/feature_flags/feature_flag.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Literal, Optional
1+
from typing import Any, Literal, Optional, Sequence
22
from workos.types.workos_model import WorkOSModel
33

44

@@ -8,5 +8,8 @@ class FeatureFlag(WorkOSModel):
88
slug: str
99
name: str
1010
description: Optional[str]
11+
tags: Sequence[str]
12+
enabled: bool
13+
default_value: Optional[Any]
1114
created_at: str
1215
updated_at: str

src/workos/user_management.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from workos._client_configuration import ClientConfiguration
55
from workos.session import AsyncSession, Session
6+
from workos.feature_flags import FeatureFlagsListResource
67
from workos.types.feature_flags import FeatureFlag
78
from workos.types.feature_flags.list_filters import FeatureFlagListFilters
89
from workos.types.list_resource import (
@@ -119,10 +120,6 @@
119120
Invitation, InvitationsListFilters, ListMetadata
120121
]
121122

122-
FeatureFlagsListResource = WorkOSListResource[
123-
FeatureFlag, FeatureFlagListFilters, ListMetadata
124-
]
125-
126123
SessionsListResource = WorkOSListResource[
127124
UserManagementSession, SessionsListFilters, ListMetadata
128125
]

0 commit comments

Comments
 (0)