workos 5.36.0__tar.gz → 5.38.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. {workos-5.36.0 → workos-5.38.0}/PKG-INFO +1 -1
  2. workos-5.38.0/tests/test_pipes.py +167 -0
  3. {workos-5.36.0 → workos-5.38.0}/tests/test_user_management.py +34 -0
  4. {workos-5.36.0 → workos-5.38.0}/tests/test_vault.py +28 -0
  5. {workos-5.36.0 → workos-5.38.0}/workos/__about__.py +1 -1
  6. {workos-5.36.0 → workos-5.38.0}/workos/_base_client.py +5 -0
  7. {workos-5.36.0 → workos-5.38.0}/workos/async_client.py +7 -0
  8. {workos-5.36.0 → workos-5.38.0}/workos/client.py +7 -0
  9. workos-5.38.0/workos/pipes.py +93 -0
  10. workos-5.38.0/workos/types/pipes/__init__.py +6 -0
  11. workos-5.38.0/workos/types/pipes/pipes.py +34 -0
  12. {workos-5.36.0 → workos-5.38.0}/workos/user_management.py +86 -0
  13. {workos-5.36.0 → workos-5.38.0}/workos/vault.py +29 -0
  14. {workos-5.36.0 → workos-5.38.0}/workos.egg-info/PKG-INFO +1 -1
  15. {workos-5.36.0 → workos-5.38.0}/workos.egg-info/SOURCES.txt +4 -0
  16. {workos-5.36.0 → workos-5.38.0}/LICENSE +0 -0
  17. {workos-5.36.0 → workos-5.38.0}/README.md +0 -0
  18. {workos-5.36.0 → workos-5.38.0}/setup.cfg +0 -0
  19. {workos-5.36.0 → workos-5.38.0}/setup.py +0 -0
  20. {workos-5.36.0 → workos-5.38.0}/tests/test_api_keys.py +0 -0
  21. {workos-5.36.0 → workos-5.38.0}/tests/test_async_http_client.py +0 -0
  22. {workos-5.36.0 → workos-5.38.0}/tests/test_audit_logs.py +0 -0
  23. {workos-5.36.0 → workos-5.38.0}/tests/test_client.py +0 -0
  24. {workos-5.36.0 → workos-5.38.0}/tests/test_directory_sync.py +0 -0
  25. {workos-5.36.0 → workos-5.38.0}/tests/test_events.py +0 -0
  26. {workos-5.36.0 → workos-5.38.0}/tests/test_fga.py +0 -0
  27. {workos-5.36.0 → workos-5.38.0}/tests/test_mfa.py +0 -0
  28. {workos-5.36.0 → workos-5.38.0}/tests/test_organization_domains.py +0 -0
  29. {workos-5.36.0 → workos-5.38.0}/tests/test_organizations.py +0 -0
  30. {workos-5.36.0 → workos-5.38.0}/tests/test_passwordless.py +0 -0
  31. {workos-5.36.0 → workos-5.38.0}/tests/test_portal.py +0 -0
  32. {workos-5.36.0 → workos-5.38.0}/tests/test_session.py +0 -0
  33. {workos-5.36.0 → workos-5.38.0}/tests/test_sso.py +0 -0
  34. {workos-5.36.0 → workos-5.38.0}/tests/test_sync_http_client.py +0 -0
  35. {workos-5.36.0 → workos-5.38.0}/tests/test_user_management_list_sessions.py +0 -0
  36. {workos-5.36.0 → workos-5.38.0}/tests/test_user_management_revoke_session.py +0 -0
  37. {workos-5.36.0 → workos-5.38.0}/tests/test_webhooks.py +0 -0
  38. {workos-5.36.0 → workos-5.38.0}/tests/test_widgets.py +0 -0
  39. {workos-5.36.0 → workos-5.38.0}/workos/__init__.py +0 -0
  40. {workos-5.36.0 → workos-5.38.0}/workos/_client_configuration.py +0 -0
  41. {workos-5.36.0 → workos-5.38.0}/workos/api_keys.py +0 -0
  42. {workos-5.36.0 → workos-5.38.0}/workos/audit_logs.py +0 -0
  43. {workos-5.36.0 → workos-5.38.0}/workos/directory_sync.py +0 -0
  44. {workos-5.36.0 → workos-5.38.0}/workos/events.py +0 -0
  45. {workos-5.36.0 → workos-5.38.0}/workos/exceptions.py +0 -0
  46. {workos-5.36.0 → workos-5.38.0}/workos/fga.py +0 -0
  47. {workos-5.36.0 → workos-5.38.0}/workos/mfa.py +0 -0
  48. {workos-5.36.0 → workos-5.38.0}/workos/organization_domains.py +0 -0
  49. {workos-5.36.0 → workos-5.38.0}/workos/organizations.py +0 -0
  50. {workos-5.36.0 → workos-5.38.0}/workos/passwordless.py +0 -0
  51. {workos-5.36.0 → workos-5.38.0}/workos/portal.py +0 -0
  52. {workos-5.36.0 → workos-5.38.0}/workos/py.typed +0 -0
  53. {workos-5.36.0 → workos-5.38.0}/workos/session.py +0 -0
  54. {workos-5.36.0 → workos-5.38.0}/workos/sso.py +0 -0
  55. {workos-5.36.0 → workos-5.38.0}/workos/types/__init__.py +0 -0
  56. {workos-5.36.0 → workos-5.38.0}/workos/types/api_keys/__init__.py +0 -0
  57. {workos-5.36.0 → workos-5.38.0}/workos/types/api_keys/api_keys.py +0 -0
  58. {workos-5.36.0 → workos-5.38.0}/workos/types/audit_logs/__init__.py +0 -0
  59. {workos-5.36.0 → workos-5.38.0}/workos/types/audit_logs/audit_log_event.py +0 -0
  60. {workos-5.36.0 → workos-5.38.0}/workos/types/audit_logs/audit_log_event_actor.py +0 -0
  61. {workos-5.36.0 → workos-5.38.0}/workos/types/audit_logs/audit_log_event_context.py +0 -0
  62. {workos-5.36.0 → workos-5.38.0}/workos/types/audit_logs/audit_log_event_target.py +0 -0
  63. {workos-5.36.0 → workos-5.38.0}/workos/types/audit_logs/audit_log_export.py +0 -0
  64. {workos-5.36.0 → workos-5.38.0}/workos/types/audit_logs/audit_log_metadata.py +0 -0
  65. {workos-5.36.0 → workos-5.38.0}/workos/types/directory_sync/__init__.py +0 -0
  66. {workos-5.36.0 → workos-5.38.0}/workos/types/directory_sync/directory.py +0 -0
  67. {workos-5.36.0 → workos-5.38.0}/workos/types/directory_sync/directory_group.py +0 -0
  68. {workos-5.36.0 → workos-5.38.0}/workos/types/directory_sync/directory_state.py +0 -0
  69. {workos-5.36.0 → workos-5.38.0}/workos/types/directory_sync/directory_type.py +0 -0
  70. {workos-5.36.0 → workos-5.38.0}/workos/types/directory_sync/directory_user.py +0 -0
  71. {workos-5.36.0 → workos-5.38.0}/workos/types/directory_sync/list_filters.py +0 -0
  72. {workos-5.36.0 → workos-5.38.0}/workos/types/events/__init__.py +0 -0
  73. {workos-5.36.0 → workos-5.38.0}/workos/types/events/authentication_payload.py +0 -0
  74. {workos-5.36.0 → workos-5.38.0}/workos/types/events/connection_payload_with_legacy_fields.py +0 -0
  75. {workos-5.36.0 → workos-5.38.0}/workos/types/events/directory_group_membership_payload.py +0 -0
  76. {workos-5.36.0 → workos-5.38.0}/workos/types/events/directory_group_with_previous_attributes.py +0 -0
  77. {workos-5.36.0 → workos-5.38.0}/workos/types/events/directory_payload.py +0 -0
  78. {workos-5.36.0 → workos-5.38.0}/workos/types/events/directory_payload_with_legacy_fields.py +0 -0
  79. {workos-5.36.0 → workos-5.38.0}/workos/types/events/directory_user_with_previous_attributes.py +0 -0
  80. {workos-5.36.0 → workos-5.38.0}/workos/types/events/event.py +0 -0
  81. {workos-5.36.0 → workos-5.38.0}/workos/types/events/event_model.py +0 -0
  82. {workos-5.36.0 → workos-5.38.0}/workos/types/events/event_type.py +0 -0
  83. {workos-5.36.0 → workos-5.38.0}/workos/types/events/list_filters.py +0 -0
  84. {workos-5.36.0 → workos-5.38.0}/workos/types/events/organization_domain_verification_failed_payload.py +0 -0
  85. {workos-5.36.0 → workos-5.38.0}/workos/types/events/previous_attributes.py +0 -0
  86. {workos-5.36.0 → workos-5.38.0}/workos/types/events/session_payload.py +0 -0
  87. {workos-5.36.0 → workos-5.38.0}/workos/types/feature_flags/__init__.py +0 -0
  88. {workos-5.36.0 → workos-5.38.0}/workos/types/feature_flags/feature_flag.py +0 -0
  89. {workos-5.36.0 → workos-5.38.0}/workos/types/feature_flags/list_filters.py +0 -0
  90. {workos-5.36.0 → workos-5.38.0}/workos/types/fga/__init__.py +0 -0
  91. {workos-5.36.0 → workos-5.38.0}/workos/types/fga/authorization_resource_types.py +0 -0
  92. {workos-5.36.0 → workos-5.38.0}/workos/types/fga/authorization_resources.py +0 -0
  93. {workos-5.36.0 → workos-5.38.0}/workos/types/fga/check.py +0 -0
  94. {workos-5.36.0 → workos-5.38.0}/workos/types/fga/list_filters.py +0 -0
  95. {workos-5.36.0 → workos-5.38.0}/workos/types/fga/warnings.py +0 -0
  96. {workos-5.36.0 → workos-5.38.0}/workos/types/fga/warrant.py +0 -0
  97. {workos-5.36.0 → workos-5.38.0}/workos/types/list_resource.py +0 -0
  98. {workos-5.36.0 → workos-5.38.0}/workos/types/metadata.py +0 -0
  99. {workos-5.36.0 → workos-5.38.0}/workos/types/mfa/__init__.py +0 -0
  100. {workos-5.36.0 → workos-5.38.0}/workos/types/mfa/authentication_challenge.py +0 -0
  101. {workos-5.36.0 → workos-5.38.0}/workos/types/mfa/authentication_challenge_verification_response.py +0 -0
  102. {workos-5.36.0 → workos-5.38.0}/workos/types/mfa/authentication_factor.py +0 -0
  103. {workos-5.36.0 → workos-5.38.0}/workos/types/mfa/authentication_factor_totp_and_challenge_response.py +0 -0
  104. {workos-5.36.0 → workos-5.38.0}/workos/types/mfa/enroll_authentication_factor_type.py +0 -0
  105. {workos-5.36.0 → workos-5.38.0}/workos/types/organization_domains/__init__.py +0 -0
  106. {workos-5.36.0 → workos-5.38.0}/workos/types/organization_domains/organization_domain.py +0 -0
  107. {workos-5.36.0 → workos-5.38.0}/workos/types/organizations/__init__.py +0 -0
  108. {workos-5.36.0 → workos-5.38.0}/workos/types/organizations/domain_data_input.py +0 -0
  109. {workos-5.36.0 → workos-5.38.0}/workos/types/organizations/list_filters.py +0 -0
  110. {workos-5.36.0 → workos-5.38.0}/workos/types/organizations/organization.py +0 -0
  111. {workos-5.36.0 → workos-5.38.0}/workos/types/organizations/organization_common.py +0 -0
  112. {workos-5.36.0 → workos-5.38.0}/workos/types/passwordless/__init__.py +0 -0
  113. {workos-5.36.0 → workos-5.38.0}/workos/types/passwordless/passwordless_session.py +0 -0
  114. {workos-5.36.0 → workos-5.38.0}/workos/types/passwordless/passwordless_session_type.py +0 -0
  115. {workos-5.36.0 → workos-5.38.0}/workos/types/portal/__init__.py +0 -0
  116. {workos-5.36.0 → workos-5.38.0}/workos/types/portal/portal_link.py +0 -0
  117. {workos-5.36.0 → workos-5.38.0}/workos/types/portal/portal_link_intent.py +0 -0
  118. {workos-5.36.0 → workos-5.38.0}/workos/types/portal/portal_link_intent_options.py +0 -0
  119. {workos-5.36.0 → workos-5.38.0}/workos/types/roles/__init__.py +0 -0
  120. {workos-5.36.0 → workos-5.38.0}/workos/types/roles/role.py +0 -0
  121. {workos-5.36.0 → workos-5.38.0}/workos/types/sso/__init__.py +0 -0
  122. {workos-5.36.0 → workos-5.38.0}/workos/types/sso/connection.py +0 -0
  123. {workos-5.36.0 → workos-5.38.0}/workos/types/sso/connection_domain.py +0 -0
  124. {workos-5.36.0 → workos-5.38.0}/workos/types/sso/profile.py +0 -0
  125. {workos-5.36.0 → workos-5.38.0}/workos/types/sso/sso_provider_type.py +0 -0
  126. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/__init__.py +0 -0
  127. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/authenticate_with_common.py +0 -0
  128. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/authentication_response.py +0 -0
  129. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/email_verification.py +0 -0
  130. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/impersonator.py +0 -0
  131. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/invitation.py +0 -0
  132. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/list_filters.py +0 -0
  133. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/magic_auth.py +0 -0
  134. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/oauth_tokens.py +0 -0
  135. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/organization_membership.py +0 -0
  136. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/password_hash_type.py +0 -0
  137. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/password_reset.py +0 -0
  138. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/screen_hint.py +0 -0
  139. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/session.py +0 -0
  140. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/user.py +0 -0
  141. {workos-5.36.0 → workos-5.38.0}/workos/types/user_management/user_management_provider_type.py +0 -0
  142. {workos-5.36.0 → workos-5.38.0}/workos/types/vault/__init__.py +0 -0
  143. {workos-5.36.0 → workos-5.38.0}/workos/types/vault/key.py +0 -0
  144. {workos-5.36.0 → workos-5.38.0}/workos/types/vault/object.py +0 -0
  145. {workos-5.36.0 → workos-5.38.0}/workos/types/webhooks/__init__.py +0 -0
  146. {workos-5.36.0 → workos-5.38.0}/workos/types/webhooks/webhook.py +0 -0
  147. {workos-5.36.0 → workos-5.38.0}/workos/types/webhooks/webhook_model.py +0 -0
  148. {workos-5.36.0 → workos-5.38.0}/workos/types/webhooks/webhook_payload.py +0 -0
  149. {workos-5.36.0 → workos-5.38.0}/workos/types/widgets/__init__.py +0 -0
  150. {workos-5.36.0 → workos-5.38.0}/workos/types/widgets/widget_scope.py +0 -0
  151. {workos-5.36.0 → workos-5.38.0}/workos/types/widgets/widget_token_response.py +0 -0
  152. {workos-5.36.0 → workos-5.38.0}/workos/types/workos_model.py +0 -0
  153. {workos-5.36.0 → workos-5.38.0}/workos/typing/__init__.py +0 -0
  154. {workos-5.36.0 → workos-5.38.0}/workos/typing/literals.py +0 -0
  155. {workos-5.36.0 → workos-5.38.0}/workos/typing/sync_or_async.py +0 -0
  156. {workos-5.36.0 → workos-5.38.0}/workos/typing/untyped_literal.py +0 -0
  157. {workos-5.36.0 → workos-5.38.0}/workos/typing/webhooks.py +0 -0
  158. {workos-5.36.0 → workos-5.38.0}/workos/utils/__init__.py +0 -0
  159. {workos-5.36.0 → workos-5.38.0}/workos/utils/_base_http_client.py +0 -0
  160. {workos-5.36.0 → workos-5.38.0}/workos/utils/crypto_provider.py +0 -0
  161. {workos-5.36.0 → workos-5.38.0}/workos/utils/http_client.py +0 -0
  162. {workos-5.36.0 → workos-5.38.0}/workos/utils/pagination_order.py +0 -0
  163. {workos-5.36.0 → workos-5.38.0}/workos/utils/request_helper.py +0 -0
  164. {workos-5.36.0 → workos-5.38.0}/workos/webhooks.py +0 -0
  165. {workos-5.36.0 → workos-5.38.0}/workos/widgets.py +0 -0
  166. {workos-5.36.0 → workos-5.38.0}/workos.egg-info/dependency_links.txt +0 -0
  167. {workos-5.36.0 → workos-5.38.0}/workos.egg-info/not-zip-safe +0 -0
  168. {workos-5.36.0 → workos-5.38.0}/workos.egg-info/requires.txt +0 -0
  169. {workos-5.36.0 → workos-5.38.0}/workos.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: workos
3
- Version: 5.36.0
3
+ Version: 5.38.0
4
4
  Summary: WorkOS Python Client
5
5
  Home-page: https://github.com/workos-inc/workos-python
6
6
  Author: WorkOS
@@ -0,0 +1,167 @@
1
+ import pytest
2
+
3
+ from tests.utils.syncify import syncify
4
+ from workos.pipes import AsyncPipes, Pipes
5
+
6
+
7
+ @pytest.mark.sync_and_async(Pipes, AsyncPipes)
8
+ class TestPipes:
9
+ @pytest.fixture
10
+ def mock_access_token(self):
11
+ return {
12
+ "object": "access_token",
13
+ "access_token": "test_access_token_123",
14
+ "expires_at": "2026-01-09T12:00:00.000Z",
15
+ "scopes": ["read:users", "write:users"],
16
+ "missing_scopes": [],
17
+ }
18
+
19
+ def test_get_access_token_success_with_expiry(
20
+ self,
21
+ module_instance,
22
+ mock_access_token,
23
+ capture_and_mock_http_client_request,
24
+ ):
25
+ response_body = {
26
+ "active": True,
27
+ "access_token": mock_access_token,
28
+ }
29
+ request_kwargs = capture_and_mock_http_client_request(
30
+ module_instance._http_client, response_body, 200
31
+ )
32
+
33
+ result = syncify(
34
+ module_instance.get_access_token(
35
+ provider="test-provider",
36
+ user_id="user_123",
37
+ )
38
+ )
39
+
40
+ assert request_kwargs["url"].endswith("data-integrations/test-provider/token")
41
+ assert request_kwargs["method"] == "post"
42
+ assert request_kwargs["json"]["user_id"] == "user_123"
43
+ assert result.active is True
44
+ assert result.access_token.access_token == mock_access_token["access_token"]
45
+ assert result.access_token.scopes == mock_access_token["scopes"]
46
+
47
+ def test_get_access_token_success_without_expiry(
48
+ self,
49
+ module_instance,
50
+ capture_and_mock_http_client_request,
51
+ ):
52
+ response_body = {
53
+ "active": True,
54
+ "access_token": {
55
+ "object": "access_token",
56
+ "access_token": "test_token",
57
+ "expires_at": None,
58
+ "scopes": ["read"],
59
+ "missing_scopes": [],
60
+ },
61
+ }
62
+ capture_and_mock_http_client_request(
63
+ module_instance._http_client, response_body, 200
64
+ )
65
+
66
+ result = syncify(
67
+ module_instance.get_access_token(
68
+ provider="test-provider",
69
+ user_id="user_123",
70
+ )
71
+ )
72
+
73
+ assert result.active is True
74
+ assert result.access_token.expires_at is None
75
+
76
+ def test_get_access_token_with_organization_id(
77
+ self,
78
+ module_instance,
79
+ mock_access_token,
80
+ capture_and_mock_http_client_request,
81
+ ):
82
+ response_body = {
83
+ "active": True,
84
+ "access_token": mock_access_token,
85
+ }
86
+ request_kwargs = capture_and_mock_http_client_request(
87
+ module_instance._http_client, response_body, 200
88
+ )
89
+
90
+ syncify(
91
+ module_instance.get_access_token(
92
+ provider="test-provider",
93
+ user_id="user_123",
94
+ organization_id="org_456",
95
+ )
96
+ )
97
+
98
+ assert request_kwargs["json"]["organization_id"] == "org_456"
99
+
100
+ def test_get_access_token_without_organization_id(
101
+ self,
102
+ module_instance,
103
+ mock_access_token,
104
+ capture_and_mock_http_client_request,
105
+ ):
106
+ response_body = {
107
+ "active": True,
108
+ "access_token": mock_access_token,
109
+ }
110
+ request_kwargs = capture_and_mock_http_client_request(
111
+ module_instance._http_client, response_body, 200
112
+ )
113
+
114
+ syncify(
115
+ module_instance.get_access_token(
116
+ provider="test-provider",
117
+ user_id="user_123",
118
+ )
119
+ )
120
+
121
+ assert "organization_id" not in request_kwargs["json"]
122
+
123
+ def test_get_access_token_not_installed(
124
+ self,
125
+ module_instance,
126
+ capture_and_mock_http_client_request,
127
+ ):
128
+ response_body = {
129
+ "active": False,
130
+ "error": "not_installed",
131
+ }
132
+ capture_and_mock_http_client_request(
133
+ module_instance._http_client, response_body, 200
134
+ )
135
+
136
+ result = syncify(
137
+ module_instance.get_access_token(
138
+ provider="test-provider",
139
+ user_id="user_123",
140
+ )
141
+ )
142
+
143
+ assert result.active is False
144
+ assert result.error == "not_installed"
145
+
146
+ def test_get_access_token_needs_reauthorization(
147
+ self,
148
+ module_instance,
149
+ capture_and_mock_http_client_request,
150
+ ):
151
+ response_body = {
152
+ "active": False,
153
+ "error": "needs_reauthorization",
154
+ }
155
+ capture_and_mock_http_client_request(
156
+ module_instance._http_client, response_body, 200
157
+ )
158
+
159
+ result = syncify(
160
+ module_instance.get_access_token(
161
+ provider="test-provider",
162
+ user_id="user_123",
163
+ )
164
+ )
165
+
166
+ assert result.active is False
167
+ assert result.error == "needs_reauthorization"
@@ -6,6 +6,7 @@ import pytest
6
6
 
7
7
  from tests.utils.fixtures.mock_auth_factor_totp import MockAuthenticationFactorTotp
8
8
  from tests.utils.fixtures.mock_email_verification import MockEmailVerification
9
+ from tests.utils.fixtures.mock_feature_flag import MockFeatureFlag
9
10
  from tests.utils.fixtures.mock_invitation import MockInvitation
10
11
  from tests.utils.fixtures.mock_magic_auth import MockMagicAuth
11
12
  from tests.utils.fixtures.mock_organization_membership import MockOrganizationMembership
@@ -146,6 +147,14 @@ class UserManagementFixtures:
146
147
  invitations_list = [MockInvitation(id=str(i)).dict() for i in range(40)]
147
148
  return list_response_of(data=invitations_list)
148
149
 
150
+ @pytest.fixture
151
+ def mock_feature_flags(self):
152
+ return {
153
+ "data": [MockFeatureFlag(id=f"flag_{str(i)}").dict() for i in range(2)],
154
+ "object": "list",
155
+ "list_metadata": {"before": None, "after": None},
156
+ }
157
+
149
158
 
150
159
  class TestUserManagementBase(UserManagementFixtures):
151
160
  @pytest.fixture(autouse=True)
@@ -1250,3 +1259,28 @@ class TestUserManagement(UserManagementFixtures):
1250
1259
 
1251
1260
  with pytest.raises(Exception):
1252
1261
  syncify(self.user_management.resend_invitation("invitation_accepted"))
1262
+
1263
+ def test_list_feature_flags(
1264
+ self, mock_feature_flags, capture_and_mock_http_client_request
1265
+ ):
1266
+ request_kwargs = capture_and_mock_http_client_request(
1267
+ self.http_client, mock_feature_flags, 200
1268
+ )
1269
+
1270
+ feature_flags_response = syncify(
1271
+ self.user_management.list_feature_flags(
1272
+ user_id="user_01H7ZGXFP5C6BBQY6Z7277ZCT0"
1273
+ )
1274
+ )
1275
+
1276
+ def to_dict(x):
1277
+ return x.dict()
1278
+
1279
+ assert request_kwargs["method"] == "get"
1280
+ assert request_kwargs["url"].endswith(
1281
+ "/user_management/users/user_01H7ZGXFP5C6BBQY6Z7277ZCT0/feature-flags"
1282
+ )
1283
+ assert (
1284
+ list(map(to_dict, feature_flags_response.data))
1285
+ == mock_feature_flags["data"]
1286
+ )
@@ -107,6 +107,34 @@ class TestVault:
107
107
  ):
108
108
  self.vault.read_object(object_id=None)
109
109
 
110
+ def test_read_object_by_name_success(
111
+ self, mock_vault_object, capture_and_mock_http_client_request
112
+ ):
113
+ request_kwargs = capture_and_mock_http_client_request(
114
+ self.http_client, mock_vault_object, 200
115
+ )
116
+
117
+ vault_object = self.vault.read_object_by_name(name="test-secret")
118
+
119
+ assert request_kwargs["method"] == "get"
120
+ assert request_kwargs["url"].endswith("/vault/v1/kv/name/test-secret")
121
+ assert vault_object.id == "vault_01234567890abcdef"
122
+ assert vault_object.name == "test-secret"
123
+ assert vault_object.value == "secret-value"
124
+ assert vault_object.metadata.environment_id == "env_01234567890abcdef"
125
+
126
+ def test_read_object_by_name_missing_name(self):
127
+ with pytest.raises(
128
+ ValueError, match="Incomplete arguments: 'name' is a required argument"
129
+ ):
130
+ self.vault.read_object_by_name(name="")
131
+
132
+ def test_read_object_by_name_none_name(self):
133
+ with pytest.raises(
134
+ ValueError, match="Incomplete arguments: 'name' is a required argument"
135
+ ):
136
+ self.vault.read_object_by_name(name=None)
137
+
110
138
  def test_list_objects_default_params(
111
139
  self, mock_vault_objects_list, capture_and_mock_http_client_request
112
140
  ):
@@ -12,7 +12,7 @@ __package_name__ = "workos"
12
12
 
13
13
  __package_url__ = "https://github.com/workos-inc/workos-python"
14
14
 
15
- __version__ = "5.36.0"
15
+ __version__ = "5.38.0"
16
16
 
17
17
  __author__ = "WorkOS"
18
18
 
@@ -10,6 +10,7 @@ from workos.events import EventsModule
10
10
  from workos.fga import FGAModule
11
11
  from workos.mfa import MFAModule
12
12
  from workos.organization_domains import OrganizationDomainsModule
13
+ from workos.pipes import PipesModule
13
14
  from workos.organizations import OrganizationsModule
14
15
  from workos.passwordless import PasswordlessModule
15
16
  from workos.portal import PortalModule
@@ -101,6 +102,10 @@ class BaseClient(ClientConfiguration):
101
102
  @abstractmethod
102
103
  def passwordless(self) -> PasswordlessModule: ...
103
104
 
105
+ @property
106
+ @abstractmethod
107
+ def pipes(self) -> PipesModule: ...
108
+
104
109
  @property
105
110
  @abstractmethod
106
111
  def portal(self) -> PortalModule: ...
@@ -10,6 +10,7 @@ from workos.mfa import MFAModule
10
10
  from workos.organizations import AsyncOrganizations
11
11
  from workos.organization_domains import AsyncOrganizationDomains
12
12
  from workos.passwordless import PasswordlessModule
13
+ from workos.pipes import AsyncPipes
13
14
  from workos.portal import PortalModule
14
15
  from workos.sso import AsyncSSO
15
16
  from workos.user_management import AsyncUserManagement
@@ -102,6 +103,12 @@ class AsyncClient(BaseClient):
102
103
  "Passwordless APIs are not yet supported in the async client."
103
104
  )
104
105
 
106
+ @property
107
+ def pipes(self) -> AsyncPipes:
108
+ if not getattr(self, "_pipes", None):
109
+ self._pipes = AsyncPipes(self._http_client)
110
+ return self._pipes
111
+
105
112
  @property
106
113
  def portal(self) -> PortalModule:
107
114
  raise NotImplementedError(
@@ -8,6 +8,7 @@ from workos.fga import FGA
8
8
  from workos.organizations import Organizations
9
9
  from workos.organization_domains import OrganizationDomains
10
10
  from workos.passwordless import Passwordless
11
+ from workos.pipes import Pipes
11
12
  from workos.portal import Portal
12
13
  from workos.sso import SSO
13
14
  from workos.webhooks import Webhooks
@@ -102,6 +103,12 @@ class SyncClient(BaseClient):
102
103
  self._passwordless = Passwordless(self._http_client)
103
104
  return self._passwordless
104
105
 
106
+ @property
107
+ def pipes(self) -> Pipes:
108
+ if not getattr(self, "_pipes", None):
109
+ self._pipes = Pipes(self._http_client)
110
+ return self._pipes
111
+
105
112
  @property
106
113
  def portal(self) -> Portal:
107
114
  if not getattr(self, "_portal", None):
@@ -0,0 +1,93 @@
1
+ from typing import Dict, Optional, Protocol
2
+
3
+ from workos.types.pipes import (
4
+ GetAccessTokenFailureResponse,
5
+ GetAccessTokenResponse,
6
+ GetAccessTokenSuccessResponse,
7
+ )
8
+ from workos.typing.sync_or_async import SyncOrAsync
9
+ from workos.utils.http_client import AsyncHTTPClient, SyncHTTPClient
10
+ from workos.utils.request_helper import REQUEST_METHOD_POST
11
+
12
+
13
+ class PipesModule(Protocol):
14
+ """Protocol defining the Pipes module interface."""
15
+
16
+ def get_access_token(
17
+ self,
18
+ *,
19
+ provider: str,
20
+ user_id: str,
21
+ organization_id: Optional[str] = None,
22
+ ) -> SyncOrAsync[GetAccessTokenResponse]:
23
+ """Retrieve an access token for a third-party provider.
24
+
25
+ Kwargs:
26
+ provider (str): The third-party provider identifier
27
+ user_id (str): The WorkOS user ID
28
+ organization_id (str, optional): The WorkOS organization ID
29
+
30
+ Returns:
31
+ GetAccessTokenResponse: Success response with token or failure response with error
32
+ """
33
+ ...
34
+
35
+
36
+ class Pipes(PipesModule):
37
+ """Sync implementation of the Pipes module."""
38
+
39
+ _http_client: SyncHTTPClient
40
+
41
+ def __init__(self, http_client: SyncHTTPClient):
42
+ self._http_client = http_client
43
+
44
+ def get_access_token(
45
+ self,
46
+ *,
47
+ provider: str,
48
+ user_id: str,
49
+ organization_id: Optional[str] = None,
50
+ ) -> GetAccessTokenResponse:
51
+ json_data: Dict[str, str] = {"user_id": user_id}
52
+ if organization_id is not None:
53
+ json_data["organization_id"] = organization_id
54
+
55
+ response = self._http_client.request(
56
+ f"data-integrations/{provider}/token",
57
+ method=REQUEST_METHOD_POST,
58
+ json=json_data,
59
+ )
60
+
61
+ if response.get("active") is True:
62
+ return GetAccessTokenSuccessResponse.model_validate(response)
63
+ return GetAccessTokenFailureResponse.model_validate(response)
64
+
65
+
66
+ class AsyncPipes(PipesModule):
67
+ """Async implementation of the Pipes module."""
68
+
69
+ _http_client: AsyncHTTPClient
70
+
71
+ def __init__(self, http_client: AsyncHTTPClient):
72
+ self._http_client = http_client
73
+
74
+ async def get_access_token(
75
+ self,
76
+ *,
77
+ provider: str,
78
+ user_id: str,
79
+ organization_id: Optional[str] = None,
80
+ ) -> GetAccessTokenResponse:
81
+ json_data: Dict[str, str] = {"user_id": user_id}
82
+ if organization_id is not None:
83
+ json_data["organization_id"] = organization_id
84
+
85
+ response = await self._http_client.request(
86
+ f"data-integrations/{provider}/token",
87
+ method=REQUEST_METHOD_POST,
88
+ json=json_data,
89
+ )
90
+
91
+ if response.get("active") is True:
92
+ return GetAccessTokenSuccessResponse.model_validate(response)
93
+ return GetAccessTokenFailureResponse.model_validate(response)
@@ -0,0 +1,6 @@
1
+ from workos.types.pipes.pipes import (
2
+ AccessToken as AccessToken,
3
+ GetAccessTokenFailureResponse as GetAccessTokenFailureResponse,
4
+ GetAccessTokenResponse as GetAccessTokenResponse,
5
+ GetAccessTokenSuccessResponse as GetAccessTokenSuccessResponse,
6
+ )
@@ -0,0 +1,34 @@
1
+ from datetime import datetime
2
+ from typing import Literal, Optional, Sequence, Union
3
+
4
+ from workos.types.workos_model import WorkOSModel
5
+
6
+
7
+ class AccessToken(WorkOSModel):
8
+ """Represents an OAuth access token for a third-party provider."""
9
+
10
+ object: Literal["access_token"]
11
+ access_token: str
12
+ expires_at: Optional[datetime] = None
13
+ scopes: Sequence[str]
14
+ missing_scopes: Sequence[str]
15
+
16
+
17
+ class GetAccessTokenSuccessResponse(WorkOSModel):
18
+ """Successful response containing the access token."""
19
+
20
+ active: Literal[True]
21
+ access_token: AccessToken
22
+
23
+
24
+ class GetAccessTokenFailureResponse(WorkOSModel):
25
+ """Failed response indicating why the token couldn't be retrieved."""
26
+
27
+ active: Literal[False]
28
+ error: Literal["not_installed", "needs_reauthorization"]
29
+
30
+
31
+ GetAccessTokenResponse = Union[
32
+ GetAccessTokenSuccessResponse,
33
+ GetAccessTokenFailureResponse,
34
+ ]
@@ -2,6 +2,8 @@ from typing import Awaitable, Optional, Protocol, Sequence, Type, Union, cast
2
2
  from urllib.parse import urlencode
3
3
  from workos._client_configuration import ClientConfiguration
4
4
  from workos.session import AsyncSession, Session
5
+ from workos.types.feature_flags import FeatureFlag
6
+ from workos.types.feature_flags.list_filters import FeatureFlagListFilters
5
7
  from workos.types.list_resource import (
6
8
  ListArgs,
7
9
  ListMetadata,
@@ -97,6 +99,7 @@ INVITATION_REVOKE_PATH = "user_management/invitations/{0}/revoke"
97
99
  INVITATION_RESEND_PATH = "user_management/invitations/{0}/resend"
98
100
  PASSWORD_RESET_PATH = "user_management/password_reset"
99
101
  PASSWORD_RESET_DETAIL_PATH = "user_management/password_reset/{0}"
102
+ USER_FEATURE_FLAGS_PATH = "user_management/users/{0}/feature-flags"
100
103
 
101
104
 
102
105
  UsersListResource = WorkOSListResource[User, UsersListFilters, ListMetadata]
@@ -113,6 +116,10 @@ InvitationsListResource = WorkOSListResource[
113
116
  Invitation, InvitationsListFilters, ListMetadata
114
117
  ]
115
118
 
119
+ FeatureFlagsListResource = WorkOSListResource[
120
+ FeatureFlag, FeatureFlagListFilters, ListMetadata
121
+ ]
122
+
116
123
  from workos.types.user_management.list_filters import SessionsListFilters
117
124
 
118
125
  SessionsListResource = WorkOSListResource[
@@ -908,6 +915,29 @@ class UserManagementModule(Protocol):
908
915
  """
909
916
  ...
910
917
 
918
+ def list_feature_flags(
919
+ self,
920
+ user_id: str,
921
+ *,
922
+ limit: int = DEFAULT_LIST_RESPONSE_LIMIT,
923
+ before: Optional[str] = None,
924
+ after: Optional[str] = None,
925
+ order: PaginationOrder = "desc",
926
+ ) -> SyncOrAsync[FeatureFlagsListResource]:
927
+ """Retrieve a list of feature flags for a user
928
+
929
+ Args:
930
+ user_id (str): User's unique identifier
931
+ limit (int): Maximum number of records to return. (Optional)
932
+ before (str): Pagination cursor to receive records before a provided Feature Flag ID. (Optional)
933
+ after (str): Pagination cursor to receive records after a provided Feature Flag ID. (Optional)
934
+ order (Literal["asc","desc"]): Sort records in either ascending or descending (default) order by created_at timestamp. (Optional)
935
+
936
+ Returns:
937
+ FeatureFlagsListResource: Feature flags list response from WorkOS.
938
+ """
939
+ ...
940
+
911
941
 
912
942
  class UserManagement(UserManagementModule):
913
943
  _http_client: SyncHTTPClient
@@ -1603,6 +1633,34 @@ class UserManagement(UserManagementModule):
1603
1633
 
1604
1634
  return Invitation.model_validate(response)
1605
1635
 
1636
+ def list_feature_flags(
1637
+ self,
1638
+ user_id: str,
1639
+ *,
1640
+ limit: int = DEFAULT_LIST_RESPONSE_LIMIT,
1641
+ before: Optional[str] = None,
1642
+ after: Optional[str] = None,
1643
+ order: PaginationOrder = "desc",
1644
+ ) -> FeatureFlagsListResource:
1645
+ list_params: FeatureFlagListFilters = {
1646
+ "limit": limit,
1647
+ "before": before,
1648
+ "after": after,
1649
+ "order": order,
1650
+ }
1651
+
1652
+ response = self._http_client.request(
1653
+ USER_FEATURE_FLAGS_PATH.format(user_id),
1654
+ method=REQUEST_METHOD_GET,
1655
+ params=list_params,
1656
+ )
1657
+
1658
+ return FeatureFlagsListResource(
1659
+ list_method=self.list_feature_flags,
1660
+ list_args=list_params,
1661
+ **ListPage[FeatureFlag](**response).model_dump(),
1662
+ )
1663
+
1606
1664
 
1607
1665
  class AsyncUserManagement(UserManagementModule):
1608
1666
  _http_client: AsyncHTTPClient
@@ -2312,3 +2370,31 @@ class AsyncUserManagement(UserManagementModule):
2312
2370
  )
2313
2371
 
2314
2372
  return Invitation.model_validate(response)
2373
+
2374
+ async def list_feature_flags(
2375
+ self,
2376
+ user_id: str,
2377
+ *,
2378
+ limit: int = DEFAULT_LIST_RESPONSE_LIMIT,
2379
+ before: Optional[str] = None,
2380
+ after: Optional[str] = None,
2381
+ order: PaginationOrder = "desc",
2382
+ ) -> FeatureFlagsListResource:
2383
+ list_params: FeatureFlagListFilters = {
2384
+ "limit": limit,
2385
+ "before": before,
2386
+ "after": after,
2387
+ "order": order,
2388
+ }
2389
+
2390
+ response = await self._http_client.request(
2391
+ USER_FEATURE_FLAGS_PATH.format(user_id),
2392
+ method=REQUEST_METHOD_GET,
2393
+ params=list_params,
2394
+ )
2395
+
2396
+ return FeatureFlagsListResource(
2397
+ list_method=self.list_feature_flags,
2398
+ list_args=list_params,
2399
+ **ListPage[FeatureFlag](**response).model_dump(),
2400
+ )
@@ -37,6 +37,17 @@ class VaultModule(Protocol):
37
37
  """
38
38
  ...
39
39
 
40
+ def read_object_by_name(self, *, name: str) -> VaultObject:
41
+ """
42
+ Get a Vault object by name with the value decrypted.
43
+
44
+ Kwargs:
45
+ name (str): The unique name of the object.
46
+ Returns:
47
+ VaultObject: A vault object with metadata, name and decrypted value.
48
+ """
49
+ ...
50
+
40
51
  def list_objects(
41
52
  self,
42
53
  *,
@@ -230,6 +241,24 @@ class Vault(VaultModule):
230
241
 
231
242
  return VaultObject.model_validate(response)
232
243
 
244
+ def read_object_by_name(
245
+ self,
246
+ *,
247
+ name: str,
248
+ ) -> VaultObject:
249
+ if not name:
250
+ raise ValueError("Incomplete arguments: 'name' is a required argument")
251
+
252
+ response = self._http_client.request(
253
+ RequestHelper.build_parameterized_url(
254
+ "vault/v1/kv/name/{name}",
255
+ name=name,
256
+ ),
257
+ method=REQUEST_METHOD_GET,
258
+ )
259
+
260
+ return VaultObject.model_validate(response)
261
+
233
262
  def list_objects(
234
263
  self,
235
264
  *,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: workos
3
- Version: 5.36.0
3
+ Version: 5.38.0
4
4
  Summary: WorkOS Python Client
5
5
  Home-page: https://github.com/workos-inc/workos-python
6
6
  Author: WorkOS
@@ -12,6 +12,7 @@ tests/test_mfa.py
12
12
  tests/test_organization_domains.py
13
13
  tests/test_organizations.py
14
14
  tests/test_passwordless.py
15
+ tests/test_pipes.py
15
16
  tests/test_portal.py
16
17
  tests/test_session.py
17
18
  tests/test_sso.py
@@ -38,6 +39,7 @@ workos/mfa.py
38
39
  workos/organization_domains.py
39
40
  workos/organizations.py
40
41
  workos/passwordless.py
42
+ workos/pipes.py
41
43
  workos/portal.py
42
44
  workos/py.typed
43
45
  workos/session.py
@@ -113,6 +115,8 @@ workos/types/organizations/organization_common.py
113
115
  workos/types/passwordless/__init__.py
114
116
  workos/types/passwordless/passwordless_session.py
115
117
  workos/types/passwordless/passwordless_session_type.py
118
+ workos/types/pipes/__init__.py
119
+ workos/types/pipes/pipes.py
116
120
  workos/types/portal/__init__.py
117
121
  workos/types/portal/portal_link.py
118
122
  workos/types/portal/portal_link_intent.py
File without changes
File without changes
File without changes
File without changes
File without changes