From b3b67303af09febea7a0c867ea5bbc7df2309f17 Mon Sep 17 00:00:00 2001 From: "workos-tars[bot]" <269013284+workos-tars[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:46:13 +0000 Subject: [PATCH] feat(generated): Add flag target resources AUTH-6891 makes flag targets and custom target types first-class API resources. Generate the SDK surface so the endpoints can be made public without handwritten wrappers. --- .oagen-manifest.json | 50 +++ src/workos/_client.py | 22 ++ src/workos/flag_target_types/__init__.py | 7 + src/workos/flag_target_types/_resource.py | 249 +++++++++++++ .../flag_target_types/models/__init__.py | 4 + .../models/create_flag_target_type.py | 6 + .../models/flag_target_type.py | 49 +++ src/workos/flag_targets/__init__.py | 4 + src/workos/flag_targets/_resource.py | 335 ++++++++++++++++++ src/workos/flag_targets/models/__init__.py | 4 + .../flag_targets/models/create_flag_target.py | 39 ++ src/workos/flag_targets/models/flag_target.py | 73 ++++ .../types/flag_target_types/__init__.py | 3 + src/workos/types/flag_targets/__init__.py | 3 + tests/fixtures/create_flag_target.json | 5 + tests/fixtures/create_flag_target_type.json | 3 + tests/fixtures/flag_target.json | 13 + tests/fixtures/flag_target_type.json | 7 + tests/fixtures/list_flag_target.json | 21 ++ tests/fixtures/list_flag_target_type.json | 15 + tests/test_flag_target_types.py | 290 +++++++++++++++ ...est_flag_target_types_models_round_trip.py | 33 ++ tests/test_flag_targets.py | 327 +++++++++++++++++ tests/test_flag_targets_models_round_trip.py | 45 +++ 24 files changed, 1607 insertions(+) create mode 100644 src/workos/flag_target_types/__init__.py create mode 100644 src/workos/flag_target_types/_resource.py create mode 100644 src/workos/flag_target_types/models/__init__.py create mode 100644 src/workos/flag_target_types/models/create_flag_target_type.py create mode 100644 src/workos/flag_target_types/models/flag_target_type.py create mode 100644 src/workos/flag_targets/__init__.py create mode 100644 src/workos/flag_targets/_resource.py create mode 100644 src/workos/flag_targets/models/__init__.py create mode 100644 src/workos/flag_targets/models/create_flag_target.py create mode 100644 src/workos/flag_targets/models/flag_target.py create mode 100644 src/workos/types/flag_target_types/__init__.py create mode 100644 src/workos/types/flag_targets/__init__.py create mode 100644 tests/fixtures/create_flag_target.json create mode 100644 tests/fixtures/create_flag_target_type.json create mode 100644 tests/fixtures/flag_target.json create mode 100644 tests/fixtures/flag_target_type.json create mode 100644 tests/fixtures/list_flag_target.json create mode 100644 tests/fixtures/list_flag_target_type.json create mode 100644 tests/test_flag_target_types.py create mode 100644 tests/test_flag_target_types_models_round_trip.py create mode 100644 tests/test_flag_targets.py create mode 100644 tests/test_flag_targets_models_round_trip.py diff --git a/.oagen-manifest.json b/.oagen-manifest.json index 08501acd..317125f7 100644 --- a/.oagen-manifest.json +++ b/.oagen-manifest.json @@ -562,6 +562,16 @@ "src/workos/feature_flags/__init__.py", "src/workos/feature_flags/_resource.py", "src/workos/feature_flags/models/__init__.py", + "src/workos/flag_target_types/__init__.py", + "src/workos/flag_target_types/_resource.py", + "src/workos/flag_target_types/models/__init__.py", + "src/workos/flag_target_types/models/create_flag_target_type.py", + "src/workos/flag_target_types/models/flag_target_type.py", + "src/workos/flag_targets/__init__.py", + "src/workos/flag_targets/_resource.py", + "src/workos/flag_targets/models/__init__.py", + "src/workos/flag_targets/models/create_flag_target.py", + "src/workos/flag_targets/models/flag_target.py", "src/workos/groups/__init__.py", "src/workos/groups/_resource.py", "src/workos/groups/models/__init__.py", @@ -671,6 +681,8 @@ "src/workos/types/directory_sync/__init__.py", "src/workos/types/events/__init__.py", "src/workos/types/feature_flags/__init__.py", + "src/workos/types/flag_target_types/__init__.py", + "src/workos/types/flag_targets/__init__.py", "src/workos/types/groups/__init__.py", "src/workos/types/multi_factor_auth/__init__.py", "src/workos/types/organization_domains/__init__.py", @@ -950,6 +962,8 @@ "tests/fixtures/create_data_integration.json", "tests/fixtures/create_data_key_request.json", "tests/fixtures/create_data_key_response.json", + "tests/fixtures/create_flag_target.json", + "tests/fixtures/create_flag_target_type.json", "tests/fixtures/create_group.json", "tests/fixtures/create_group_membership.json", "tests/fixtures/create_group_role_assignment.json", @@ -1071,6 +1085,8 @@ "tests/fixtures/flag_rule_updated_context_previous_attribute_data.json", "tests/fixtures/flag_rule_updated_data.json", "tests/fixtures/flag_rule_updated_data_owner.json", + "tests/fixtures/flag_target.json", + "tests/fixtures/flag_target_type.json", "tests/fixtures/flag_updated.json", "tests/fixtures/flag_updated_context.json", "tests/fixtures/flag_updated_context_actor.json", @@ -1117,6 +1133,8 @@ "tests/fixtures/list_directory_user_with_groups.json", "tests/fixtures/list_event_schema.json", "tests/fixtures/list_flag.json", + "tests/fixtures/list_flag_target.json", + "tests/fixtures/list_flag_target_type.json", "tests/fixtures/list_group.json", "tests/fixtures/list_group_role_assignment.json", "tests/fixtures/list_metadata.json", @@ -1354,6 +1372,10 @@ "tests/test_events_models_round_trip.py", "tests/test_feature_flags.py", "tests/test_feature_flags_models_round_trip.py", + "tests/test_flag_target_types.py", + "tests/test_flag_target_types_models_round_trip.py", + "tests/test_flag_targets.py", + "tests/test_flag_targets_models_round_trip.py", "tests/test_groups.py", "tests/test_groups_models_round_trip.py", "tests/test_multi_factor_auth.py", @@ -2229,6 +2251,34 @@ "PUT /data-integrations/{slug}/client-credentials": { "sdkMethod": "update_data_integration_client_credentials", "service": "pipes" + }, + "GET /flag_target_types": { + "sdkMethod": "list_flag_target_types", + "service": "flag_target_types" + }, + "POST /flag_target_types": { + "sdkMethod": "create_flag_target_type", + "service": "flag_target_types" + }, + "GET /flag_target_types/{id}": { + "sdkMethod": "get_flag_target_type", + "service": "flag_target_types" + }, + "GET /flag_targets": { + "sdkMethod": "list_flag_targets", + "service": "flag_targets" + }, + "POST /flag_targets": { + "sdkMethod": "create_flag_target", + "service": "flag_targets" + }, + "GET /flag_targets/{id}": { + "sdkMethod": "get_flag_target", + "service": "flag_targets" + }, + "DELETE /flag_targets/{id}": { + "sdkMethod": "delete_flag_target", + "service": "flag_targets" } } } diff --git a/src/workos/_client.py b/src/workos/_client.py index 41d319cc..70e3df41 100644 --- a/src/workos/_client.py +++ b/src/workos/_client.py @@ -18,6 +18,7 @@ from .directory_sync._resource import DirectorySync, AsyncDirectorySync from .events._resource import Events, AsyncEvents from .feature_flags._resource import FeatureFlags, AsyncFeatureFlags +from .flag_target_types._resource import FlagTargetTypes, AsyncFlagTargetTypes from .organization_domains._resource import ( OrganizationDomains, AsyncOrganizationDomains, @@ -37,6 +38,7 @@ from .webhooks._resource import Webhooks, AsyncWebhooks from .widgets._resource import Widgets, AsyncWidgets from .audit_logs._resource import AuditLogs, AsyncAuditLogs +from .flag_targets._resource import FlagTargets, AsyncFlagTargets from .passwordless import AsyncPasswordless, Passwordless from .actions import Actions, AsyncActions from .pkce import PKCE @@ -95,6 +97,11 @@ def feature_flags(self) -> FeatureFlags: """Feature Flags API resources.""" return FeatureFlags(self) + @functools.cached_property + def flag_target_types(self) -> FlagTargetTypes: + """Flag Target Types API resources.""" + return FlagTargetTypes(self) + @functools.cached_property def organization_domains(self) -> OrganizationDomains: """Organization Domains API resources.""" @@ -160,6 +167,11 @@ def audit_logs(self) -> AuditLogs: """Audit Logs API resources.""" return AuditLogs(self) + @functools.cached_property + def flag_targets(self) -> FlagTargets: + """Flag Targets API resources.""" + return FlagTargets(self) + @functools.cached_property def mfa(self) -> MultiFactorAuth: """Alias for multi_factor_auth.""" @@ -238,6 +250,11 @@ def feature_flags(self) -> AsyncFeatureFlags: """Feature Flags API resources.""" return AsyncFeatureFlags(self) + @functools.cached_property + def flag_target_types(self) -> AsyncFlagTargetTypes: + """Flag Target Types API resources.""" + return AsyncFlagTargetTypes(self) + @functools.cached_property def organization_domains(self) -> AsyncOrganizationDomains: """Organization Domains API resources.""" @@ -303,6 +320,11 @@ def audit_logs(self) -> AsyncAuditLogs: """Audit Logs API resources.""" return AsyncAuditLogs(self) + @functools.cached_property + def flag_targets(self) -> AsyncFlagTargets: + """Flag Targets API resources.""" + return AsyncFlagTargets(self) + @functools.cached_property def mfa(self) -> AsyncMultiFactorAuth: """Alias for multi_factor_auth.""" diff --git a/src/workos/flag_target_types/__init__.py b/src/workos/flag_target_types/__init__.py new file mode 100644 index 00000000..1335dcbc --- /dev/null +++ b/src/workos/flag_target_types/__init__.py @@ -0,0 +1,7 @@ +# This file is auto-generated by oagen. Do not edit. + +from ._resource import ( + FlagTargetTypes as FlagTargetTypes, + AsyncFlagTargetTypes as AsyncFlagTargetTypes, +) +from .models import * diff --git a/src/workos/flag_target_types/_resource.py b/src/workos/flag_target_types/_resource.py new file mode 100644 index 00000000..66b7fc87 --- /dev/null +++ b/src/workos/flag_target_types/_resource.py @@ -0,0 +1,249 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union + +if TYPE_CHECKING: + from .._client import AsyncWorkOSClient, WorkOSClient + +from .._types import RequestOptions, enum_value +from .models import FlagTargetType +from workos.common.models.pagination_order import PaginationOrder +from .._pagination import AsyncPage, SyncPage + + +class FlagTargetTypes: + """Flag Target Types API resources.""" + + def __init__(self, client: "WorkOSClient") -> None: + self._client = client + + def list_flag_target_types( + self, + *, + limit: Optional[int] = None, + before: Optional[str] = None, + after: Optional[str] = None, + order: Optional[Union[PaginationOrder, str]] = "desc", + request_options: Optional[RequestOptions] = None, + ) -> SyncPage[FlagTargetType]: + """List flag target types + + Get a list of the custom Flag Target Types registered for the current environment's project. The built-in `user` and `organization` target types are not included. + + Args: + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. + order: Order the results by the creation time. Defaults to `desc`. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SyncPage[FlagTargetType] + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + }.items() + if v is not None + } + return self._client.request_page( + method="get", + path=("flag_target_types",), + model=FlagTargetType, + params=params, + request_options=request_options, + ) + + def create_flag_target_type( + self, + *, + slug: str, + request_options: Optional[RequestOptions] = None, + ) -> FlagTargetType: + """Create a flag target type + + Register a custom Flag Target Type, making its slug usable as the `target_type` of a Flag Target anywhere in the project. + + Args: + slug: A unique key used as the `target_type` of a Flag Target, scoped to the project. May contain lowercase letters, numbers, underscores and hyphens. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + FlagTargetType + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: Dict[str, Any] = { + "slug": slug, + } + return self._client.request( + method="post", + path=("flag_target_types",), + body=body, + model=FlagTargetType, + request_options=request_options, + ) + + def get_flag_target_type( + self, + id: str, + *, + request_options: Optional[RequestOptions] = None, + ) -> FlagTargetType: + """Get a flag target type + + Get the details of an existing Flag Target Type. + + Args: + id: Unique identifier of the Flag Target Type. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + FlagTargetType + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="get", + path=("flag_target_types", str(id)), + model=FlagTargetType, + request_options=request_options, + ) + + +class AsyncFlagTargetTypes: + """Flag Target Types API resources (async).""" + + def __init__(self, client: "AsyncWorkOSClient") -> None: + self._client = client + + async def list_flag_target_types( + self, + *, + limit: Optional[int] = None, + before: Optional[str] = None, + after: Optional[str] = None, + order: Optional[Union[PaginationOrder, str]] = "desc", + request_options: Optional[RequestOptions] = None, + ) -> AsyncPage[FlagTargetType]: + """List flag target types + + Get a list of the custom Flag Target Types registered for the current environment's project. The built-in `user` and `organization` target types are not included. + + Args: + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. + order: Order the results by the creation time. Defaults to `desc`. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AsyncPage[FlagTargetType] + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + }.items() + if v is not None + } + return await self._client.request_page( + method="get", + path=("flag_target_types",), + model=FlagTargetType, + params=params, + request_options=request_options, + ) + + async def create_flag_target_type( + self, + *, + slug: str, + request_options: Optional[RequestOptions] = None, + ) -> FlagTargetType: + """Create a flag target type + + Register a custom Flag Target Type, making its slug usable as the `target_type` of a Flag Target anywhere in the project. + + Args: + slug: A unique key used as the `target_type` of a Flag Target, scoped to the project. May contain lowercase letters, numbers, underscores and hyphens. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + FlagTargetType + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: Dict[str, Any] = { + "slug": slug, + } + return await self._client.request( + method="post", + path=("flag_target_types",), + body=body, + model=FlagTargetType, + request_options=request_options, + ) + + async def get_flag_target_type( + self, + id: str, + *, + request_options: Optional[RequestOptions] = None, + ) -> FlagTargetType: + """Get a flag target type + + Get the details of an existing Flag Target Type. + + Args: + id: Unique identifier of the Flag Target Type. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + FlagTargetType + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="get", + path=("flag_target_types", str(id)), + model=FlagTargetType, + request_options=request_options, + ) diff --git a/src/workos/flag_target_types/models/__init__.py b/src/workos/flag_target_types/models/__init__.py new file mode 100644 index 00000000..f50241b7 --- /dev/null +++ b/src/workos/flag_target_types/models/__init__.py @@ -0,0 +1,4 @@ +# This file is auto-generated by oagen. Do not edit. + +from .create_flag_target_type import CreateFlagTargetType as CreateFlagTargetType +from .flag_target_type import FlagTargetType as FlagTargetType diff --git a/src/workos/flag_target_types/models/create_flag_target_type.py b/src/workos/flag_target_types/models/create_flag_target_type.py new file mode 100644 index 00000000..a59d6b70 --- /dev/null +++ b/src/workos/flag_target_types/models/create_flag_target_type.py @@ -0,0 +1,6 @@ +# This file is auto-generated by oagen. Do not edit. + +from typing import TypeAlias +from workos.common.models.add_role_permission import AddRolePermission + +CreateFlagTargetType: TypeAlias = AddRolePermission diff --git a/src/workos/flag_target_types/models/flag_target_type.py b/src/workos/flag_target_types/models/flag_target_type.py new file mode 100644 index 00000000..e7bb4aff --- /dev/null +++ b/src/workos/flag_target_types/models/flag_target_type.py @@ -0,0 +1,49 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Dict, Literal +from workos._types import _raise_deserialize_error +from workos._types import _format_datetime, _parse_datetime + + +@dataclass(slots=True) +class FlagTargetType: + """Flag Target Type model.""" + + object: Literal["flag_target_type"] + """Distinguishes the Flag Target Type object.""" + id: str + """Unique identifier of the Flag Target Type.""" + slug: str + """A unique key used as the `target_type` of a Flag Target, scoped to the project.""" + created_at: datetime + """An ISO 8601 timestamp.""" + updated_at: datetime + """An ISO 8601 timestamp.""" + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "FlagTargetType": + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "flag_target_type"), + id=data["id"], + slug=data["slug"], + created_at=_parse_datetime(data["created_at"]), + updated_at=_parse_datetime(data["updated_at"]), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("FlagTargetType", e) + + def to_dict(self) -> Dict[str, Any]: + """Serialize to a dictionary.""" + result: Dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["slug"] = self.slug + result["created_at"] = _format_datetime(self.created_at) + result["updated_at"] = _format_datetime(self.updated_at) + return result diff --git a/src/workos/flag_targets/__init__.py b/src/workos/flag_targets/__init__.py new file mode 100644 index 00000000..0949c9b5 --- /dev/null +++ b/src/workos/flag_targets/__init__.py @@ -0,0 +1,4 @@ +# This file is auto-generated by oagen. Do not edit. + +from ._resource import FlagTargets as FlagTargets, AsyncFlagTargets as AsyncFlagTargets +from .models import * diff --git a/src/workos/flag_targets/_resource.py b/src/workos/flag_targets/_resource.py new file mode 100644 index 00000000..13199974 --- /dev/null +++ b/src/workos/flag_targets/_resource.py @@ -0,0 +1,335 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union + +if TYPE_CHECKING: + from .._client import AsyncWorkOSClient, WorkOSClient + +from .._types import RequestOptions, enum_value +from .models import FlagTarget +from workos.common.models.pagination_order import PaginationOrder +from .._pagination import AsyncPage, SyncPage + + +class FlagTargets: + """Flag Targets API resources.""" + + def __init__(self, client: "WorkOSClient") -> None: + self._client = client + + def list_flag_targets( + self, + *, + limit: Optional[int] = None, + before: Optional[str] = None, + after: Optional[str] = None, + order: Optional[Union[PaginationOrder, str]] = "desc", + flag_slug: Optional[str] = None, + target_type: Optional[str] = None, + target_id: Optional[str] = None, + request_options: Optional[RequestOptions] = None, + ) -> SyncPage[FlagTarget]: + """List flag targets + + Get a list of the Flag Targets in the current environment, optionally filtered by Feature Flag slug, target type, or targeted entity. + + Args: + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. + order: Order the results by the creation time. Defaults to `desc`. + flag_slug: Filter Flag Targets by the slug of the Feature Flag they belong to. + target_type: Filter Flag Targets by the type of the targeted entity, to find every Feature Flag targeting entities of that type. + target_id: Filter Flag Targets by the ID of the targeted entity, to find every Feature Flag targeting it. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + SyncPage[FlagTarget] + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + "flag_slug": flag_slug, + "target_type": target_type, + "target_id": target_id, + }.items() + if v is not None + } + return self._client.request_page( + method="get", + path=("flag_targets",), + model=FlagTarget, + params=params, + request_options=request_options, + ) + + def create_flag_target( + self, + *, + flag_slug: str, + target_type: str, + target_id: str, + request_options: Optional[RequestOptions] = None, + ) -> FlagTarget: + """Create a flag target + + Enable a Feature Flag for a target in the current environment. The `target_type` is given explicitly: a custom target type has to be registered with `POST /flag_target_types` first, and its `target_id` is an identifier from your own domain model, which carries no prefix to infer a type from. + + Args: + flag_slug: The slug of the Feature Flag to target. + target_type: The type of the target: `organization`, `user`, or the slug of a Flag Target Type registered for the project. + target_id: The ID of the entity to target. For the built-in target types this is a WorkOS Organization or User ID; for a custom target type it is an identifier from your own domain model. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + FlagTarget + + Raises: + BadRequestError: If the request is malformed (400). + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: Dict[str, Any] = { + "flag_slug": flag_slug, + "target_type": target_type, + "target_id": target_id, + } + return self._client.request( + method="post", + path=("flag_targets",), + body=body, + model=FlagTarget, + request_options=request_options, + ) + + def get_flag_target( + self, + id: str, + *, + request_options: Optional[RequestOptions] = None, + ) -> FlagTarget: + """Get a flag target + + Get the details of an existing Flag Target. + + Args: + id: Unique identifier of the Flag Target. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + FlagTarget + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return self._client.request( + method="get", + path=("flag_targets", str(id)), + model=FlagTarget, + request_options=request_options, + ) + + def delete_flag_target( + self, + id: str, + *, + request_options: Optional[RequestOptions] = None, + ) -> None: + """Delete a flag target + + Disable a Feature Flag for a target by deleting the Flag Target. + + Args: + id: Unique identifier of the Flag Target. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + self._client.request( + method="delete", + path=("flag_targets", str(id)), + request_options=request_options, + ) + + +class AsyncFlagTargets: + """Flag Targets API resources (async).""" + + def __init__(self, client: "AsyncWorkOSClient") -> None: + self._client = client + + async def list_flag_targets( + self, + *, + limit: Optional[int] = None, + before: Optional[str] = None, + after: Optional[str] = None, + order: Optional[Union[PaginationOrder, str]] = "desc", + flag_slug: Optional[str] = None, + target_type: Optional[str] = None, + target_id: Optional[str] = None, + request_options: Optional[RequestOptions] = None, + ) -> AsyncPage[FlagTarget]: + """List flag targets + + Get a list of the Flag Targets in the current environment, optionally filtered by Feature Flag slug, target type, or targeted entity. + + Args: + limit: Upper limit on the number of objects to return, between `1` and `100`. Defaults to `10`. + before: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `before="obj_123"` to fetch a new batch of objects before `"obj_123"`. + after: An object ID that defines your place in the list. When the ID is not present, you are at the end of the list. For example, if you make a list request and receive 100 objects, ending with `"obj_123"`, your subsequent call can include `after="obj_123"` to fetch a new batch of objects after `"obj_123"`. + order: Order the results by the creation time. Defaults to `desc`. + flag_slug: Filter Flag Targets by the slug of the Feature Flag they belong to. + target_type: Filter Flag Targets by the type of the targeted entity, to find every Feature Flag targeting entities of that type. + target_id: Filter Flag Targets by the ID of the targeted entity, to find every Feature Flag targeting it. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + AsyncPage[FlagTarget] + + Raises: + BadRequestError: If the request is malformed (400). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + params = { + k: v + for k, v in { + "limit": limit, + "before": before, + "after": after, + "order": enum_value(order) if order is not None else None, + "flag_slug": flag_slug, + "target_type": target_type, + "target_id": target_id, + }.items() + if v is not None + } + return await self._client.request_page( + method="get", + path=("flag_targets",), + model=FlagTarget, + params=params, + request_options=request_options, + ) + + async def create_flag_target( + self, + *, + flag_slug: str, + target_type: str, + target_id: str, + request_options: Optional[RequestOptions] = None, + ) -> FlagTarget: + """Create a flag target + + Enable a Feature Flag for a target in the current environment. The `target_type` is given explicitly: a custom target type has to be registered with `POST /flag_target_types` first, and its `target_id` is an identifier from your own domain model, which carries no prefix to infer a type from. + + Args: + flag_slug: The slug of the Feature Flag to target. + target_type: The type of the target: `organization`, `user`, or the slug of a Flag Target Type registered for the project. + target_id: The ID of the entity to target. For the built-in target types this is a WorkOS Organization or User ID; for a custom target type it is an identifier from your own domain model. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + FlagTarget + + Raises: + BadRequestError: If the request is malformed (400). + NotFoundError: If the resource is not found (404). + UnprocessableEntityError: If the request data is unprocessable (422). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + body: Dict[str, Any] = { + "flag_slug": flag_slug, + "target_type": target_type, + "target_id": target_id, + } + return await self._client.request( + method="post", + path=("flag_targets",), + body=body, + model=FlagTarget, + request_options=request_options, + ) + + async def get_flag_target( + self, + id: str, + *, + request_options: Optional[RequestOptions] = None, + ) -> FlagTarget: + """Get a flag target + + Get the details of an existing Flag Target. + + Args: + id: Unique identifier of the Flag Target. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Returns: + FlagTarget + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + return await self._client.request( + method="get", + path=("flag_targets", str(id)), + model=FlagTarget, + request_options=request_options, + ) + + async def delete_flag_target( + self, + id: str, + *, + request_options: Optional[RequestOptions] = None, + ) -> None: + """Delete a flag target + + Disable a Feature Flag for a target by deleting the Flag Target. + + Args: + id: Unique identifier of the Flag Target. + request_options: Per-request options. Supports extra_headers, timeout, max_retries, and base_url override. + + Raises: + NotFoundError: If the resource is not found (404). + AuthenticationError: If the API key is invalid (401). + RateLimitExceededError: If rate limited (429). + ServerError: If the server returns a 5xx error. + """ + await self._client.request( + method="delete", + path=("flag_targets", str(id)), + request_options=request_options, + ) diff --git a/src/workos/flag_targets/models/__init__.py b/src/workos/flag_targets/models/__init__.py new file mode 100644 index 00000000..ca37597c --- /dev/null +++ b/src/workos/flag_targets/models/__init__.py @@ -0,0 +1,4 @@ +# This file is auto-generated by oagen. Do not edit. + +from .create_flag_target import CreateFlagTarget as CreateFlagTarget +from .flag_target import FlagTarget as FlagTarget diff --git a/src/workos/flag_targets/models/create_flag_target.py b/src/workos/flag_targets/models/create_flag_target.py new file mode 100644 index 00000000..c5c09e2e --- /dev/null +++ b/src/workos/flag_targets/models/create_flag_target.py @@ -0,0 +1,39 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Dict +from workos._types import _raise_deserialize_error + + +@dataclass(slots=True) +class CreateFlagTarget: + """Create Flag Target model.""" + + flag_slug: str + """The slug of the Feature Flag to target.""" + target_type: str + """The type of the target: `organization`, `user`, or the slug of a Flag Target Type registered for the project.""" + target_id: str + """The ID of the entity to target. For the built-in target types this is a WorkOS Organization or User ID; for a custom target type it is an identifier from your own domain model.""" + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "CreateFlagTarget": + """Deserialize from a dictionary.""" + try: + return cls( + flag_slug=data["flag_slug"], + target_type=data["target_type"], + target_id=data["target_id"], + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("CreateFlagTarget", e) + + def to_dict(self) -> Dict[str, Any]: + """Serialize to a dictionary.""" + result: Dict[str, Any] = {} + result["flag_slug"] = self.flag_slug + result["target_type"] = self.target_type + result["target_id"] = self.target_id + return result diff --git a/src/workos/flag_targets/models/flag_target.py b/src/workos/flag_targets/models/flag_target.py new file mode 100644 index 00000000..580ef5a2 --- /dev/null +++ b/src/workos/flag_targets/models/flag_target.py @@ -0,0 +1,73 @@ +# This file is auto-generated by oagen. Do not edit. + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Dict, Literal +from workos._types import _raise_deserialize_error +from workos._types import _format_datetime, _parse_datetime + + +@dataclass(slots=True) +class FlagTarget: + """Flag Target model.""" + + object: Literal["flag_target"] + """Distinguishes the Flag Target object.""" + id: str + """Unique identifier of the Flag Target.""" + flag_id: str + """The ID of the Feature Flag the target belongs to.""" + flag_slug: str + """The slug of the Feature Flag the target belongs to, which is how the Feature Flag endpoints address it.""" + environment_id: str + """The ID of the environment the Flag Target belongs to.""" + target_type: str + """The type of the target: `organization`, `user`, or a custom target type slug.""" + target_id: str + """The ID of the targeted entity.""" + value_type: Literal["boolean"] + """The type of `value`. Only `boolean` is supported today; additional types are reserved for future multivariate Feature Flags.""" + value: bool + """The value the Feature Flag evaluates to for this target.""" + created_at: datetime + """An ISO 8601 timestamp.""" + updated_at: datetime + """An ISO 8601 timestamp.""" + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "FlagTarget": + """Deserialize from a dictionary.""" + try: + return cls( + object=data.get("object", "flag_target"), + id=data["id"], + flag_id=data["flag_id"], + flag_slug=data["flag_slug"], + environment_id=data["environment_id"], + target_type=data["target_type"], + target_id=data["target_id"], + value_type=data.get("value_type", "boolean"), + value=data["value"], + created_at=_parse_datetime(data["created_at"]), + updated_at=_parse_datetime(data["updated_at"]), + ) + except (KeyError, ValueError) as e: + _raise_deserialize_error("FlagTarget", e) + + def to_dict(self) -> Dict[str, Any]: + """Serialize to a dictionary.""" + result: Dict[str, Any] = {} + result["object"] = self.object + result["id"] = self.id + result["flag_id"] = self.flag_id + result["flag_slug"] = self.flag_slug + result["environment_id"] = self.environment_id + result["target_type"] = self.target_type + result["target_id"] = self.target_id + result["value_type"] = self.value_type + result["value"] = self.value + result["created_at"] = _format_datetime(self.created_at) + result["updated_at"] = _format_datetime(self.updated_at) + return result diff --git a/src/workos/types/flag_target_types/__init__.py b/src/workos/types/flag_target_types/__init__.py new file mode 100644 index 00000000..9a89427a --- /dev/null +++ b/src/workos/types/flag_target_types/__init__.py @@ -0,0 +1,3 @@ +# This file is auto-generated by oagen. Do not edit. + +from workos.flag_target_types.models import * # noqa: F401,F403 diff --git a/src/workos/types/flag_targets/__init__.py b/src/workos/types/flag_targets/__init__.py new file mode 100644 index 00000000..54a43732 --- /dev/null +++ b/src/workos/types/flag_targets/__init__.py @@ -0,0 +1,3 @@ +# This file is auto-generated by oagen. Do not edit. + +from workos.flag_targets.models import * # noqa: F401,F403 diff --git a/tests/fixtures/create_flag_target.json b/tests/fixtures/create_flag_target.json new file mode 100644 index 00000000..34d48863 --- /dev/null +++ b/tests/fixtures/create_flag_target.json @@ -0,0 +1,5 @@ +{ + "flag_slug": "new-checkout", + "target_type": "organization", + "target_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY" +} diff --git a/tests/fixtures/create_flag_target_type.json b/tests/fixtures/create_flag_target_type.json new file mode 100644 index 00000000..2d0e12a1 --- /dev/null +++ b/tests/fixtures/create_flag_target_type.json @@ -0,0 +1,3 @@ +{ + "slug": "workspace" +} diff --git a/tests/fixtures/flag_target.json b/tests/fixtures/flag_target.json new file mode 100644 index 00000000..103f11ff --- /dev/null +++ b/tests/fixtures/flag_target.json @@ -0,0 +1,13 @@ +{ + "object": "flag_target", + "id": "flag_target_01EHZNVPK3SFK441A1RGBFSHRT", + "flag_id": "flag_01EHZNVPK3SFK441A1RGBFSHRT", + "flag_slug": "new-checkout", + "environment_id": "environment_01EHWNCE74X7JSDV0X3SZ3KJNY", + "target_type": "organization", + "target_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "value_type": "boolean", + "value": true, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/flag_target_type.json b/tests/fixtures/flag_target_type.json new file mode 100644 index 00000000..88777edd --- /dev/null +++ b/tests/fixtures/flag_target_type.json @@ -0,0 +1,7 @@ +{ + "object": "flag_target_type", + "id": "flag_target_type_01EHZNVPK3SFK441A1RGBFSHRT", + "slug": "workspace", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/tests/fixtures/list_flag_target.json b/tests/fixtures/list_flag_target.json new file mode 100644 index 00000000..1bab753b --- /dev/null +++ b/tests/fixtures/list_flag_target.json @@ -0,0 +1,21 @@ +{ + "data": [ + { + "object": "flag_target", + "id": "flag_target_01EHZNVPK3SFK441A1RGBFSHRT", + "flag_id": "flag_01EHZNVPK3SFK441A1RGBFSHRT", + "flag_slug": "new-checkout", + "environment_id": "environment_01EHWNCE74X7JSDV0X3SZ3KJNY", + "target_type": "organization", + "target_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "value_type": "boolean", + "value": true, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + } + ], + "list_metadata": { + "before": null, + "after": null + } +} diff --git a/tests/fixtures/list_flag_target_type.json b/tests/fixtures/list_flag_target_type.json new file mode 100644 index 00000000..da42ebb0 --- /dev/null +++ b/tests/fixtures/list_flag_target_type.json @@ -0,0 +1,15 @@ +{ + "data": [ + { + "object": "flag_target_type", + "id": "flag_target_type_01EHZNVPK3SFK441A1RGBFSHRT", + "slug": "workspace", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + } + ], + "list_metadata": { + "before": null, + "after": null + } +} diff --git a/tests/test_flag_target_types.py b/tests/test_flag_target_types.py new file mode 100644 index 00000000..bf650c18 --- /dev/null +++ b/tests/test_flag_target_types.py @@ -0,0 +1,290 @@ +# This file is auto-generated by oagen. Do not edit. + +import json + +import pytest +from workos import WorkOSClient, AsyncWorkOSClient +from tests.generated_helpers import load_fixture + +from workos.common.models import PaginationOrder +from workos.flag_target_types.models import FlagTargetType +from workos._pagination import AsyncPage, SyncPage +from workos._errors import ( + AuthenticationError, + BadRequestError, + NotFoundError, + RateLimitExceededError, + ServerError, + UnprocessableEntityError, +) + + +class TestFlagTargetTypes: + def test_list_flag_target_types(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("list_flag_target_type.json"), + ) + page = workos.flag_target_types.list_flag_target_types() + assert isinstance(page, SyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], FlagTargetType) + + def test_list_flag_target_types_empty_page(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = workos.flag_target_types.list_flag_target_types() + assert isinstance(page, SyncPage) + assert page.data == [] + + def test_list_flag_target_types_encodes_query_params(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + workos.flag_target_types.list_flag_target_types( + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + + def test_create_flag_target_type(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("flag_target_type.json"), + ) + result = workos.flag_target_types.create_flag_target_type(slug="test_slug") + assert isinstance(result, FlagTargetType) + assert result.object == "flag_target_type" + assert result.id == "flag_target_type_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/flag_target_types") + body = json.loads(request.content) + assert body["slug"] == "test_slug" + + def test_get_flag_target_type(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("flag_target_type.json"), + ) + result = workos.flag_target_types.get_flag_target_type("test_id") + assert isinstance(result, FlagTargetType) + assert result.object == "flag_target_type" + assert result.id == "flag_target_type_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/flag_target_types/test_id") + + def test_list_flag_target_types_with_request_options(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + workos.flag_target_types.list_flag_target_types( + request_options={"extra_headers": {"X-Custom": "value"}} + ) + request = httpx_mock.get_request() + assert request.headers["X-Custom"] == "value" + + def test_list_flag_target_types_unauthorized(self, workos, httpx_mock): + httpx_mock.add_response( + status_code=401, + json={"message": "Unauthorized"}, + ) + with pytest.raises(AuthenticationError): + workos.flag_target_types.list_flag_target_types() + + def test_list_flag_target_types_not_found(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=404, json={"message": "Not found"}) + with pytest.raises(NotFoundError): + workos.flag_target_types.list_flag_target_types() + finally: + workos.close() + + def test_list_flag_target_types_rate_limited(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response( + status_code=429, + headers={"Retry-After": "0"}, + json={"message": "Slow down"}, + ) + with pytest.raises(RateLimitExceededError): + workos.flag_target_types.list_flag_target_types() + finally: + workos.close() + + def test_list_flag_target_types_server_error(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=500, json={"message": "Server error"}) + with pytest.raises(ServerError): + workos.flag_target_types.list_flag_target_types() + finally: + workos.close() + + def test_list_flag_target_types_bad_request(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=400, json={"message": "Bad request"}) + with pytest.raises(BadRequestError): + workos.flag_target_types.list_flag_target_types() + finally: + workos.close() + + def test_list_flag_target_types_unprocessable(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=422, json={"message": "Unprocessable"}) + with pytest.raises(UnprocessableEntityError): + workos.flag_target_types.list_flag_target_types() + finally: + workos.close() + + +class TestAsyncFlagTargetTypes: + @pytest.mark.asyncio + async def test_list_flag_target_types(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("list_flag_target_type.json")) + page = await async_workos.flag_target_types.list_flag_target_types() + assert isinstance(page, AsyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], FlagTargetType) + + @pytest.mark.asyncio + async def test_list_flag_target_types_empty_page(self, async_workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = await async_workos.flag_target_types.list_flag_target_types() + assert isinstance(page, AsyncPage) + assert page.data == [] + + @pytest.mark.asyncio + async def test_list_flag_target_types_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + await async_workos.flag_target_types.list_flag_target_types( + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + + @pytest.mark.asyncio + async def test_create_flag_target_type(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("flag_target_type.json")) + result = await async_workos.flag_target_types.create_flag_target_type( + slug="test_slug" + ) + assert isinstance(result, FlagTargetType) + assert result.object == "flag_target_type" + assert result.id == "flag_target_type_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/flag_target_types") + + @pytest.mark.asyncio + async def test_get_flag_target_type(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("flag_target_type.json")) + result = await async_workos.flag_target_types.get_flag_target_type("test_id") + assert isinstance(result, FlagTargetType) + assert result.object == "flag_target_type" + assert result.id == "flag_target_type_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/flag_target_types/test_id") + + @pytest.mark.asyncio + async def test_list_flag_target_types_with_request_options( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + await async_workos.flag_target_types.list_flag_target_types( + request_options={"extra_headers": {"X-Custom": "value"}} + ) + request = httpx_mock.get_request() + assert request.headers["X-Custom"] == "value" + + @pytest.mark.asyncio + async def test_list_flag_target_types_unauthorized(self, async_workos, httpx_mock): + httpx_mock.add_response(status_code=401, json={"message": "Unauthorized"}) + with pytest.raises(AuthenticationError): + await async_workos.flag_target_types.list_flag_target_types() + + @pytest.mark.asyncio + async def test_list_flag_target_types_not_found(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=404, json={"message": "Not found"}) + with pytest.raises(NotFoundError): + await workos.flag_target_types.list_flag_target_types() + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_list_flag_target_types_rate_limited(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response( + status_code=429, + headers={"Retry-After": "0"}, + json={"message": "Slow down"}, + ) + with pytest.raises(RateLimitExceededError): + await workos.flag_target_types.list_flag_target_types() + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_list_flag_target_types_server_error(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=500, json={"message": "Server error"}) + with pytest.raises(ServerError): + await workos.flag_target_types.list_flag_target_types() + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_list_flag_target_types_bad_request(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=400, json={"message": "Bad request"}) + with pytest.raises(BadRequestError): + await workos.flag_target_types.list_flag_target_types() + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_list_flag_target_types_unprocessable(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=422, json={"message": "Unprocessable"}) + with pytest.raises(UnprocessableEntityError): + await workos.flag_target_types.list_flag_target_types() + finally: + await workos.close() diff --git a/tests/test_flag_target_types_models_round_trip.py b/tests/test_flag_target_types_models_round_trip.py new file mode 100644 index 00000000..8efc263a --- /dev/null +++ b/tests/test_flag_target_types_models_round_trip.py @@ -0,0 +1,33 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Model round-trip tests: from_dict(to_dict()) preserves data.""" + +from tests.generated_helpers import load_fixture + +from workos.flag_target_types.models import FlagTargetType + + +class TestModelRoundTrip: + def test_flag_target_type_round_trip(self): + data = load_fixture("flag_target_type.json") + instance = FlagTargetType.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = FlagTargetType.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_flag_target_type_minimal_payload(self): + data = { + "object": "flag_target_type", + "id": "flag_target_type_01EHZNVPK3SFK441A1RGBFSHRT", + "slug": "workspace", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = FlagTargetType.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["slug"] == data["slug"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"] diff --git a/tests/test_flag_targets.py b/tests/test_flag_targets.py new file mode 100644 index 00000000..b3b86627 --- /dev/null +++ b/tests/test_flag_targets.py @@ -0,0 +1,327 @@ +# This file is auto-generated by oagen. Do not edit. + +import json + +import pytest +from workos import WorkOSClient, AsyncWorkOSClient +from tests.generated_helpers import load_fixture + +from workos.common.models import PaginationOrder +from workos.flag_targets.models import FlagTarget +from workos._pagination import AsyncPage, SyncPage +from workos._errors import ( + AuthenticationError, + BadRequestError, + NotFoundError, + RateLimitExceededError, + ServerError, + UnprocessableEntityError, +) + + +class TestFlagTargets: + def test_list_flag_targets(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("list_flag_target.json"), + ) + page = workos.flag_targets.list_flag_targets() + assert isinstance(page, SyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], FlagTarget) + + def test_list_flag_targets_empty_page(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = workos.flag_targets.list_flag_targets() + assert isinstance(page, SyncPage) + assert page.data == [] + + def test_list_flag_targets_encodes_query_params(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + workos.flag_targets.list_flag_targets( + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + flag_slug="value flag_slug/test", + target_type="value target_type/test", + target_id="value target_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + assert request.url.params["flag_slug"] == "value flag_slug/test" + assert request.url.params["target_type"] == "value target_type/test" + assert request.url.params["target_id"] == "value target_id/test" + + def test_create_flag_target(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("flag_target.json"), + ) + result = workos.flag_targets.create_flag_target( + flag_slug="test_flag_slug", + target_type="test_target_type", + target_id="test_target_id", + ) + assert isinstance(result, FlagTarget) + assert result.object == "flag_target" + assert result.id == "flag_target_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/flag_targets") + body = json.loads(request.content) + assert body["flag_slug"] == "test_flag_slug" + assert body["target_type"] == "test_target_type" + assert body["target_id"] == "test_target_id" + + def test_get_flag_target(self, workos, httpx_mock): + httpx_mock.add_response( + json=load_fixture("flag_target.json"), + ) + result = workos.flag_targets.get_flag_target("test_id") + assert isinstance(result, FlagTarget) + assert result.object == "flag_target" + assert result.id == "flag_target_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/flag_targets/test_id") + + def test_delete_flag_target(self, workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = workos.flag_targets.delete_flag_target("test_id") + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith("/flag_targets/test_id") + + def test_list_flag_targets_with_request_options(self, workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + workos.flag_targets.list_flag_targets( + request_options={"extra_headers": {"X-Custom": "value"}} + ) + request = httpx_mock.get_request() + assert request.headers["X-Custom"] == "value" + + def test_list_flag_targets_unauthorized(self, workos, httpx_mock): + httpx_mock.add_response( + status_code=401, + json={"message": "Unauthorized"}, + ) + with pytest.raises(AuthenticationError): + workos.flag_targets.list_flag_targets() + + def test_list_flag_targets_not_found(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=404, json={"message": "Not found"}) + with pytest.raises(NotFoundError): + workos.flag_targets.list_flag_targets() + finally: + workos.close() + + def test_list_flag_targets_rate_limited(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response( + status_code=429, + headers={"Retry-After": "0"}, + json={"message": "Slow down"}, + ) + with pytest.raises(RateLimitExceededError): + workos.flag_targets.list_flag_targets() + finally: + workos.close() + + def test_list_flag_targets_server_error(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=500, json={"message": "Server error"}) + with pytest.raises(ServerError): + workos.flag_targets.list_flag_targets() + finally: + workos.close() + + def test_list_flag_targets_bad_request(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=400, json={"message": "Bad request"}) + with pytest.raises(BadRequestError): + workos.flag_targets.list_flag_targets() + finally: + workos.close() + + def test_list_flag_targets_unprocessable(self, httpx_mock): + workos = WorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=422, json={"message": "Unprocessable"}) + with pytest.raises(UnprocessableEntityError): + workos.flag_targets.list_flag_targets() + finally: + workos.close() + + +class TestAsyncFlagTargets: + @pytest.mark.asyncio + async def test_list_flag_targets(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("list_flag_target.json")) + page = await async_workos.flag_targets.list_flag_targets() + assert isinstance(page, AsyncPage) + assert len(page.data) == 1 + assert isinstance(page.data[0], FlagTarget) + + @pytest.mark.asyncio + async def test_list_flag_targets_empty_page(self, async_workos, httpx_mock): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + page = await async_workos.flag_targets.list_flag_targets() + assert isinstance(page, AsyncPage) + assert page.data == [] + + @pytest.mark.asyncio + async def test_list_flag_targets_encodes_query_params( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + await async_workos.flag_targets.list_flag_targets( + limit=10, + before="cursor before", + after="cursor/after", + order=PaginationOrder("value_order"), + flag_slug="value flag_slug/test", + target_type="value target_type/test", + target_id="value target_id/test", + ) + request = httpx_mock.get_request() + assert request.url.params["limit"] == "10" + assert request.url.params["before"] == "cursor before" + assert request.url.params["after"] == "cursor/after" + assert request.url.params["order"] == "value_order" + assert request.url.params["flag_slug"] == "value flag_slug/test" + assert request.url.params["target_type"] == "value target_type/test" + assert request.url.params["target_id"] == "value target_id/test" + + @pytest.mark.asyncio + async def test_create_flag_target(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("flag_target.json")) + result = await async_workos.flag_targets.create_flag_target( + flag_slug="test_flag_slug", + target_type="test_target_type", + target_id="test_target_id", + ) + assert isinstance(result, FlagTarget) + assert result.object == "flag_target" + assert result.id == "flag_target_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "POST" + assert request.url.path.endswith("/flag_targets") + + @pytest.mark.asyncio + async def test_get_flag_target(self, async_workos, httpx_mock): + httpx_mock.add_response(json=load_fixture("flag_target.json")) + result = await async_workos.flag_targets.get_flag_target("test_id") + assert isinstance(result, FlagTarget) + assert result.object == "flag_target" + assert result.id == "flag_target_01EHZNVPK3SFK441A1RGBFSHRT" + request = httpx_mock.get_request() + assert request.method == "GET" + assert request.url.path.endswith("/flag_targets/test_id") + + @pytest.mark.asyncio + async def test_delete_flag_target(self, async_workos, httpx_mock): + httpx_mock.add_response(status_code=204) + result = await async_workos.flag_targets.delete_flag_target("test_id") + assert result is None + request = httpx_mock.get_request() + assert request.method == "DELETE" + assert request.url.path.endswith("/flag_targets/test_id") + + @pytest.mark.asyncio + async def test_list_flag_targets_with_request_options( + self, async_workos, httpx_mock + ): + httpx_mock.add_response(json={"data": [], "list_metadata": {}}) + await async_workos.flag_targets.list_flag_targets( + request_options={"extra_headers": {"X-Custom": "value"}} + ) + request = httpx_mock.get_request() + assert request.headers["X-Custom"] == "value" + + @pytest.mark.asyncio + async def test_list_flag_targets_unauthorized(self, async_workos, httpx_mock): + httpx_mock.add_response(status_code=401, json={"message": "Unauthorized"}) + with pytest.raises(AuthenticationError): + await async_workos.flag_targets.list_flag_targets() + + @pytest.mark.asyncio + async def test_list_flag_targets_not_found(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=404, json={"message": "Not found"}) + with pytest.raises(NotFoundError): + await workos.flag_targets.list_flag_targets() + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_list_flag_targets_rate_limited(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response( + status_code=429, + headers={"Retry-After": "0"}, + json={"message": "Slow down"}, + ) + with pytest.raises(RateLimitExceededError): + await workos.flag_targets.list_flag_targets() + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_list_flag_targets_server_error(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=500, json={"message": "Server error"}) + with pytest.raises(ServerError): + await workos.flag_targets.list_flag_targets() + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_list_flag_targets_bad_request(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=400, json={"message": "Bad request"}) + with pytest.raises(BadRequestError): + await workos.flag_targets.list_flag_targets() + finally: + await workos.close() + + @pytest.mark.asyncio + async def test_list_flag_targets_unprocessable(self, httpx_mock): + workos = AsyncWorkOSClient( + api_key="sk_test_123", client_id="client_test", max_retries=0 + ) + try: + httpx_mock.add_response(status_code=422, json={"message": "Unprocessable"}) + with pytest.raises(UnprocessableEntityError): + await workos.flag_targets.list_flag_targets() + finally: + await workos.close() diff --git a/tests/test_flag_targets_models_round_trip.py b/tests/test_flag_targets_models_round_trip.py new file mode 100644 index 00000000..6eae1739 --- /dev/null +++ b/tests/test_flag_targets_models_round_trip.py @@ -0,0 +1,45 @@ +# This file is auto-generated by oagen. Do not edit. + +"""Model round-trip tests: from_dict(to_dict()) preserves data.""" + +from tests.generated_helpers import load_fixture + +from workos.flag_targets.models import FlagTarget + + +class TestModelRoundTrip: + def test_flag_target_round_trip(self): + data = load_fixture("flag_target.json") + instance = FlagTarget.from_dict(data) + serialized = instance.to_dict() + assert serialized == data + restored = FlagTarget.from_dict(serialized) + assert restored.to_dict() == serialized + + def test_flag_target_minimal_payload(self): + data = { + "object": "flag_target", + "id": "flag_target_01EHZNVPK3SFK441A1RGBFSHRT", + "flag_id": "flag_01EHZNVPK3SFK441A1RGBFSHRT", + "flag_slug": "new-checkout", + "environment_id": "environment_01EHWNCE74X7JSDV0X3SZ3KJNY", + "target_type": "organization", + "target_id": "org_01EHWNCE74X7JSDV0X3SZ3KJNY", + "value_type": "boolean", + "value": True, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + } + instance = FlagTarget.from_dict(data) + serialized = instance.to_dict() + assert serialized["object"] == data["object"] + assert serialized["id"] == data["id"] + assert serialized["flag_id"] == data["flag_id"] + assert serialized["flag_slug"] == data["flag_slug"] + assert serialized["environment_id"] == data["environment_id"] + assert serialized["target_type"] == data["target_type"] + assert serialized["target_id"] == data["target_id"] + assert serialized["value_type"] == data["value_type"] + assert serialized["value"] == data["value"] + assert serialized["created_at"] == data["created_at"] + assert serialized["updated_at"] == data["updated_at"]