Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ab35617
adding base types and module registraiton
swaroopAkkineniWorkos Feb 19, 2026
5f6892d
moar
swaroopAkkineniWorkos Feb 19, 2026
0bc40fb
lol
swaroopAkkineniWorkos Feb 20, 2026
526eb7d
lol
swaroopAkkineniWorkos Feb 20, 2026
13dad51
nits for re-dupe OrganizationMembershipStatus
swaroopAkkineniWorkos Feb 23, 2026
ff239c7
Format
swaroopAkkineniWorkos Feb 23, 2026
a5823c6
adding AUTHORIZATION_RESOURCES_PATH to base
swaroopAkkineniWorkos Feb 23, 2026
8dd434f
FGA_1: create/delete/get/update resource (#563)
swaroopAkkineniWorkos Feb 24, 2026
fe59018
FGA_2: listResources(), get/update/delete resource_by_external_id (#569)
swaroopAkkineniWorkos Feb 24, 2026
a50f5d5
FGA_3: check() (#568)
swaroopAkkineniWorkos Feb 24, 2026
3eca552
remove organization_name for list
swaroopAkkineniWorkos Feb 24, 2026
fa170f0
casade
swaroopAkkineniWorkos Feb 24, 2026
035f1c3
lol
swaroopAkkineniWorkos Feb 24, 2026
8fbbc5f
moar
swaroopAkkineniWorkos Feb 25, 2026
7342ad4
Create
swaroopAkkineniWorkos Feb 25, 2026
3f25161
test update
swaroopAkkineniWorkos Feb 25, 2026
dfca291
break outp
swaroopAkkineniWorkos Feb 25, 2026
5f72f0d
lol
swaroopAkkineniWorkos Feb 26, 2026
a7c6d28
moar
swaroopAkkineniWorkos Feb 26, 2026
0111cde
moar
swaroopAkkineniWorkos Feb 26, 2026
ba59cf6
merge conflcits
swaroopAkkineniWorkos Mar 2, 2026
6b8ca59
refactor authorization resource
swaroopAkkineniWorkos Mar 2, 2026
f04c33e
Test updates
swaroopAkkineniWorkos Mar 2, 2026
778aa67
get_resource
swaroopAkkineniWorkos Mar 2, 2026
dcd8bda
create
swaroopAkkineniWorkos Mar 2, 2026
070c2ca
Get changes
swaroopAkkineniWorkos Mar 2, 2026
58564df
delete
swaroopAkkineniWorkos Mar 2, 2026
a71ebc6
list
swaroopAkkineniWorkos Mar 2, 2026
2199f79
get_resource_by_external_id
swaroopAkkineniWorkos Mar 2, 2026
d3e3c79
update_resource_by_external_id
swaroopAkkineniWorkos Mar 2, 2026
3127241
delete_resource_by_external_id
swaroopAkkineniWorkos Mar 2, 2026
a554548
check
swaroopAkkineniWorkos Mar 2, 2026
d7c4f7e
Cleaner tests
swaroopAkkineniWorkos Mar 2, 2026
5211752
nits
swaroopAkkineniWorkos Mar 2, 2026
a6d1db9
lint
swaroopAkkineniWorkos Mar 2, 2026
17c754c
type fix
swaroopAkkineniWorkos Mar 2, 2026
7c96c59
type fix
swaroopAkkineniWorkos Mar 2, 2026
954bb37
type fix
swaroopAkkineniWorkos Mar 2, 2026
0e5eaab
update
swaroopAkkineniWorkos Mar 2, 2026
87eb732
final nit
swaroopAkkineniWorkos Mar 2, 2026
01e567d
FGA_4: list/assign/remove role, remove roleAssginment (#570)
swaroopAkkineniWorkos Mar 4, 2026
0ef43e8
Merge branch 'main' into ENT-5224-python-sdk-for-fga-worktree-fuck-ar…
swaroopAkkineniWorkos Mar 4, 2026
08f1c84
feat: FGA_5 list_resources_for_membership, list_memberships_for_resou…
swaroopAkkineniWorkos Mar 4, 2026
aa664d9
lol
swaroopAkkineniWorkos Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/workos/types/authorization/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
from workos.types.authorization.access_evaluation import AccessEvaluation
from workos.types.authorization.environment_role import (
EnvironmentRole,
EnvironmentRoleList,
)
from workos.types.authorization.organization_membership import (
AuthorizationOrganizationMembership,
)
from workos.types.authorization.organization_role import (
OrganizationRole,
OrganizationRoleEvent,
OrganizationRoleList,
)
from workos.types.authorization.permission import Permission
from workos.types.authorization.resource import Resource
from workos.types.authorization.role import (
Role,
RoleList,
)
from workos.types.authorization.role_assignment import (
RoleAssignment,
RoleAssignmentResource,
RoleAssignmentRole,
)
7 changes: 7 additions & 0 deletions src/workos/types/authorization/access_evaluation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from workos.types.workos_model import WorkOSModel


class AccessEvaluation(WorkOSModel):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just call this a CheckResponse?

Copy link
Copy Markdown
Contributor Author

@swaroopAkkineniWorkos swaroopAkkineniWorkos Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated the name to AccessCheckResponse

"""Representation of a WorkOS Authorization access check result."""

authorized: bool
26 changes: 26 additions & 0 deletions src/workos/types/authorization/organization_membership.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import Any, Literal, Mapping, Optional

from workos.types.workos_model import WorkOSModel
from workos.typing.literals import LiteralOrUntyped

OrganizationMembershipStatus = Literal["active", "inactive", "pending"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated OrganizationMembershipStatus literal

This exact same Literal["active", "inactive", "pending"] type alias is already defined in src/workos/types/user_management/organization_membership.py:7. Consider importing from a shared location or re-exporting from one module to the other to avoid the definitions drifting apart over time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pushed up a change where I moved "OrganizationMembershipStatus" to it's own file to be imported



class AuthorizationOrganizationMembership(WorkOSModel):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a type in the organization memberships package that we can reuse?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For instance create a base type like we did for the node package

"""Representation of an Organization Membership returned by Authorization endpoints.

This is a separate type from the user_management OrganizationMembership because
authorization endpoints return memberships without the ``role`` field and include
``organization_name``. Additionally, ``custom_attributes`` is optional here as
authorization endpoints may omit it.
"""

object: Literal["organization_membership"]
id: str
user_id: str
organization_id: str
organization_name: str
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we don't include organization_name in the responses where this gets used. For instance listOrganizationMembershipsForResource calls serializeBase, which doesn't include organization_name.

status: LiteralOrUntyped[OrganizationMembershipStatus]
custom_attributes: Optional[Mapping[str, Any]] = None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

authz doesn't return custom attributes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will remove

created_at: str
updated_at: str
18 changes: 18 additions & 0 deletions src/workos/types/authorization/resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from typing import Literal, Optional

from workos.types.workos_model import WorkOSModel


class Resource(WorkOSModel):
"""Representation of an Authorization Resource."""

object: Literal["authorization_resource"]
id: str
external_id: str
name: str
description: Optional[str] = None
resource_type_slug: str
organization_id: str
parent_resource_id: Optional[str] = None
created_at: str
updated_at: str
22 changes: 22 additions & 0 deletions src/workos/types/authorization/role_assignment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import Literal

from workos.types.workos_model import WorkOSModel


class RoleAssignmentRole(WorkOSModel):
slug: str


class RoleAssignmentResource(WorkOSModel):
id: str
external_id: str
resource_type_slug: str


class RoleAssignment(WorkOSModel):
object: Literal["role_assignment"]
id: str
role: RoleAssignmentRole
resource: RoleAssignmentResource
created_at: str
updated_at: str
8 changes: 8 additions & 0 deletions src/workos/types/list_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
from typing_extensions import Required, TypedDict
from workos.types.api_keys import ApiKey
from workos.types.audit_logs import AuditLogAction, AuditLogSchema
from workos.types.authorization.organization_membership import (
AuthorizationOrganizationMembership,
)
from workos.types.authorization.permission import Permission
from workos.types.authorization.resource import Resource
from workos.types.authorization.role_assignment import RoleAssignment
from workos.types.directory_sync import (
Directory,
DirectoryGroup,
Expand Down Expand Up @@ -59,6 +64,9 @@
Organization,
OrganizationMembership,
Permission,
Resource,
RoleAssignment,
AuthorizationOrganizationMembership,
AuthorizationResource,
AuthorizationResourceType,
User,
Expand Down
8 changes: 5 additions & 3 deletions src/workos/utils/_base_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def _prepare_request(
json: JsonType = None,
headers: HeadersType = None,
exclude_default_auth_headers: bool = False,
force_include_body: bool = False,
) -> PreparedRequest:
"""Executes a request against the WorkOS API.

Expand All @@ -133,7 +134,8 @@ def _prepare_request(
method Optional[str]: One of the supported methods as defined by the REQUEST_METHOD_X constants
params Optional[dict]: Query params or body payload to be added to the request
headers Optional[dict]: Custom headers to be added to the request
token Optional[str]: Bearer token
exclude_default_auth_headers (bool): If True, excludes default auth headers from the request
force_include_body (bool): If True, allows sending a body in a bodyless request (used for DELETE requests)

Returns:
dict: Response from WorkOS
Expand All @@ -149,7 +151,7 @@ def _prepare_request(
REQUEST_METHOD_GET,
]

if bodyless_http_method and json is not None:
if bodyless_http_method and json is not None and not force_include_body:
raise ValueError(f"Cannot send a body with a {parsed_method} request")

# Remove any parameters that are None
Expand All @@ -161,7 +163,7 @@ def _prepare_request(
json = {k: v for k, v in json.items() if v is not None}

# We'll spread these return values onto the HTTP client request method
if bodyless_http_method:
if bodyless_http_method and not force_include_body:
return {
"method": parsed_method,
"url": url,
Expand Down
44 changes: 43 additions & 1 deletion src/workos/utils/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
ParamsType,
ResponseJson,
)
from workos.utils.request_helper import REQUEST_METHOD_GET
from workos.utils.request_helper import REQUEST_METHOD_DELETE, REQUEST_METHOD_GET


class SyncHttpxClientWrapper(httpx.Client):
Expand Down Expand Up @@ -113,6 +113,27 @@ def request(
response = self._client.request(**prepared_request_parameters)
return self._handle_response(response)

def delete_with_body(
self,
path: str,
json: JsonType = None,
params: ParamsType = None,
headers: HeadersType = None,
exclude_default_auth_headers: bool = False,
) -> ResponseJson:
"""Executes a DELETE request with a JSON body against the WorkOS API."""
prepared_request_parameters = self._prepare_request(
path=path,
method=REQUEST_METHOD_DELETE,
json=json,
params=params,
headers=headers,
exclude_default_auth_headers=exclude_default_auth_headers,
force_include_body=True,
)
response = self._client.request(**prepared_request_parameters)
return self._handle_response(response)


class AsyncHttpxClientWrapper(httpx.AsyncClient):
def __del__(self) -> None:
Expand Down Expand Up @@ -210,5 +231,26 @@ async def request(
response = await self._client.request(**prepared_request_parameters)
return self._handle_response(response)

async def delete_with_body(
self,
path: str,
json: JsonType = None,
params: ParamsType = None,
headers: HeadersType = None,
exclude_default_auth_headers: bool = False,
) -> ResponseJson:
"""Executes a DELETE request with a JSON body against the WorkOS API."""
prepared_request_parameters = self._prepare_request(
path=path,
method=REQUEST_METHOD_DELETE,
json=json,
params=params,
headers=headers,
exclude_default_auth_headers=exclude_default_auth_headers,
force_include_body=True,
)
response = await self._client.request(**prepared_request_parameters)
return self._handle_response(response)


HTTPClient = Union[AsyncHTTPClient, SyncHTTPClient]
37 changes: 37 additions & 0 deletions tests/test_async_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,40 @@ async def test_request_removes_none_json_values(
json={"organization_id": None, "test": "value"},
)
assert request_kwargs["json"] == {"test": "value"}

async def test_delete_with_body_sends_json(
self, capture_and_mock_http_client_request
):
request_kwargs = capture_and_mock_http_client_request(self.http_client, {}, 200)

await self.http_client.delete_with_body(
path="/test",
json={"resource_id": "res_01ABC"},
)

assert request_kwargs["method"] == "delete"
assert request_kwargs["json"] == {"resource_id": "res_01ABC"}

async def test_delete_with_body_sends_params(
self, capture_and_mock_http_client_request
):
request_kwargs = capture_and_mock_http_client_request(self.http_client, {}, 200)

await self.http_client.delete_with_body(
path="/test",
json={"resource_id": "res_01ABC"},
params={"org_id": "org_01ABC"},
)

assert request_kwargs["params"] == {"org_id": "org_01ABC"}
assert request_kwargs["json"] == {"resource_id": "res_01ABC"}

async def test_delete_without_body_raises_value_error(self):
with pytest.raises(
ValueError, match="Cannot send a body with a delete request"
):
await self.http_client.request(
path="/test",
method="delete",
json={"should": "fail"},
)
Loading