From a306facab5bc6174ea342effcd0aaef2c532774e Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Thu, 7 May 2026 14:35:42 -0700 Subject: [PATCH 01/13] fix: percent-encode path segments in base client Centralize path-segment encoding so user-supplied identifiers cannot escape their intended URL segment via reserved characters such as '/', '?', '#', or '%'. A new helper `_encode_path` splits on '/' and applies `urllib.parse.quote(seg, safe='')` to each segment, and is invoked from both the sync and async `request` methods. This covers all ~115 generated call sites without per-site changes. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/workos/_base_client.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/workos/_base_client.py b/src/workos/_base_client.py index 83c18d39..9153a6dd 100644 --- a/src/workos/_base_client.py +++ b/src/workos/_base_client.py @@ -10,6 +10,7 @@ from datetime import datetime, timezone from email.utils import parsedate_to_datetime from typing import Any, Dict, Optional, Type, cast, overload +from urllib.parse import quote import httpx @@ -128,6 +129,21 @@ def _resolve_base_url(self, request_options: Optional[RequestOptions]) -> str: return str(base_url).rstrip("/") return self._base_url.rstrip("/") + @staticmethod + def _encode_path(path: str) -> str: + """Percent-encode each path segment to prevent path-traversal/injection. + + Splits on ``/`` and applies ``urllib.parse.quote(seg, safe='')`` to each + segment so that user-supplied IDs containing reserved characters (``/``, + ``?``, ``#``, ``%``, etc.) cannot escape their intended segment. The + leading slash (if any) is preserved. + """ + if not path: + return path + leading = "/" if path.startswith("/") else "" + body = path[1:] if leading else path + return leading + "/".join(quote(seg, safe="") for seg in body.split("/")) + def _resolve_timeout(self, request_options: Optional[RequestOptions]) -> float: timeout = self._request_timeout if request_options: @@ -406,7 +422,7 @@ def request( request_options: Optional[RequestOptions] = None, ) -> Any: """Make an HTTP request with retry logic.""" - url = f"{self._resolve_base_url(request_options)}/{path}" + url = f"{self._resolve_base_url(request_options)}/{self._encode_path(path).lstrip('/')}" headers = self._build_headers(method, idempotency_key, request_options) timeout = self._resolve_timeout(request_options) max_retries = self._resolve_max_retries(request_options) @@ -631,7 +647,7 @@ async def request( request_options: Optional[RequestOptions] = None, ) -> Any: """Make an async HTTP request with retry logic.""" - url = f"{self._resolve_base_url(request_options)}/{path}" + url = f"{self._resolve_base_url(request_options)}/{self._encode_path(path).lstrip('/')}" headers = self._build_headers(method, idempotency_key, request_options) timeout = self._resolve_timeout(request_options) max_retries = self._resolve_max_retries(request_options) From 380434bd35f5e16fdb51ed003def4dcac862b362 Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Thu, 7 May 2026 14:36:51 -0700 Subject: [PATCH 02/13] fix: add structured failure reasons for session refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds new members to `AuthenticateWithSessionCookieFailureReason` (`MFA_CHALLENGE_REQUIRED`, `SSO_REQUIRED`, `EMAIL_VERIFICATION_REQUIRED`, `ORGANIZATION_SELECTION_REQUIRED`, `REFRESH_DENIED`, `REFRESH_NETWORK_ERROR`) and maps the relevant auth-flow / network exceptions raised during refresh to those reasons instead of stringifying the exception. The `reason` field is already typed `Union[..., str]`, so existing string-based consumers keep working — additive only. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/workos/session.py | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/workos/session.py b/src/workos/session.py index fbee198e..e87975c9 100644 --- a/src/workos/session.py +++ b/src/workos/session.py @@ -24,6 +24,16 @@ from cryptography.fernet import Fernet from jwt import PyJWKClient +from ._errors import ( + AuthenticationError, + EmailVerificationRequiredError, + MfaChallengeError, + OrganizationSelectionRequiredError, + SsoRequiredError, + WorkOSConnectionError, + WorkOSTimeoutError, +) + if TYPE_CHECKING: from ._client import AsyncWorkOSClient, WorkOSClient @@ -37,6 +47,35 @@ class AuthenticateWithSessionCookieFailureReason(Enum): INVALID_JWT = "invalid_jwt" INVALID_SESSION_COOKIE = "invalid_session_cookie" NO_SESSION_COOKIE_PROVIDED = "no_session_cookie_provided" + MFA_CHALLENGE_REQUIRED = "mfa_challenge_required" + SSO_REQUIRED = "sso_required" + EMAIL_VERIFICATION_REQUIRED = "email_verification_required" + ORGANIZATION_SELECTION_REQUIRED = "organization_selection_required" + REFRESH_DENIED = "refresh_denied" + REFRESH_NETWORK_ERROR = "refresh_network_error" + + +def _map_refresh_exception_to_reason( + exc: Exception, +) -> Union[AuthenticateWithSessionCookieFailureReason, str]: + """Map an exception raised by a refresh request to a structured reason. + + Falls back to ``str(exc)`` for unknown errors so callers retain the + pre-existing string form for diagnostics. + """ + if isinstance(exc, MfaChallengeError): + return AuthenticateWithSessionCookieFailureReason.MFA_CHALLENGE_REQUIRED + if isinstance(exc, SsoRequiredError): + return AuthenticateWithSessionCookieFailureReason.SSO_REQUIRED + if isinstance(exc, EmailVerificationRequiredError): + return AuthenticateWithSessionCookieFailureReason.EMAIL_VERIFICATION_REQUIRED + if isinstance(exc, OrganizationSelectionRequiredError): + return AuthenticateWithSessionCookieFailureReason.ORGANIZATION_SELECTION_REQUIRED + if isinstance(exc, AuthenticationError): + return AuthenticateWithSessionCookieFailureReason.REFRESH_DENIED + if isinstance(exc, (WorkOSConnectionError, WorkOSTimeoutError)): + return AuthenticateWithSessionCookieFailureReason.REFRESH_NETWORK_ERROR + return str(exc) @dataclass(slots=True) @@ -328,7 +367,7 @@ def refresh( ) except Exception as e: return RefreshWithSessionCookieErrorResponse( - authenticated=False, reason=str(e) + authenticated=False, reason=_map_refresh_exception_to_reason(e) ) def get_logout_url(self, return_to: Optional[str] = None) -> str: @@ -507,7 +546,7 @@ async def refresh( ) except Exception as e: return RefreshWithSessionCookieErrorResponse( - authenticated=False, reason=str(e) + authenticated=False, reason=_map_refresh_exception_to_reason(e) ) async def get_logout_url(self, return_to: Optional[str] = None) -> str: From ba796802475edb6cc78ffb9662f3c8136e40d384 Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Thu, 7 May 2026 14:38:14 -0700 Subject: [PATCH 03/13] fix: force public clients to drop the API key `create_public_client` now passes `api_key=None` and `is_public=True` to `WorkOSClient`. The base client honors `is_public` by forcing `_api_key` to `None` and ignoring the `WORKOS_API_KEY` environment variable, so a public/PKCE client cannot accidentally pick up an API key from the process environment. The existing `if self._client._api_key` guards in PKCE token- exchange paths (`sso/_resource.py`, `user_management/_resource.py`) keep `client_secret` out of the request body for public clients without further changes. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/workos/_base_client.py | 17 ++++++++++++++++- src/workos/public_client.py | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/workos/_base_client.py b/src/workos/_base_client.py index 9153a6dd..1becc09d 100644 --- a/src/workos/_base_client.py +++ b/src/workos/_base_client.py @@ -54,8 +54,15 @@ def __init__( request_timeout: Optional[int] = None, jwt_leeway: float = 0.0, max_retries: int = MAX_RETRIES, + is_public: bool = False, ) -> None: - self._api_key = api_key or os.environ.get("WORKOS_API_KEY") + self._is_public = is_public + # Public clients (PKCE / browser / mobile / CLI) must never attach + # an API key, even if WORKOS_API_KEY is present in the environment. + if is_public: + self._api_key: Optional[str] = None + else: + self._api_key = api_key or os.environ.get("WORKOS_API_KEY") self.client_id = client_id or os.environ.get("WORKOS_CLIENT_ID") if not self._api_key and not self.client_id: raise ValueError( @@ -348,6 +355,7 @@ def __init__( request_timeout: Optional[int] = None, jwt_leeway: float = 0.0, max_retries: int = MAX_RETRIES, + is_public: bool = False, ) -> None: """Initialize the WorkOS client. @@ -358,6 +366,10 @@ def __init__( request_timeout: HTTP request timeout in seconds. Falls back to WORKOS_REQUEST_TIMEOUT or 60. jwt_leeway: JWT clock skew leeway in seconds. max_retries: Maximum number of retries for failed requests. Defaults to 3. + is_public: When True, mark this client as public (PKCE / browser + / mobile / CLI). The API key is forced to None and the + ``WORKOS_API_KEY`` environment variable is ignored. Use + ``create_public_client`` instead of setting this directly. Raises: ValueError: If neither api_key nor client_id is provided, directly or via environment variables. @@ -369,6 +381,7 @@ def __init__( request_timeout=request_timeout, jwt_leeway=jwt_leeway, max_retries=max_retries, + is_public=is_public, ) self._client = httpx.Client( timeout=self._request_timeout, follow_redirects=True @@ -573,6 +586,7 @@ def __init__( request_timeout: Optional[int] = None, jwt_leeway: float = 0.0, max_retries: int = MAX_RETRIES, + is_public: bool = False, ) -> None: """Initialize the async WorkOS client. @@ -594,6 +608,7 @@ def __init__( request_timeout=request_timeout, jwt_leeway=jwt_leeway, max_retries=max_retries, + is_public=is_public, ) self._client = httpx.AsyncClient( timeout=self._request_timeout, follow_redirects=True diff --git a/src/workos/public_client.py b/src/workos/public_client.py index 057b7e40..b4bca338 100644 --- a/src/workos/public_client.py +++ b/src/workos/public_client.py @@ -33,7 +33,9 @@ def create_public_client( from ._client import WorkOSClient return WorkOSClient( + api_key=None, client_id=client_id, base_url=base_url, request_timeout=request_timeout, + is_public=True, ) From 7f58e9f7662bf5f9ae7182511fa92014b36d11dd Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Thu, 7 May 2026 14:38:38 -0700 Subject: [PATCH 04/13] fix: use symmetric tolerance check for webhook timestamps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the freshness window only rejected timestamps that were older than the tolerance — clock skew that put the issued timestamp in the future was silently accepted, opening the verifier to replay-style attacks once the attacker's clock drifted forward. Compare `abs(seconds_since_issued)` against `max_seconds_since_issued` in both the sync and async `verify_header` implementations. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/workos/webhooks/_resource.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/workos/webhooks/_resource.py b/src/workos/webhooks/_resource.py index 0b0d11fb..be93fa58 100644 --- a/src/workos/webhooks/_resource.py +++ b/src/workos/webhooks/_resource.py @@ -263,7 +263,7 @@ def verify_header( timestamp_in_seconds = int(issued_timestamp) / 1000 seconds_since_issued = current_time - timestamp_in_seconds - if seconds_since_issued > max_seconds_since_issued: + if abs(seconds_since_issued) > max_seconds_since_issued: raise ValueError("Timestamp outside the tolerance zone") body_str = ( @@ -520,7 +520,7 @@ def verify_header( timestamp_in_seconds = int(issued_timestamp) / 1000 seconds_since_issued = current_time - timestamp_in_seconds - if seconds_since_issued > max_seconds_since_issued: + if abs(seconds_since_issued) > max_seconds_since_issued: raise ValueError("Timestamp outside the tolerance zone") body_str = ( From 77d7ed2501a25a1f6f048969d5e5761774076810 Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Thu, 7 May 2026 14:38:57 -0700 Subject: [PATCH 05/13] fix: bound LEB128 decode to 32 bits in vault payload `_decode_u32_leb128` previously kept reading continuation bytes after the 4th byte (5+ continuations could yield a 35+ bit value that would later be used as `key_len` for slicing the payload). Tighten the loop to reject a 5th continuation byte (`i >= 4 and b & 0x80`) and validate the decoded result against the 32-bit ceiling (`> 0xFFFFFFFF`) before returning. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/workos/vault.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/workos/vault.py b/src/workos/vault.py index b5d50895..06740d2e 100644 --- a/src/workos/vault.py +++ b/src/workos/vault.py @@ -282,10 +282,12 @@ def _decode_u32_leb128(buf: bytes) -> Tuple[int, int]: res = 0 bit = 0 for i, b in enumerate(buf): - if i > 4: + if i >= 4 and (b & 0x80) != 0: raise ValueError("LEB128 integer overflow (was more than 4 bytes)") res |= (b & 0x7F) << (7 * bit) if (b & 0x80) == 0: + if res > 0xFFFFFFFF: + raise ValueError("LEB128 integer overflow (exceeds 32 bits)") return res, i + 1 bit += 1 raise ValueError("LEB128 integer not found") From 0c12462f71fa898891ea43300ba294aed3540dd7 Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Thu, 7 May 2026 14:39:33 -0700 Subject: [PATCH 06/13] fix: preserve empty AAD in vault crypto helpers Replace the truthy `if aad:` / `if associated_data` guards with explicit `is not None` checks in `_aes_gcm_encrypt`, `_aes_gcm_decrypt`, and the four `aad_buffer` construction sites. Previously an empty string passed as `associated_data` (or an empty `bytes` aad) was silently dropped, so encrypt and decrypt paths disagreed on the AAD bound to the GCM tag. Wire-format risk: any locally-persisted vault ciphertext that was encrypted by an earlier version of this SDK with `associated_data=""` (or `aad=b""`) was actually written without AAD. After this fix the SDK now binds an empty AAD, so re-encrypting with the same empty string will produce a tag the old SDK cannot verify, and decrypting old empty-AAD ciphertexts must continue to use no AAD. WorkOS Vault ciphertexts live server-side, so production impact is bounded; applications that have stashed ciphertexts locally must migrate. Document in the next release migration note. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/workos/vault.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/workos/vault.py b/src/workos/vault.py index 06740d2e..832682b1 100644 --- a/src/workos/vault.py +++ b/src/workos/vault.py @@ -240,7 +240,7 @@ def _aes_gcm_encrypt( encryptor = Cipher( algorithms.AES(key), modes.GCM(iv), backend=default_backend() ).encryptor() - if aad: + if aad is not None: encryptor.authenticate_additional_data(aad) ciphertext = encryptor.update(plaintext) + encryptor.finalize() return {"ciphertext": ciphertext, "iv": iv, "tag": encryptor.tag} @@ -256,7 +256,7 @@ def _aes_gcm_decrypt( decryptor = Cipher( algorithms.AES(key), modes.GCM(iv, tag), backend=default_backend() ).decryptor() - if aad: + if aad is not None: decryptor.authenticate_additional_data(aad) return decryptor.update(ciphertext) + decryptor.finalize() @@ -470,7 +470,9 @@ def encrypt( key = base64.b64decode(key_pair.data_key.key) key_blob = base64.b64decode(key_pair.encrypted_keys) prefix_len_buffer = _encode_u32_leb128(len(key_blob)) - aad_buffer = associated_data.encode("utf-8") if associated_data else None + aad_buffer = ( + associated_data.encode("utf-8") if associated_data is not None else None + ) iv = os.urandom(12) result = _aes_gcm_encrypt(data.encode("utf-8"), key, iv, aad_buffer) @@ -492,7 +494,9 @@ def decrypt( data_key = self.decrypt_data_key(keys=decoded.keys) key = base64.b64decode(data_key.key) - aad_buffer = associated_data.encode("utf-8") if associated_data else None + aad_buffer = ( + associated_data.encode("utf-8") if associated_data is not None else None + ) decrypted_bytes = _aes_gcm_decrypt( ciphertext=decoded.ciphertext, @@ -649,7 +653,9 @@ async def encrypt( key = base64.b64decode(key_pair.data_key.key) key_blob = base64.b64decode(key_pair.encrypted_keys) prefix_len_buffer = _encode_u32_leb128(len(key_blob)) - aad_buffer = associated_data.encode("utf-8") if associated_data else None + aad_buffer = ( + associated_data.encode("utf-8") if associated_data is not None else None + ) iv = os.urandom(12) result = _aes_gcm_encrypt(data.encode("utf-8"), key, iv, aad_buffer) @@ -670,7 +676,9 @@ async def decrypt( data_key = await self.decrypt_data_key(keys=decoded.keys) key = base64.b64decode(data_key.key) - aad_buffer = associated_data.encode("utf-8") if associated_data else None + aad_buffer = ( + associated_data.encode("utf-8") if associated_data is not None else None + ) decrypted_bytes = _aes_gcm_decrypt( ciphertext=decoded.ciphertext, From 325aa0ce39bd1ec876afd77669e40dec82256932 Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Thu, 7 May 2026 14:39:58 -0700 Subject: [PATCH 07/13] fix: treat tolerance=0 as a valid window in webhook verifier `tolerance or DEFAULT_TOLERANCE` silently coerced an explicit `tolerance=0` (caller wants no tolerance) to the default 180-second window. Use `tolerance if tolerance is not None else DEFAULT_TOLERANCE` so callers that ask for a strict zero window get one. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/workos/webhooks/_verification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workos/webhooks/_verification.py b/src/workos/webhooks/_verification.py index 3dc99582..55b1e0d0 100644 --- a/src/workos/webhooks/_verification.py +++ b/src/workos/webhooks/_verification.py @@ -78,7 +78,7 @@ def verify_header( issued_timestamp = issued_timestamp[2:] signature_hash = signature_hash[3:] - max_seconds_since_issued = tolerance or DEFAULT_TOLERANCE + max_seconds_since_issued = tolerance if tolerance is not None else DEFAULT_TOLERANCE current_time = time.time() timestamp_in_seconds = int(issued_timestamp) / 1000 seconds_since_issued = current_time - timestamp_in_seconds From bcb693b90b1cbdf2745c9d39965633816bde49af Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Thu, 7 May 2026 14:54:11 -0700 Subject: [PATCH 08/13] style(session): apply ruff format ruff format --check flagged the ORGANIZATION_SELECTION_REQUIRED return as exceeding line length. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/workos/session.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/workos/session.py b/src/workos/session.py index e87975c9..225b2b6b 100644 --- a/src/workos/session.py +++ b/src/workos/session.py @@ -70,7 +70,9 @@ def _map_refresh_exception_to_reason( if isinstance(exc, EmailVerificationRequiredError): return AuthenticateWithSessionCookieFailureReason.EMAIL_VERIFICATION_REQUIRED if isinstance(exc, OrganizationSelectionRequiredError): - return AuthenticateWithSessionCookieFailureReason.ORGANIZATION_SELECTION_REQUIRED + return ( + AuthenticateWithSessionCookieFailureReason.ORGANIZATION_SELECTION_REQUIRED + ) if isinstance(exc, AuthenticationError): return AuthenticateWithSessionCookieFailureReason.REFRESH_DENIED if isinstance(exc, (WorkOSConnectionError, WorkOSTimeoutError)): From 4cf1bf6a8580b338ad1291f04332f9cb2b9433e1 Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Fri, 8 May 2026 11:49:13 -0700 Subject: [PATCH 09/13] fix: apply symmetric tolerance check to actions and standalone verifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 7f58e9f7 closed the future-timestamp replay gap in `webhooks/_resource.verify_header`, but the same vulnerable comparison still lived in `actions._verify_signature` and the standalone `webhooks/_verification.verify_header` helper — both rejected only past-skewed timestamps, silently accepting issued timestamps in the future. Compare `abs(seconds_since_issued)` in both helpers and add regression tests covering the future-timestamp case for actions, the webhook resource, and the standalone verifier. --- src/workos/actions.py | 2 +- src/workos/webhooks/_verification.py | 2 +- tests/test_actions.py | 11 +++++++++++ tests/test_webhook_verification.py | 22 ++++++++++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/workos/actions.py b/src/workos/actions.py index cd2b3b7f..258d7986 100644 --- a/src/workos/actions.py +++ b/src/workos/actions.py @@ -44,7 +44,7 @@ def _verify_signature( timestamp_in_seconds = int(issued_timestamp) / 1000 seconds_since_issued = current_time - timestamp_in_seconds - if seconds_since_issued > tolerance: + if abs(seconds_since_issued) > tolerance: raise ValueError("Timestamp outside the tolerance zone") body_str = payload.decode("utf-8") if isinstance(payload, bytes) else payload diff --git a/src/workos/webhooks/_verification.py b/src/workos/webhooks/_verification.py index 55b1e0d0..3ec82bef 100644 --- a/src/workos/webhooks/_verification.py +++ b/src/workos/webhooks/_verification.py @@ -83,7 +83,7 @@ def verify_header( timestamp_in_seconds = int(issued_timestamp) / 1000 seconds_since_issued = current_time - timestamp_in_seconds - if seconds_since_issued > max_seconds_since_issued: + if abs(seconds_since_issued) > max_seconds_since_issued: raise ValueError("Timestamp outside the tolerance zone") unhashed_string = "{0}.{1}".format(issued_timestamp, event_body.decode("utf-8")) diff --git a/tests/test_actions.py b/tests/test_actions.py index 1d65d8f3..eb107905 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -63,6 +63,17 @@ def test_verify_header_stale_timestamp(self): tolerance=30, ) + def test_verify_header_future_timestamp(self): + future_ts = int((time.time() + 60) * 1000) + sig = _make_sig_header(SAMPLE_ACTION_PAYLOAD, SECRET, future_ts) + with pytest.raises(ValueError, match="tolerance zone"): + self.actions.verify_header( + payload=SAMPLE_ACTION_PAYLOAD, + sig_header=sig, + secret=SECRET, + tolerance=30, + ) + def test_verify_header_custom_tolerance(self): old_ts = int((time.time() - 10) * 1000) sig = _make_sig_header(SAMPLE_ACTION_PAYLOAD, SECRET, old_ts) diff --git a/tests/test_webhook_verification.py b/tests/test_webhook_verification.py index aaa2ea05..8e228b39 100644 --- a/tests/test_webhook_verification.py +++ b/tests/test_webhook_verification.py @@ -124,6 +124,17 @@ def test_verify_header_stale_timestamp(self, workos): tolerance=180, ) + def test_verify_header_future_timestamp(self, workos): + future_ts = int((time.time() + 300) * 1000) + sig = _make_sig_header(SAMPLE_EVENT, SECRET, future_ts) + with pytest.raises(ValueError, match="tolerance zone"): + workos.webhooks.verify_header( + event_body=SAMPLE_EVENT, + event_signature=sig, + secret=SECRET, + tolerance=180, + ) + class TestStandaloneVerifyEvent: def test_standalone_verify_event(self): @@ -157,3 +168,14 @@ def test_standalone_verify_header_invalid(self): event_signature=sig, secret=SECRET, ) + + def test_standalone_verify_header_future_timestamp(self): + future_ts = int((time.time() + 300) * 1000) + sig = _make_sig_header(SAMPLE_EVENT, SECRET, future_ts) + with pytest.raises(ValueError, match="tolerance zone"): + standalone_verify_header( + event_body=SAMPLE_EVENT.encode("utf-8"), + event_signature=sig, + secret=SECRET, + tolerance=180, + ) From 5a475c19b424d7642b242c6516b0d6c83713573f Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Fri, 8 May 2026 12:33:09 -0700 Subject: [PATCH 10/13] fix(session): map remaining AuthenticationFlowError subtypes to structured reasons Refresh failures from MfaEnrollmentError, OrganizationAuthMethodsRequiredError, AuthenticationMethodNotAllowedError, and RadarChallengeError previously fell through the AuthenticationError check (they inherit from AuthorizationError) and surfaced as bare strings. Add enum members and isinstance branches so all explicit AuthKit flow outcomes resolve to structured reasons. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/workos/session.py | 18 +++++++++++ tests/test_session.py | 72 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/workos/session.py b/src/workos/session.py index 225b2b6b..f289c5eb 100644 --- a/src/workos/session.py +++ b/src/workos/session.py @@ -26,9 +26,13 @@ from ._errors import ( AuthenticationError, + AuthenticationMethodNotAllowedError, EmailVerificationRequiredError, MfaChallengeError, + MfaEnrollmentError, + OrganizationAuthMethodsRequiredError, OrganizationSelectionRequiredError, + RadarChallengeError, SsoRequiredError, WorkOSConnectionError, WorkOSTimeoutError, @@ -48,9 +52,13 @@ class AuthenticateWithSessionCookieFailureReason(Enum): INVALID_SESSION_COOKIE = "invalid_session_cookie" NO_SESSION_COOKIE_PROVIDED = "no_session_cookie_provided" MFA_CHALLENGE_REQUIRED = "mfa_challenge_required" + MFA_ENROLLMENT_REQUIRED = "mfa_enrollment_required" SSO_REQUIRED = "sso_required" EMAIL_VERIFICATION_REQUIRED = "email_verification_required" ORGANIZATION_SELECTION_REQUIRED = "organization_selection_required" + ORGANIZATION_AUTH_METHODS_REQUIRED = "organization_auth_methods_required" + AUTHENTICATION_METHOD_NOT_ALLOWED = "authentication_method_not_allowed" + RADAR_CHALLENGE_REQUIRED = "radar_challenge_required" REFRESH_DENIED = "refresh_denied" REFRESH_NETWORK_ERROR = "refresh_network_error" @@ -65,6 +73,8 @@ def _map_refresh_exception_to_reason( """ if isinstance(exc, MfaChallengeError): return AuthenticateWithSessionCookieFailureReason.MFA_CHALLENGE_REQUIRED + if isinstance(exc, MfaEnrollmentError): + return AuthenticateWithSessionCookieFailureReason.MFA_ENROLLMENT_REQUIRED if isinstance(exc, SsoRequiredError): return AuthenticateWithSessionCookieFailureReason.SSO_REQUIRED if isinstance(exc, EmailVerificationRequiredError): @@ -73,6 +83,14 @@ def _map_refresh_exception_to_reason( return ( AuthenticateWithSessionCookieFailureReason.ORGANIZATION_SELECTION_REQUIRED ) + if isinstance(exc, OrganizationAuthMethodsRequiredError): + return AuthenticateWithSessionCookieFailureReason.ORGANIZATION_AUTH_METHODS_REQUIRED + if isinstance(exc, AuthenticationMethodNotAllowedError): + return ( + AuthenticateWithSessionCookieFailureReason.AUTHENTICATION_METHOD_NOT_ALLOWED + ) + if isinstance(exc, RadarChallengeError): + return AuthenticateWithSessionCookieFailureReason.RADAR_CHALLENGE_REQUIRED if isinstance(exc, AuthenticationError): return AuthenticateWithSessionCookieFailureReason.REFRESH_DENIED if isinstance(exc, (WorkOSConnectionError, WorkOSTimeoutError)): diff --git a/tests/test_session.py b/tests/test_session.py index 3355bfb2..15e44e78 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -7,6 +7,19 @@ from cryptography.hazmat.primitives.asymmetric import rsa from workos import WorkOSClient +from workos._errors import ( + AuthenticationError, + AuthenticationMethodNotAllowedError, + EmailVerificationRequiredError, + MfaChallengeError, + MfaEnrollmentError, + OrganizationAuthMethodsRequiredError, + OrganizationSelectionRequiredError, + RadarChallengeError, + SsoRequiredError, + WorkOSConnectionError, + WorkOSTimeoutError, +) from workos.session import ( AsyncSession, AuthenticateWithSessionCookieErrorResponse, @@ -14,6 +27,7 @@ AuthenticateWithSessionCookieSuccessResponse, RefreshWithSessionCookieErrorResponse, Session, + _map_refresh_exception_to_reason, seal_data, seal_session_from_auth_response, unseal_data, @@ -212,6 +226,64 @@ def test_session_refresh_missing_refresh_token(self): assert isinstance(result, RefreshWithSessionCookieErrorResponse) +class TestMapRefreshExceptionToReason: + @pytest.mark.parametrize( + "exc, expected", + [ + ( + MfaChallengeError("mfa challenge"), + AuthenticateWithSessionCookieFailureReason.MFA_CHALLENGE_REQUIRED, + ), + ( + MfaEnrollmentError("mfa enrollment"), + AuthenticateWithSessionCookieFailureReason.MFA_ENROLLMENT_REQUIRED, + ), + ( + SsoRequiredError("sso required"), + AuthenticateWithSessionCookieFailureReason.SSO_REQUIRED, + ), + ( + EmailVerificationRequiredError("email verification required"), + AuthenticateWithSessionCookieFailureReason.EMAIL_VERIFICATION_REQUIRED, + ), + ( + OrganizationSelectionRequiredError("org selection required"), + AuthenticateWithSessionCookieFailureReason.ORGANIZATION_SELECTION_REQUIRED, + ), + ( + OrganizationAuthMethodsRequiredError("org auth methods required"), + AuthenticateWithSessionCookieFailureReason.ORGANIZATION_AUTH_METHODS_REQUIRED, + ), + ( + AuthenticationMethodNotAllowedError("method not allowed"), + AuthenticateWithSessionCookieFailureReason.AUTHENTICATION_METHOD_NOT_ALLOWED, + ), + ( + RadarChallengeError("radar challenge"), + AuthenticateWithSessionCookieFailureReason.RADAR_CHALLENGE_REQUIRED, + ), + ( + AuthenticationError("unauthorized"), + AuthenticateWithSessionCookieFailureReason.REFRESH_DENIED, + ), + ( + WorkOSConnectionError("connection failed"), + AuthenticateWithSessionCookieFailureReason.REFRESH_NETWORK_ERROR, + ), + ( + WorkOSTimeoutError("timeout"), + AuthenticateWithSessionCookieFailureReason.REFRESH_NETWORK_ERROR, + ), + ], + ) + def test_known_exceptions_map_to_reason(self, exc, expected): + assert _map_refresh_exception_to_reason(exc) == expected + + def test_unknown_exception_falls_back_to_string(self): + result = _map_refresh_exception_to_reason(RuntimeError("boom")) + assert result == "boom" + + @pytest.mark.asyncio class TestAsyncSession: def _mock_jwks(self, public_key): From 79278da2863ad3368c019519909ee959b9a46ce6 Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Fri, 8 May 2026 12:33:16 -0700 Subject: [PATCH 11/13] test(webhooks): assert tolerance=0 honours zero-second window Regression guard for the standalone _verification.verify_header tolerance handling: a 1-second-old signature with tolerance=0 must be rejected, so reverting to the prior `tolerance or DEFAULT_TOLERANCE` form would fail. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/test_webhook_verification.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_webhook_verification.py b/tests/test_webhook_verification.py index 8e228b39..ba98d5f1 100644 --- a/tests/test_webhook_verification.py +++ b/tests/test_webhook_verification.py @@ -179,3 +179,14 @@ def test_standalone_verify_header_future_timestamp(self): secret=SECRET, tolerance=180, ) + + def test_standalone_verify_header_tolerance_zero_rejects_old_timestamp(self): + old_ts = int((time.time() - 1) * 1000) + sig = _make_sig_header(SAMPLE_EVENT, SECRET, old_ts) + with pytest.raises(ValueError, match="tolerance zone"): + standalone_verify_header( + event_body=SAMPLE_EVENT.encode("utf-8"), + event_signature=sig, + secret=SECRET, + tolerance=0, + ) From 29c595dea314e2b18b2dda5dbd058f607fcc57aa Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Fri, 8 May 2026 12:43:01 -0700 Subject: [PATCH 12/13] Revert "fix: preserve empty AAD in vault crypto helpers" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 0c12462f71fa898891ea43300ba294aed3540dd7. The original truthy `if aad:` guard was self-consistent within the SDK (encrypt and decrypt agreed: empty string and None both meant "no AAD"), and matches the convention used by sibling WorkOS SDKs. Switching to `is not None` made `aad=""` semantically distinct from `aad=None`, which is cryptographically more precise but breaks decryption of every vault ciphertext previously encrypted with `associated_data=""` once a caller upgrades — the new SDK calls `authenticate_additional_data(b"")` and the GCM tag (computed without AAD on the encrypt side) will not verify. The threat being closed (a caller passing "" expecting an empty AAD to be authenticated, but silently getting none) is a narrow integrity misperception, not an exploitable hole, and there is no signal anyone in the wild relies on it. The wire-format break is the larger harm. A coordinated cross-SDK change with a versioned ciphertext header is the right path if this ever needs to be revisited. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/workos/vault.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/workos/vault.py b/src/workos/vault.py index 832682b1..06740d2e 100644 --- a/src/workos/vault.py +++ b/src/workos/vault.py @@ -240,7 +240,7 @@ def _aes_gcm_encrypt( encryptor = Cipher( algorithms.AES(key), modes.GCM(iv), backend=default_backend() ).encryptor() - if aad is not None: + if aad: encryptor.authenticate_additional_data(aad) ciphertext = encryptor.update(plaintext) + encryptor.finalize() return {"ciphertext": ciphertext, "iv": iv, "tag": encryptor.tag} @@ -256,7 +256,7 @@ def _aes_gcm_decrypt( decryptor = Cipher( algorithms.AES(key), modes.GCM(iv, tag), backend=default_backend() ).decryptor() - if aad is not None: + if aad: decryptor.authenticate_additional_data(aad) return decryptor.update(ciphertext) + decryptor.finalize() @@ -470,9 +470,7 @@ def encrypt( key = base64.b64decode(key_pair.data_key.key) key_blob = base64.b64decode(key_pair.encrypted_keys) prefix_len_buffer = _encode_u32_leb128(len(key_blob)) - aad_buffer = ( - associated_data.encode("utf-8") if associated_data is not None else None - ) + aad_buffer = associated_data.encode("utf-8") if associated_data else None iv = os.urandom(12) result = _aes_gcm_encrypt(data.encode("utf-8"), key, iv, aad_buffer) @@ -494,9 +492,7 @@ def decrypt( data_key = self.decrypt_data_key(keys=decoded.keys) key = base64.b64decode(data_key.key) - aad_buffer = ( - associated_data.encode("utf-8") if associated_data is not None else None - ) + aad_buffer = associated_data.encode("utf-8") if associated_data else None decrypted_bytes = _aes_gcm_decrypt( ciphertext=decoded.ciphertext, @@ -653,9 +649,7 @@ async def encrypt( key = base64.b64decode(key_pair.data_key.key) key_blob = base64.b64decode(key_pair.encrypted_keys) prefix_len_buffer = _encode_u32_leb128(len(key_blob)) - aad_buffer = ( - associated_data.encode("utf-8") if associated_data is not None else None - ) + aad_buffer = associated_data.encode("utf-8") if associated_data else None iv = os.urandom(12) result = _aes_gcm_encrypt(data.encode("utf-8"), key, iv, aad_buffer) @@ -676,9 +670,7 @@ async def decrypt( data_key = await self.decrypt_data_key(keys=decoded.keys) key = base64.b64decode(data_key.key) - aad_buffer = ( - associated_data.encode("utf-8") if associated_data is not None else None - ) + aad_buffer = associated_data.encode("utf-8") if associated_data else None decrypted_bytes = _aes_gcm_decrypt( ciphertext=decoded.ciphertext, From d65b3146c77160cb0d9db1d0dd3774f0553073a8 Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Mon, 11 May 2026 12:36:37 -0400 Subject: [PATCH 13/13] fix(client): pass path as sequence of segments to prevent traversal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Callers now pass each path component separately (e.g. ("organizations", organization_id)) so _encode_path can URL-encode each element with safe="" before joining. A "/" or ".." inside a caller-supplied id is now encoded as %2F / %2E%2E and cannot escape its intended segment — the structural fix Greptile flagged on PR #654. - _base_client.py: request/request_raw/request_list/request_page and build_url (sync + async) take Sequence[str]; _encode_path raises TypeError on a bare str so a forgotten tuple wrapper fails loudly. - Hand-maintained files (session, passwordless, vault, and PKCE/SSO helpers fenced inside generated resources) converted to tuples. - Regenerated all resource files via oagen; the urllib.parse.quote import is no longer needed in generated code. Co-Authored-By: Claude Opus 4.7 (1M context) --- .oagen-manifest.json | 2 +- src/workos/_base_client.py | 70 +-- src/workos/admin_portal/_resource.py | 4 +- src/workos/api_keys/_resource.py | 17 +- src/workos/audit_logs/_resource.py | 33 +- src/workos/authorization/_resource.py | 413 ++++++++++++++---- src/workos/connect/_resource.py | 45 +- src/workos/directory_sync/_resource.py | 29 +- src/workos/events/_resource.py | 4 +- src/workos/feature_flags/_resource.py | 33 +- src/workos/groups/_resource.py | 71 ++- src/workos/multi_factor_auth/_resource.py | 29 +- src/workos/organization_domains/_resource.py | 17 +- src/workos/organizations/_resource.py | 29 +- src/workos/passwordless.py | 8 +- src/workos/pipes/_resource.py | 45 +- src/workos/radar/_resource.py | 17 +- src/workos/session.py | 4 +- src/workos/sso/_resource.py | 41 +- src/workos/user_management/_resource.py | 233 +++++----- .../_resource.py | 5 +- src/workos/vault.py | 40 +- src/workos/webhooks/_resource.py | 17 +- src/workos/widgets/_resource.py | 4 +- tests/test_generated_client.py | 94 ++-- 25 files changed, 813 insertions(+), 491 deletions(-) diff --git a/.oagen-manifest.json b/.oagen-manifest.json index 6dc3b99f..5232cb40 100644 --- a/.oagen-manifest.json +++ b/.oagen-manifest.json @@ -1,7 +1,7 @@ { "version": 2, "language": "python", - "generatedAt": "2026-05-06T23:30:58.090Z", + "generatedAt": "2026-05-11T15:56:51.952Z", "files": [ "src/workos/_client.py", "src/workos/admin_portal/__init__.py", diff --git a/src/workos/_base_client.py b/src/workos/_base_client.py index 1becc09d..198c29d6 100644 --- a/src/workos/_base_client.py +++ b/src/workos/_base_client.py @@ -9,7 +9,7 @@ import random from datetime import datetime, timezone from email.utils import parsedate_to_datetime -from typing import Any, Dict, Optional, Type, cast, overload +from typing import Any, Dict, Optional, Sequence, Type, cast, overload from urllib.parse import quote import httpx @@ -88,12 +88,14 @@ def base_url(self) -> str: """The base URL for API requests.""" return self._base_url - def build_url(self, path: str, params: Optional[Dict[str, Any]] = None) -> str: + def build_url( + self, path: Sequence[str], params: Optional[Dict[str, Any]] = None + ) -> str: """Build a full URL with query parameters for redirect/authorization endpoints.""" from urllib.parse import urlencode base = self._base_url.rstrip("/") - url = f"{base}/{path}" + url = f"{base}/{self._encode_path(path)}" if params: url = f"{url}?{urlencode(params)}" return url @@ -137,19 +139,25 @@ def _resolve_base_url(self, request_options: Optional[RequestOptions]) -> str: return self._base_url.rstrip("/") @staticmethod - def _encode_path(path: str) -> str: - """Percent-encode each path segment to prevent path-traversal/injection. - - Splits on ``/`` and applies ``urllib.parse.quote(seg, safe='')`` to each - segment so that user-supplied IDs containing reserved characters (``/``, - ``?``, ``#``, ``%``, etc.) cannot escape their intended segment. The - leading slash (if any) is preserved. + def _encode_path(path: Sequence[str]) -> str: + """Percent-encode each path segment and join with ``/``. + + Callers pass each path component as a separate element (e.g. + ``("organizations", organization_id)``). Each element is URL-encoded + with ``safe=""`` so a caller-supplied id containing ``/``, ``?``, + ``#``, ``%``, or ``..`` cannot escape its intended segment — this is + the structural protection against forged cross-resource API requests + under the application's API key. + + A bare string would be silently iterable as a sequence of single + characters; we reject it explicitly so a forgotten tuple wrapper at a + call site fails loudly instead of producing a per-character URL. """ - if not path: - return path - leading = "/" if path.startswith("/") else "" - body = path[1:] if leading else path - return leading + "/".join(quote(seg, safe="") for seg in body.split("/")) + if isinstance(path, str): + raise TypeError( + "path must be a sequence of segments (e.g. a tuple), not a str" + ) + return "/".join(quote(str(seg), safe="") for seg in path) def _resolve_timeout(self, request_options: Optional[RequestOptions]) -> float: timeout = self._request_timeout @@ -401,7 +409,7 @@ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: def request( self, method: str, - path: str, + path: Sequence[str], *, model: Type[D], params: Optional[Dict[str, Any]] = ..., @@ -414,7 +422,7 @@ def request( def request( self, method: str, - path: str, + path: Sequence[str], *, model: None = ..., params: Optional[Dict[str, Any]] = ..., @@ -426,7 +434,7 @@ def request( def request( self, method: str, - path: str, + path: Sequence[str], *, params: Optional[Dict[str, Any]] = None, body: Optional[Dict[str, Any]] = None, @@ -435,7 +443,7 @@ def request( request_options: Optional[RequestOptions] = None, ) -> Any: """Make an HTTP request with retry logic.""" - url = f"{self._resolve_base_url(request_options)}/{self._encode_path(path).lstrip('/')}" + url = f"{self._resolve_base_url(request_options)}/{self._encode_path(path)}" headers = self._build_headers(method, idempotency_key, request_options) timeout = self._resolve_timeout(request_options) max_retries = self._resolve_max_retries(request_options) @@ -482,7 +490,7 @@ def request( def request_raw( self, method: str, - path: str, + path: Sequence[str], *, params: Optional[Dict[str, Any]] = None, body: Optional[Dict[str, Any]] = None, @@ -507,7 +515,7 @@ def request_raw( def request_list( self, method: str, - path: str, + path: Sequence[str], *, params: Optional[Dict[str, Any]] = None, body: Optional[Dict[str, Any]] = None, @@ -529,14 +537,14 @@ def request_list( ) if not isinstance(result, list): raise WorkOSError( - f"Expected array response from {method.upper()} /{path}, got {type(result).__name__}" + f"Expected array response from {method.upper()} /{'/'.join(path)}, got {type(result).__name__}" ) return result def request_page( self, method: str, - path: str, + path: Sequence[str], *, model: Type[D], params: Optional[Dict[str, Any]] = None, @@ -628,7 +636,7 @@ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: async def request( self, method: str, - path: str, + path: Sequence[str], *, model: Type[D], params: Optional[Dict[str, Any]] = ..., @@ -641,7 +649,7 @@ async def request( async def request( self, method: str, - path: str, + path: Sequence[str], *, model: None = ..., params: Optional[Dict[str, Any]] = ..., @@ -653,7 +661,7 @@ async def request( async def request( self, method: str, - path: str, + path: Sequence[str], *, params: Optional[Dict[str, Any]] = None, body: Optional[Dict[str, Any]] = None, @@ -662,7 +670,7 @@ async def request( request_options: Optional[RequestOptions] = None, ) -> Any: """Make an async HTTP request with retry logic.""" - url = f"{self._resolve_base_url(request_options)}/{self._encode_path(path).lstrip('/')}" + url = f"{self._resolve_base_url(request_options)}/{self._encode_path(path)}" headers = self._build_headers(method, idempotency_key, request_options) timeout = self._resolve_timeout(request_options) max_retries = self._resolve_max_retries(request_options) @@ -709,7 +717,7 @@ async def request( async def request_raw( self, method: str, - path: str, + path: Sequence[str], *, params: Optional[Dict[str, Any]] = None, body: Optional[Dict[str, Any]] = None, @@ -734,7 +742,7 @@ async def request_raw( async def request_list( self, method: str, - path: str, + path: Sequence[str], *, params: Optional[Dict[str, Any]] = None, body: Optional[Dict[str, Any]] = None, @@ -756,14 +764,14 @@ async def request_list( ) if not isinstance(result, list): raise WorkOSError( - f"Expected array response from {method.upper()} /{path}, got {type(result).__name__}" + f"Expected array response from {method.upper()} /{'/'.join(path)}, got {type(result).__name__}" ) return result async def request_page( self, method: str, - path: str, + path: Sequence[str], *, model: Type[D], params: Optional[Dict[str, Any]] = None, diff --git a/src/workos/admin_portal/_resource.py b/src/workos/admin_portal/_resource.py index 57e4233d..c6bf0ec9 100644 --- a/src/workos/admin_portal/_resource.py +++ b/src/workos/admin_portal/_resource.py @@ -77,7 +77,7 @@ def generate_link( } return self._client.request( method="post", - path="portal/generate_link", + path=("portal", "generate_link"), body=body, model=PortalLinkResponse, request_options=request_options, @@ -149,7 +149,7 @@ async def generate_link( } return await self._client.request( method="post", - path="portal/generate_link", + path=("portal", "generate_link"), body=body, model=PortalLinkResponse, request_options=request_options, diff --git a/src/workos/api_keys/_resource.py b/src/workos/api_keys/_resource.py index e72adfb6..b0598e73 100644 --- a/src/workos/api_keys/_resource.py +++ b/src/workos/api_keys/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -67,7 +66,7 @@ def list_organization_api_keys( } return self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/api_keys", + path=("organizations", str(organization_id), "api_keys"), model=OrganizationApiKey, params=params, request_options=request_options, @@ -111,7 +110,7 @@ def create_organization_api_key( } return self._client.request( method="post", - path=f"organizations/{quote(str(organization_id), safe='')}/api_keys", + path=("organizations", str(organization_id), "api_keys"), body=body, model=OrganizationApiKeyWithValue, request_options=request_options, @@ -145,7 +144,7 @@ def create_validation( } return self._client.request( method="post", - path="api_keys/validations", + path=("api_keys", "validations"), body=body, model=ApiKeyValidationResponse, request_options=request_options, @@ -173,7 +172,7 @@ def delete_api_key( """ self._client.request( method="delete", - path=f"api_keys/{quote(str(id), safe='')}", + path=("api_keys", str(id)), request_options=request_options, ) @@ -227,7 +226,7 @@ async def list_organization_api_keys( } return await self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/api_keys", + path=("organizations", str(organization_id), "api_keys"), model=OrganizationApiKey, params=params, request_options=request_options, @@ -271,7 +270,7 @@ async def create_organization_api_key( } return await self._client.request( method="post", - path=f"organizations/{quote(str(organization_id), safe='')}/api_keys", + path=("organizations", str(organization_id), "api_keys"), body=body, model=OrganizationApiKeyWithValue, request_options=request_options, @@ -305,7 +304,7 @@ async def create_validation( } return await self._client.request( method="post", - path="api_keys/validations", + path=("api_keys", "validations"), body=body, model=ApiKeyValidationResponse, request_options=request_options, @@ -333,6 +332,6 @@ async def delete_api_key( """ await self._client.request( method="delete", - path=f"api_keys/{quote(str(id), safe='')}", + path=("api_keys", str(id)), request_options=request_options, ) diff --git a/src/workos/audit_logs/_resource.py b/src/workos/audit_logs/_resource.py index 9c7dc15b..db5352b6 100644 --- a/src/workos/audit_logs/_resource.py +++ b/src/workos/audit_logs/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -54,7 +53,7 @@ def get_organization_audit_logs_retention( """ return self._client.request( method="get", - path=f"organizations/{quote(str(id), safe='')}/audit_logs_retention", + path=("organizations", str(id), "audit_logs_retention"), model=AuditLogsRetentionJson, request_options=request_options, ) @@ -90,7 +89,7 @@ def update_organization_audit_logs_retention( } return self._client.request( method="put", - path=f"organizations/{quote(str(id), safe='')}/audit_logs_retention", + path=("organizations", str(id), "audit_logs_retention"), body=body, model=AuditLogsRetentionJson, request_options=request_options, @@ -138,7 +137,7 @@ def list_actions( } return self._client.request_page( method="get", - path="audit_logs/actions", + path=("audit_logs", "actions"), model=AuditLogActionJson, params=params, request_options=request_options, @@ -188,7 +187,7 @@ def list_action_schemas( } return self._client.request_page( method="get", - path=f"audit_logs/actions/{quote(str(action_name), safe='')}/schemas", + path=("audit_logs", "actions", str(action_name), "schemas"), model=AuditLogSchemaJson, params=params, request_options=request_options, @@ -234,7 +233,7 @@ def create_schema( } return self._client.request( method="post", - path=f"audit_logs/actions/{quote(str(action_name), safe='')}/schemas", + path=("audit_logs", "actions", str(action_name), "schemas"), body=body, model=AuditLogSchemaJson, request_options=request_options, @@ -281,7 +280,7 @@ def create_event( } return self._client.request( method="post", - path="audit_logs/events", + path=("audit_logs", "events"), body=body, model=AuditLogEventCreateResponse, idempotency_key=idempotency_key, @@ -341,7 +340,7 @@ def create_export( } return self._client.request( method="post", - path="audit_logs/exports", + path=("audit_logs", "exports"), body=body, model=AuditLogExportJson, request_options=request_options, @@ -372,7 +371,7 @@ def get_export( """ return self._client.request( method="get", - path=f"audit_logs/exports/{quote(str(audit_log_export_id), safe='')}", + path=("audit_logs", "exports", str(audit_log_export_id)), model=AuditLogExportJson, request_options=request_options, ) @@ -409,7 +408,7 @@ async def get_organization_audit_logs_retention( """ return await self._client.request( method="get", - path=f"organizations/{quote(str(id), safe='')}/audit_logs_retention", + path=("organizations", str(id), "audit_logs_retention"), model=AuditLogsRetentionJson, request_options=request_options, ) @@ -445,7 +444,7 @@ async def update_organization_audit_logs_retention( } return await self._client.request( method="put", - path=f"organizations/{quote(str(id), safe='')}/audit_logs_retention", + path=("organizations", str(id), "audit_logs_retention"), body=body, model=AuditLogsRetentionJson, request_options=request_options, @@ -493,7 +492,7 @@ async def list_actions( } return await self._client.request_page( method="get", - path="audit_logs/actions", + path=("audit_logs", "actions"), model=AuditLogActionJson, params=params, request_options=request_options, @@ -543,7 +542,7 @@ async def list_action_schemas( } return await self._client.request_page( method="get", - path=f"audit_logs/actions/{quote(str(action_name), safe='')}/schemas", + path=("audit_logs", "actions", str(action_name), "schemas"), model=AuditLogSchemaJson, params=params, request_options=request_options, @@ -589,7 +588,7 @@ async def create_schema( } return await self._client.request( method="post", - path=f"audit_logs/actions/{quote(str(action_name), safe='')}/schemas", + path=("audit_logs", "actions", str(action_name), "schemas"), body=body, model=AuditLogSchemaJson, request_options=request_options, @@ -636,7 +635,7 @@ async def create_event( } return await self._client.request( method="post", - path="audit_logs/events", + path=("audit_logs", "events"), body=body, model=AuditLogEventCreateResponse, idempotency_key=idempotency_key, @@ -696,7 +695,7 @@ async def create_export( } return await self._client.request( method="post", - path="audit_logs/exports", + path=("audit_logs", "exports"), body=body, model=AuditLogExportJson, request_options=request_options, @@ -727,7 +726,7 @@ async def get_export( """ return await self._client.request( method="get", - path=f"audit_logs/exports/{quote(str(audit_log_export_id), safe='')}", + path=("audit_logs", "exports", str(audit_log_export_id)), model=AuditLogExportJson, request_options=request_options, ) diff --git a/src/workos/authorization/_resource.py b/src/workos/authorization/_resource.py index b55c3acc..cd7afcd8 100644 --- a/src/workos/authorization/_resource.py +++ b/src/workos/authorization/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -117,7 +116,12 @@ def check( body["resource_type_slug"] = resource_target.resource_type_slug return self._client.request( method="post", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/check", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "check", + ), body=body, model=AuthorizationCheck, request_options=request_options, @@ -185,7 +189,12 @@ def list_resources_for_membership( ) return self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/resources", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "resources", + ), model=AuthorizationResource, params=params, request_options=request_options, @@ -238,7 +247,14 @@ def list_effective_permissions( } return self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/resources/{quote(str(resource_id), safe='')}/permissions", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "resources", + str(resource_id), + "permissions", + ), model=AuthorizationPermission, params=params, request_options=request_options, @@ -293,7 +309,15 @@ def list_effective_permissions_by_external_id( } return self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}/permissions", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "resources", + str(resource_type_slug), + str(external_id), + "permissions", + ), model=AuthorizationPermission, params=params, request_options=request_options, @@ -343,7 +367,12 @@ def list_role_assignments( } return self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + ), model=UserRoleAssignment, params=params, request_options=request_options, @@ -388,7 +417,12 @@ def assign_role( body["resource_type_slug"] = resource_target.resource_type_slug return self._client.request( method="post", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + ), body=body, model=UserRoleAssignment, request_options=request_options, @@ -430,7 +464,12 @@ def remove_role( body["resource_type_slug"] = resource_target.resource_type_slug self._client.request( method="delete", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + ), body=body, request_options=request_options, ) @@ -460,7 +499,13 @@ def remove_role_assignment( """ self._client.request( method="delete", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments/{quote(str(role_assignment_id), safe='')}", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + str(role_assignment_id), + ), request_options=request_options, ) @@ -490,7 +535,7 @@ def list_organization_roles( """ return self._client.request( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles", + path=("authorization", "organizations", str(organization_id), "roles"), model=RoleList, request_options=request_options, ) @@ -542,7 +587,7 @@ def create_organization_role( } return self._client.request( method="post", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles", + path=("authorization", "organizations", str(organization_id), "roles"), body=body, model=Role, request_options=request_options, @@ -576,7 +621,13 @@ def get_organization_role( """ return self._client.request( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + ), model=Role, request_options=request_options, ) @@ -623,7 +674,13 @@ def update_organization_role( } return self._client.request( method="patch", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + ), body=body, model=Role, request_options=request_options, @@ -656,7 +713,13 @@ def delete_organization_role( """ self._client.request( method="delete", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + ), request_options=request_options, ) @@ -695,7 +758,14 @@ def add_organization_role_permission( } return self._client.request( method="post", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}/permissions", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + "permissions", + ), body=body, model=Role, request_options=request_options, @@ -735,7 +805,14 @@ def set_organization_role_permissions( } return self._client.request( method="put", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}/permissions", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + "permissions", + ), body=body, model=Role, request_options=request_options, @@ -768,7 +845,15 @@ def remove_organization_role_permission( """ self._client.request( method="delete", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}/permissions/{quote(str(permission_slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + "permissions", + str(permission_slug), + ), request_options=request_options, ) @@ -802,7 +887,14 @@ def get_resource_by_external_id( """ return self._client.request( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + ), model=AuthorizationResource, request_options=request_options, ) @@ -866,7 +958,14 @@ def update_resource_by_external_id( ) return self._client.request( method="patch", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + ), body=body, model=AuthorizationResource, request_options=request_options, @@ -910,7 +1009,14 @@ def delete_resource_by_external_id( } self._client.request( method="delete", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + ), params=params, request_options=request_options, ) @@ -973,7 +1079,15 @@ def list_memberships_for_resource_by_external_id( } return self._client.request_page( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}/organization_memberships", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + "organization_memberships", + ), model=UserOrganizationMembershipBaseListData, params=params, request_options=request_options, @@ -1027,7 +1141,15 @@ def list_role_assignments_for_resource_by_external_id( } return self._client.request_page( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}/role_assignments", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + "role_assignments", + ), model=UserRoleAssignment, params=params, request_options=request_options, @@ -1095,7 +1217,7 @@ def list_resources( params["parent_external_id"] = parent.parent_external_id return self._client.request_page( method="get", - path="authorization/resources", + path=("authorization", "resources"), model=AuthorizationResource, params=params, request_options=request_options, @@ -1163,7 +1285,7 @@ def create_resource( ) return self._client.request( method="post", - path="authorization/resources", + path=("authorization", "resources"), body=body, model=AuthorizationResource, request_options=request_options, @@ -1196,7 +1318,7 @@ def get_resource( """ return self._client.request( method="get", - path=f"authorization/resources/{quote(str(resource_id), safe='')}", + path=("authorization", "resources", str(resource_id)), model=AuthorizationResource, request_options=request_options, ) @@ -1256,7 +1378,7 @@ def update_resource( ) return self._client.request( method="patch", - path=f"authorization/resources/{quote(str(resource_id), safe='')}", + path=("authorization", "resources", str(resource_id)), body=body, model=AuthorizationResource, request_options=request_options, @@ -1296,7 +1418,7 @@ def delete_resource( } self._client.request( method="delete", - path=f"authorization/resources/{quote(str(resource_id), safe='')}", + path=("authorization", "resources", str(resource_id)), params=params, request_options=request_options, ) @@ -1355,7 +1477,12 @@ def list_memberships_for_resource( } return self._client.request_page( method="get", - path=f"authorization/resources/{quote(str(resource_id), safe='')}/organization_memberships", + path=( + "authorization", + "resources", + str(resource_id), + "organization_memberships", + ), model=UserOrganizationMembershipBaseListData, params=params, request_options=request_options, @@ -1405,7 +1532,7 @@ def list_role_assignments_for_resource( } return self._client.request_page( method="get", - path=f"authorization/resources/{quote(str(resource_id), safe='')}/role_assignments", + path=("authorization", "resources", str(resource_id), "role_assignments"), model=UserRoleAssignment, params=params, request_options=request_options, @@ -1431,7 +1558,7 @@ def list_environment_roles( """ return self._client.request( method="get", - path="authorization/roles", + path=("authorization", "roles"), model=RoleList, request_options=request_options, ) @@ -1481,7 +1608,7 @@ def create_environment_role( } return self._client.request( method="post", - path="authorization/roles", + path=("authorization", "roles"), body=body, model=Role, request_options=request_options, @@ -1513,7 +1640,7 @@ def get_environment_role( """ return self._client.request( method="get", - path=f"authorization/roles/{quote(str(slug), safe='')}", + path=("authorization", "roles", str(slug)), model=Role, request_options=request_options, ) @@ -1558,7 +1685,7 @@ def update_environment_role( } return self._client.request( method="patch", - path=f"authorization/roles/{quote(str(slug), safe='')}", + path=("authorization", "roles", str(slug)), body=body, model=Role, request_options=request_options, @@ -1597,7 +1724,7 @@ def add_environment_role_permission( } return self._client.request( method="post", - path=f"authorization/roles/{quote(str(slug), safe='')}/permissions", + path=("authorization", "roles", str(slug), "permissions"), body=body, model=Role, request_options=request_options, @@ -1636,7 +1763,7 @@ def set_environment_role_permissions( } return self._client.request( method="put", - path=f"authorization/roles/{quote(str(slug), safe='')}/permissions", + path=("authorization", "roles", str(slug), "permissions"), body=body, model=Role, request_options=request_options, @@ -1683,7 +1810,7 @@ def list_permissions( } return self._client.request_page( method="get", - path="authorization/permissions", + path=("authorization", "permissions"), model=AuthorizationPermission, params=params, request_options=request_options, @@ -1733,7 +1860,7 @@ def create_permission( } return self._client.request( method="post", - path="authorization/permissions", + path=("authorization", "permissions"), body=body, model=Permission, request_options=request_options, @@ -1764,7 +1891,7 @@ def get_permission( """ return self._client.request( method="get", - path=f"authorization/permissions/{quote(str(slug), safe='')}", + path=("authorization", "permissions", str(slug)), model=AuthorizationPermission, request_options=request_options, ) @@ -1808,7 +1935,7 @@ def update_permission( } return self._client.request( method="patch", - path=f"authorization/permissions/{quote(str(slug), safe='')}", + path=("authorization", "permissions", str(slug)), body=body, model=AuthorizationPermission, request_options=request_options, @@ -1837,7 +1964,7 @@ def delete_permission( """ self._client.request( method="delete", - path=f"authorization/permissions/{quote(str(slug), safe='')}", + path=("authorization", "permissions", str(slug)), request_options=request_options, ) @@ -1887,7 +2014,12 @@ async def check( body["resource_type_slug"] = resource_target.resource_type_slug return await self._client.request( method="post", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/check", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "check", + ), body=body, model=AuthorizationCheck, request_options=request_options, @@ -1955,7 +2087,12 @@ async def list_resources_for_membership( ) return await self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/resources", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "resources", + ), model=AuthorizationResource, params=params, request_options=request_options, @@ -2008,7 +2145,14 @@ async def list_effective_permissions( } return await self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/resources/{quote(str(resource_id), safe='')}/permissions", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "resources", + str(resource_id), + "permissions", + ), model=AuthorizationPermission, params=params, request_options=request_options, @@ -2063,7 +2207,15 @@ async def list_effective_permissions_by_external_id( } return await self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}/permissions", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "resources", + str(resource_type_slug), + str(external_id), + "permissions", + ), model=AuthorizationPermission, params=params, request_options=request_options, @@ -2113,7 +2265,12 @@ async def list_role_assignments( } return await self._client.request_page( method="get", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + ), model=UserRoleAssignment, params=params, request_options=request_options, @@ -2158,7 +2315,12 @@ async def assign_role( body["resource_type_slug"] = resource_target.resource_type_slug return await self._client.request( method="post", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + ), body=body, model=UserRoleAssignment, request_options=request_options, @@ -2200,7 +2362,12 @@ async def remove_role( body["resource_type_slug"] = resource_target.resource_type_slug await self._client.request( method="delete", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + ), body=body, request_options=request_options, ) @@ -2230,7 +2397,13 @@ async def remove_role_assignment( """ await self._client.request( method="delete", - path=f"authorization/organization_memberships/{quote(str(organization_membership_id), safe='')}/role_assignments/{quote(str(role_assignment_id), safe='')}", + path=( + "authorization", + "organization_memberships", + str(organization_membership_id), + "role_assignments", + str(role_assignment_id), + ), request_options=request_options, ) @@ -2260,7 +2433,7 @@ async def list_organization_roles( """ return await self._client.request( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles", + path=("authorization", "organizations", str(organization_id), "roles"), model=RoleList, request_options=request_options, ) @@ -2312,7 +2485,7 @@ async def create_organization_role( } return await self._client.request( method="post", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles", + path=("authorization", "organizations", str(organization_id), "roles"), body=body, model=Role, request_options=request_options, @@ -2346,7 +2519,13 @@ async def get_organization_role( """ return await self._client.request( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + ), model=Role, request_options=request_options, ) @@ -2393,7 +2572,13 @@ async def update_organization_role( } return await self._client.request( method="patch", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + ), body=body, model=Role, request_options=request_options, @@ -2426,7 +2611,13 @@ async def delete_organization_role( """ await self._client.request( method="delete", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + ), request_options=request_options, ) @@ -2465,7 +2656,14 @@ async def add_organization_role_permission( } return await self._client.request( method="post", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}/permissions", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + "permissions", + ), body=body, model=Role, request_options=request_options, @@ -2505,7 +2703,14 @@ async def set_organization_role_permissions( } return await self._client.request( method="put", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}/permissions", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + "permissions", + ), body=body, model=Role, request_options=request_options, @@ -2538,7 +2743,15 @@ async def remove_organization_role_permission( """ await self._client.request( method="delete", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/roles/{quote(str(slug), safe='')}/permissions/{quote(str(permission_slug), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "roles", + str(slug), + "permissions", + str(permission_slug), + ), request_options=request_options, ) @@ -2572,7 +2785,14 @@ async def get_resource_by_external_id( """ return await self._client.request( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + ), model=AuthorizationResource, request_options=request_options, ) @@ -2636,7 +2856,14 @@ async def update_resource_by_external_id( ) return await self._client.request( method="patch", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + ), body=body, model=AuthorizationResource, request_options=request_options, @@ -2680,7 +2907,14 @@ async def delete_resource_by_external_id( } await self._client.request( method="delete", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + ), params=params, request_options=request_options, ) @@ -2743,7 +2977,15 @@ async def list_memberships_for_resource_by_external_id( } return await self._client.request_page( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}/organization_memberships", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + "organization_memberships", + ), model=UserOrganizationMembershipBaseListData, params=params, request_options=request_options, @@ -2797,7 +3039,15 @@ async def list_role_assignments_for_resource_by_external_id( } return await self._client.request_page( method="get", - path=f"authorization/organizations/{quote(str(organization_id), safe='')}/resources/{quote(str(resource_type_slug), safe='')}/{quote(str(external_id), safe='')}/role_assignments", + path=( + "authorization", + "organizations", + str(organization_id), + "resources", + str(resource_type_slug), + str(external_id), + "role_assignments", + ), model=UserRoleAssignment, params=params, request_options=request_options, @@ -2865,7 +3115,7 @@ async def list_resources( params["parent_external_id"] = parent.parent_external_id return await self._client.request_page( method="get", - path="authorization/resources", + path=("authorization", "resources"), model=AuthorizationResource, params=params, request_options=request_options, @@ -2933,7 +3183,7 @@ async def create_resource( ) return await self._client.request( method="post", - path="authorization/resources", + path=("authorization", "resources"), body=body, model=AuthorizationResource, request_options=request_options, @@ -2966,7 +3216,7 @@ async def get_resource( """ return await self._client.request( method="get", - path=f"authorization/resources/{quote(str(resource_id), safe='')}", + path=("authorization", "resources", str(resource_id)), model=AuthorizationResource, request_options=request_options, ) @@ -3026,7 +3276,7 @@ async def update_resource( ) return await self._client.request( method="patch", - path=f"authorization/resources/{quote(str(resource_id), safe='')}", + path=("authorization", "resources", str(resource_id)), body=body, model=AuthorizationResource, request_options=request_options, @@ -3066,7 +3316,7 @@ async def delete_resource( } await self._client.request( method="delete", - path=f"authorization/resources/{quote(str(resource_id), safe='')}", + path=("authorization", "resources", str(resource_id)), params=params, request_options=request_options, ) @@ -3125,7 +3375,12 @@ async def list_memberships_for_resource( } return await self._client.request_page( method="get", - path=f"authorization/resources/{quote(str(resource_id), safe='')}/organization_memberships", + path=( + "authorization", + "resources", + str(resource_id), + "organization_memberships", + ), model=UserOrganizationMembershipBaseListData, params=params, request_options=request_options, @@ -3175,7 +3430,7 @@ async def list_role_assignments_for_resource( } return await self._client.request_page( method="get", - path=f"authorization/resources/{quote(str(resource_id), safe='')}/role_assignments", + path=("authorization", "resources", str(resource_id), "role_assignments"), model=UserRoleAssignment, params=params, request_options=request_options, @@ -3201,7 +3456,7 @@ async def list_environment_roles( """ return await self._client.request( method="get", - path="authorization/roles", + path=("authorization", "roles"), model=RoleList, request_options=request_options, ) @@ -3251,7 +3506,7 @@ async def create_environment_role( } return await self._client.request( method="post", - path="authorization/roles", + path=("authorization", "roles"), body=body, model=Role, request_options=request_options, @@ -3283,7 +3538,7 @@ async def get_environment_role( """ return await self._client.request( method="get", - path=f"authorization/roles/{quote(str(slug), safe='')}", + path=("authorization", "roles", str(slug)), model=Role, request_options=request_options, ) @@ -3328,7 +3583,7 @@ async def update_environment_role( } return await self._client.request( method="patch", - path=f"authorization/roles/{quote(str(slug), safe='')}", + path=("authorization", "roles", str(slug)), body=body, model=Role, request_options=request_options, @@ -3367,7 +3622,7 @@ async def add_environment_role_permission( } return await self._client.request( method="post", - path=f"authorization/roles/{quote(str(slug), safe='')}/permissions", + path=("authorization", "roles", str(slug), "permissions"), body=body, model=Role, request_options=request_options, @@ -3406,7 +3661,7 @@ async def set_environment_role_permissions( } return await self._client.request( method="put", - path=f"authorization/roles/{quote(str(slug), safe='')}/permissions", + path=("authorization", "roles", str(slug), "permissions"), body=body, model=Role, request_options=request_options, @@ -3453,7 +3708,7 @@ async def list_permissions( } return await self._client.request_page( method="get", - path="authorization/permissions", + path=("authorization", "permissions"), model=AuthorizationPermission, params=params, request_options=request_options, @@ -3503,7 +3758,7 @@ async def create_permission( } return await self._client.request( method="post", - path="authorization/permissions", + path=("authorization", "permissions"), body=body, model=Permission, request_options=request_options, @@ -3534,7 +3789,7 @@ async def get_permission( """ return await self._client.request( method="get", - path=f"authorization/permissions/{quote(str(slug), safe='')}", + path=("authorization", "permissions", str(slug)), model=AuthorizationPermission, request_options=request_options, ) @@ -3578,7 +3833,7 @@ async def update_permission( } return await self._client.request( method="patch", - path=f"authorization/permissions/{quote(str(slug), safe='')}", + path=("authorization", "permissions", str(slug)), body=body, model=AuthorizationPermission, request_options=request_options, @@ -3607,6 +3862,6 @@ async def delete_permission( """ await self._client.request( method="delete", - path=f"authorization/permissions/{quote(str(slug), safe='')}", + path=("authorization", "permissions", str(slug)), request_options=request_options, ) diff --git a/src/workos/connect/_resource.py b/src/workos/connect/_resource.py index be9036cd..0e41ab7d 100644 --- a/src/workos/connect/_resource.py +++ b/src/workos/connect/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -83,7 +82,7 @@ def complete_oauth2( } return self._client.request( method="post", - path="authkit/oauth2/complete", + path=("authkit", "oauth2", "complete"), body=body, model=ExternalAuthCompleteResponse, request_options=request_options, @@ -133,7 +132,7 @@ def list_applications( } return self._client.request_page( method="get", - path="connect/applications", + path=("connect", "applications"), model=ConnectApplication, params=params, request_options=request_options, @@ -166,7 +165,7 @@ def create_application( _body: Dict[str, Any] = body if isinstance(body, dict) else body.to_dict() return self._client.request( method="post", - path="connect/applications", + path=("connect", "applications"), body=_body, model=ConnectApplication, request_options=request_options, @@ -203,7 +202,7 @@ def create_oauth_application( return self._client.request( method="POST", - path="connect/applications", + path=("connect", "applications"), body=body, model=ConnectApplication, request_options=request_options, @@ -231,7 +230,7 @@ def create_m2m_application( return self._client.request( method="POST", - path="connect/applications", + path=("connect", "applications"), body=body, model=ConnectApplication, request_options=request_options, @@ -262,7 +261,7 @@ def get_application( """ return self._client.request( method="get", - path=f"connect/applications/{quote(str(id), safe='')}", + path=("connect", "applications", str(id)), model=ConnectApplication, request_options=request_options, ) @@ -313,7 +312,7 @@ def update_application( } return self._client.request( method="put", - path=f"connect/applications/{quote(str(id), safe='')}", + path=("connect", "applications", str(id)), body=body, model=ConnectApplication, request_options=request_options, @@ -341,7 +340,7 @@ def delete_application( """ self._client.request( method="delete", - path=f"connect/applications/{quote(str(id), safe='')}", + path=("connect", "applications", str(id)), request_options=request_options, ) @@ -370,7 +369,7 @@ def list_application_client_secrets( """ raw = self._client.request_list( method="get", - path=f"connect/applications/{quote(str(id), safe='')}/client_secrets", + path=("connect", "applications", str(id), "client_secrets"), request_options=request_options, ) return [ @@ -405,7 +404,7 @@ def create_application_client_secret( body: Dict[str, Any] = {} return self._client.request( method="post", - path=f"connect/applications/{quote(str(id), safe='')}/client_secrets", + path=("connect", "applications", str(id), "client_secrets"), body=body, model=NewConnectApplicationSecret, request_options=request_options, @@ -433,7 +432,7 @@ def delete_client_secret( """ self._client.request( method="delete", - path=f"connect/client_secrets/{quote(str(id), safe='')}", + path=("connect", "client_secrets", str(id)), request_options=request_options, ) @@ -497,7 +496,7 @@ async def complete_oauth2( } return await self._client.request( method="post", - path="authkit/oauth2/complete", + path=("authkit", "oauth2", "complete"), body=body, model=ExternalAuthCompleteResponse, request_options=request_options, @@ -547,7 +546,7 @@ async def list_applications( } return await self._client.request_page( method="get", - path="connect/applications", + path=("connect", "applications"), model=ConnectApplication, params=params, request_options=request_options, @@ -580,7 +579,7 @@ async def create_application( _body: Dict[str, Any] = body if isinstance(body, dict) else body.to_dict() return await self._client.request( method="post", - path="connect/applications", + path=("connect", "applications"), body=_body, model=ConnectApplication, request_options=request_options, @@ -617,7 +616,7 @@ async def create_oauth_application( return await self._client.request( method="POST", - path="connect/applications", + path=("connect", "applications"), body=body, model=ConnectApplication, request_options=request_options, @@ -645,7 +644,7 @@ async def create_m2m_application( return await self._client.request( method="POST", - path="connect/applications", + path=("connect", "applications"), body=body, model=ConnectApplication, request_options=request_options, @@ -676,7 +675,7 @@ async def get_application( """ return await self._client.request( method="get", - path=f"connect/applications/{quote(str(id), safe='')}", + path=("connect", "applications", str(id)), model=ConnectApplication, request_options=request_options, ) @@ -727,7 +726,7 @@ async def update_application( } return await self._client.request( method="put", - path=f"connect/applications/{quote(str(id), safe='')}", + path=("connect", "applications", str(id)), body=body, model=ConnectApplication, request_options=request_options, @@ -755,7 +754,7 @@ async def delete_application( """ await self._client.request( method="delete", - path=f"connect/applications/{quote(str(id), safe='')}", + path=("connect", "applications", str(id)), request_options=request_options, ) @@ -784,7 +783,7 @@ async def list_application_client_secrets( """ raw = await self._client.request_list( method="get", - path=f"connect/applications/{quote(str(id), safe='')}/client_secrets", + path=("connect", "applications", str(id), "client_secrets"), request_options=request_options, ) return [ @@ -819,7 +818,7 @@ async def create_application_client_secret( body: Dict[str, Any] = {} return await self._client.request( method="post", - path=f"connect/applications/{quote(str(id), safe='')}/client_secrets", + path=("connect", "applications", str(id), "client_secrets"), body=body, model=NewConnectApplicationSecret, request_options=request_options, @@ -847,6 +846,6 @@ async def delete_client_secret( """ await self._client.request( method="delete", - path=f"connect/client_secrets/{quote(str(id), safe='')}", + path=("connect", "client_secrets", str(id)), request_options=request_options, ) diff --git a/src/workos/directory_sync/_resource.py b/src/workos/directory_sync/_resource.py index 0ab19ca1..10a3bbca 100644 --- a/src/workos/directory_sync/_resource.py +++ b/src/workos/directory_sync/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -72,7 +71,7 @@ def list_directories( } return self._client.request_page( method="get", - path="directories", + path=("directories",), model=Directory, params=params, request_options=request_options, @@ -104,7 +103,7 @@ def get_directory( """ return self._client.request( method="get", - path=f"directories/{quote(str(id), safe='')}", + path=("directories", str(id)), model=Directory, request_options=request_options, ) @@ -131,7 +130,7 @@ def delete_directory( """ self._client.request( method="delete", - path=f"directories/{quote(str(id), safe='')}", + path=("directories", str(id)), request_options=request_options, ) @@ -184,7 +183,7 @@ def list_groups( } return self._client.request_page( method="get", - path="directory_groups", + path=("directory_groups",), model=DirectoryGroup, params=params, request_options=request_options, @@ -216,7 +215,7 @@ def get_group( """ return self._client.request( method="get", - path=f"directory_groups/{quote(str(id), safe='')}", + path=("directory_groups", str(id)), model=DirectoryGroup, request_options=request_options, ) @@ -270,7 +269,7 @@ def list_users( } return self._client.request_page( method="get", - path="directory_users", + path=("directory_users",), model=DirectoryUserWithGroups, params=params, request_options=request_options, @@ -302,7 +301,7 @@ def get_user( """ return self._client.request( method="get", - path=f"directory_users/{quote(str(id), safe='')}", + path=("directory_users", str(id)), model=DirectoryUserWithGroups, request_options=request_options, ) @@ -365,7 +364,7 @@ async def list_directories( } return await self._client.request_page( method="get", - path="directories", + path=("directories",), model=Directory, params=params, request_options=request_options, @@ -397,7 +396,7 @@ async def get_directory( """ return await self._client.request( method="get", - path=f"directories/{quote(str(id), safe='')}", + path=("directories", str(id)), model=Directory, request_options=request_options, ) @@ -424,7 +423,7 @@ async def delete_directory( """ await self._client.request( method="delete", - path=f"directories/{quote(str(id), safe='')}", + path=("directories", str(id)), request_options=request_options, ) @@ -477,7 +476,7 @@ async def list_groups( } return await self._client.request_page( method="get", - path="directory_groups", + path=("directory_groups",), model=DirectoryGroup, params=params, request_options=request_options, @@ -509,7 +508,7 @@ async def get_group( """ return await self._client.request( method="get", - path=f"directory_groups/{quote(str(id), safe='')}", + path=("directory_groups", str(id)), model=DirectoryGroup, request_options=request_options, ) @@ -563,7 +562,7 @@ async def list_users( } return await self._client.request_page( method="get", - path="directory_users", + path=("directory_users",), model=DirectoryUserWithGroups, params=params, request_options=request_options, @@ -595,7 +594,7 @@ async def get_user( """ return await self._client.request( method="get", - path=f"directory_users/{quote(str(id), safe='')}", + path=("directory_users", str(id)), model=DirectoryUserWithGroups, request_options=request_options, ) diff --git a/src/workos/events/_resource.py b/src/workos/events/_resource.py index d2ef7ffd..cba120e6 100644 --- a/src/workos/events/_resource.py +++ b/src/workos/events/_resource.py @@ -77,7 +77,7 @@ def list_events( SyncPage[EventSchemaVariant], self._client.request_page( method="get", - path="events", + path=("events",), model=EventSchema, # type: ignore[arg-type] # dispatcher; pagination only calls from_dict params=params, request_options=request_options, @@ -149,7 +149,7 @@ async def list_events( AsyncPage[EventSchemaVariant], await self._client.request_page( method="get", - path="events", + path=("events",), model=EventSchema, # type: ignore[arg-type] # dispatcher; pagination only calls from_dict params=params, request_options=request_options, diff --git a/src/workos/feature_flags/_resource.py b/src/workos/feature_flags/_resource.py index 44354d8d..3d232b73 100644 --- a/src/workos/feature_flags/_resource.py +++ b/src/workos/feature_flags/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -64,7 +63,7 @@ def list_feature_flags( } return self._client.request_page( method="get", - path="feature-flags", + path=("feature-flags",), model=Flag, params=params, request_options=request_options, @@ -95,7 +94,7 @@ def get_feature_flag( """ return self._client.request( method="get", - path=f"feature-flags/{quote(str(slug), safe='')}", + path=("feature-flags", str(slug)), model=Flag, request_options=request_options, ) @@ -125,7 +124,7 @@ def disable_feature_flag( """ return self._client.request( method="put", - path=f"feature-flags/{quote(str(slug), safe='')}/disable", + path=("feature-flags", str(slug), "disable"), model=FeatureFlag, request_options=request_options, ) @@ -155,7 +154,7 @@ def enable_feature_flag( """ return self._client.request( method="put", - path=f"feature-flags/{quote(str(slug), safe='')}/enable", + path=("feature-flags", str(slug), "enable"), model=FeatureFlag, request_options=request_options, ) @@ -186,7 +185,7 @@ def add_flag_target( """ self._client.request( method="post", - path=f"feature-flags/{quote(str(slug), safe='')}/targets/{quote(str(resource_id), safe='')}", + path=("feature-flags", str(slug), "targets", str(resource_id)), request_options=request_options, ) @@ -216,7 +215,7 @@ def remove_flag_target( """ self._client.request( method="delete", - path=f"feature-flags/{quote(str(slug), safe='')}/targets/{quote(str(resource_id), safe='')}", + path=("feature-flags", str(slug), "targets", str(resource_id)), request_options=request_options, ) @@ -263,7 +262,7 @@ def list_organization_feature_flags( } return self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/feature-flags", + path=("organizations", str(organization_id), "feature-flags"), model=Flag, params=params, request_options=request_options, @@ -312,7 +311,7 @@ def list_user_feature_flags( } return self._client.request_page( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/feature-flags", + path=("user_management", "users", str(user_id), "feature-flags"), model=Flag, params=params, request_options=request_options, @@ -368,7 +367,7 @@ async def list_feature_flags( } return await self._client.request_page( method="get", - path="feature-flags", + path=("feature-flags",), model=Flag, params=params, request_options=request_options, @@ -399,7 +398,7 @@ async def get_feature_flag( """ return await self._client.request( method="get", - path=f"feature-flags/{quote(str(slug), safe='')}", + path=("feature-flags", str(slug)), model=Flag, request_options=request_options, ) @@ -429,7 +428,7 @@ async def disable_feature_flag( """ return await self._client.request( method="put", - path=f"feature-flags/{quote(str(slug), safe='')}/disable", + path=("feature-flags", str(slug), "disable"), model=FeatureFlag, request_options=request_options, ) @@ -459,7 +458,7 @@ async def enable_feature_flag( """ return await self._client.request( method="put", - path=f"feature-flags/{quote(str(slug), safe='')}/enable", + path=("feature-flags", str(slug), "enable"), model=FeatureFlag, request_options=request_options, ) @@ -490,7 +489,7 @@ async def add_flag_target( """ await self._client.request( method="post", - path=f"feature-flags/{quote(str(slug), safe='')}/targets/{quote(str(resource_id), safe='')}", + path=("feature-flags", str(slug), "targets", str(resource_id)), request_options=request_options, ) @@ -520,7 +519,7 @@ async def remove_flag_target( """ await self._client.request( method="delete", - path=f"feature-flags/{quote(str(slug), safe='')}/targets/{quote(str(resource_id), safe='')}", + path=("feature-flags", str(slug), "targets", str(resource_id)), request_options=request_options, ) @@ -567,7 +566,7 @@ async def list_organization_feature_flags( } return await self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/feature-flags", + path=("organizations", str(organization_id), "feature-flags"), model=Flag, params=params, request_options=request_options, @@ -616,7 +615,7 @@ async def list_user_feature_flags( } return await self._client.request_page( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/feature-flags", + path=("user_management", "users", str(user_id), "feature-flags"), model=Flag, params=params, request_options=request_options, diff --git a/src/workos/groups/_resource.py b/src/workos/groups/_resource.py index 51c48067..cb86fd26 100644 --- a/src/workos/groups/_resource.py +++ b/src/workos/groups/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -67,7 +66,7 @@ def list_organization_groups( } return self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/groups", + path=("organizations", str(organization_id), "groups"), model=Group, params=params, request_options=request_options, @@ -113,7 +112,7 @@ def create_organization_group( } return self._client.request( method="post", - path=f"organizations/{quote(str(organization_id), safe='')}/groups", + path=("organizations", str(organization_id), "groups"), body=body, model=Group, request_options=request_options, @@ -147,7 +146,7 @@ def get_organization_group( """ return self._client.request( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}", + path=("organizations", str(organization_id), "groups", str(group_id)), model=Group, request_options=request_options, ) @@ -194,7 +193,7 @@ def update_organization_group( } return self._client.request( method="patch", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}", + path=("organizations", str(organization_id), "groups", str(group_id)), body=body, model=Group, request_options=request_options, @@ -225,7 +224,7 @@ def delete_organization_group( """ self._client.request( method="delete", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}", + path=("organizations", str(organization_id), "groups", str(group_id)), request_options=request_options, ) @@ -275,7 +274,13 @@ def list_group_organization_memberships( } return self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}/organization-memberships", + path=( + "organizations", + str(organization_id), + "groups", + str(group_id), + "organization-memberships", + ), model=UserOrganizationMembershipBaseListData, params=params, request_options=request_options, @@ -315,7 +320,13 @@ def create_group_organization_membership( } return self._client.request( method="post", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}/organization-memberships", + path=( + "organizations", + str(organization_id), + "groups", + str(group_id), + "organization-memberships", + ), body=body, model=Group, request_options=request_options, @@ -348,7 +359,14 @@ def delete_group_organization_membership( """ self._client.request( method="delete", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}/organization-memberships/{quote(str(om_id), safe='')}", + path=( + "organizations", + str(organization_id), + "groups", + str(group_id), + "organization-memberships", + str(om_id), + ), request_options=request_options, ) @@ -403,7 +421,7 @@ async def list_organization_groups( } return await self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/groups", + path=("organizations", str(organization_id), "groups"), model=Group, params=params, request_options=request_options, @@ -449,7 +467,7 @@ async def create_organization_group( } return await self._client.request( method="post", - path=f"organizations/{quote(str(organization_id), safe='')}/groups", + path=("organizations", str(organization_id), "groups"), body=body, model=Group, request_options=request_options, @@ -483,7 +501,7 @@ async def get_organization_group( """ return await self._client.request( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}", + path=("organizations", str(organization_id), "groups", str(group_id)), model=Group, request_options=request_options, ) @@ -530,7 +548,7 @@ async def update_organization_group( } return await self._client.request( method="patch", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}", + path=("organizations", str(organization_id), "groups", str(group_id)), body=body, model=Group, request_options=request_options, @@ -561,7 +579,7 @@ async def delete_organization_group( """ await self._client.request( method="delete", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}", + path=("organizations", str(organization_id), "groups", str(group_id)), request_options=request_options, ) @@ -611,7 +629,13 @@ async def list_group_organization_memberships( } return await self._client.request_page( method="get", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}/organization-memberships", + path=( + "organizations", + str(organization_id), + "groups", + str(group_id), + "organization-memberships", + ), model=UserOrganizationMembershipBaseListData, params=params, request_options=request_options, @@ -651,7 +675,13 @@ async def create_group_organization_membership( } return await self._client.request( method="post", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}/organization-memberships", + path=( + "organizations", + str(organization_id), + "groups", + str(group_id), + "organization-memberships", + ), body=body, model=Group, request_options=request_options, @@ -684,6 +714,13 @@ async def delete_group_organization_membership( """ await self._client.request( method="delete", - path=f"organizations/{quote(str(organization_id), safe='')}/groups/{quote(str(group_id), safe='')}/organization-memberships/{quote(str(om_id), safe='')}", + path=( + "organizations", + str(organization_id), + "groups", + str(group_id), + "organization-memberships", + str(om_id), + ), request_options=request_options, ) diff --git a/src/workos/multi_factor_auth/_resource.py b/src/workos/multi_factor_auth/_resource.py index 6ea2f582..d3f21002 100644 --- a/src/workos/multi_factor_auth/_resource.py +++ b/src/workos/multi_factor_auth/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -63,7 +62,7 @@ def verify_challenge( } return self._client.request( method="post", - path=f"auth/challenges/{quote(str(id), safe='')}/verify", + path=("auth", "challenges", str(id), "verify"), body=body, model=AuthenticationChallengeVerifyResponse, request_options=request_options, @@ -113,7 +112,7 @@ def enroll_factor( } return self._client.request( method="post", - path="auth/factors/enroll", + path=("auth", "factors", "enroll"), body=body, model=AuthenticationFactorEnrolled, request_options=request_options, @@ -144,7 +143,7 @@ def get_factor( """ return self._client.request( method="get", - path=f"auth/factors/{quote(str(id), safe='')}", + path=("auth", "factors", str(id)), model=AuthenticationFactor, request_options=request_options, ) @@ -171,7 +170,7 @@ def delete_factor( """ self._client.request( method="delete", - path=f"auth/factors/{quote(str(id), safe='')}", + path=("auth", "factors", str(id)), request_options=request_options, ) @@ -210,7 +209,7 @@ def challenge_factor( } return self._client.request( method="post", - path=f"auth/factors/{quote(str(id), safe='')}/challenge", + path=("auth", "factors", str(id), "challenge"), body=body, model=AuthenticationChallenge, request_options=request_options, @@ -259,7 +258,7 @@ def list_user_auth_factors( } return self._client.request_page( method="get", - path=f"user_management/users/{quote(str(userland_user_id), safe='')}/auth_factors", + path=("user_management", "users", str(userland_user_id), "auth_factors"), model=AuthenticationFactor, params=params, request_options=request_options, @@ -308,7 +307,7 @@ def create_user_auth_factor( } return self._client.request( method="post", - path=f"user_management/users/{quote(str(userland_user_id), safe='')}/auth_factors", + path=("user_management", "users", str(userland_user_id), "auth_factors"), body=body, model=UserAuthenticationFactorEnrollResponse, request_options=request_options, @@ -353,7 +352,7 @@ async def verify_challenge( } return await self._client.request( method="post", - path=f"auth/challenges/{quote(str(id), safe='')}/verify", + path=("auth", "challenges", str(id), "verify"), body=body, model=AuthenticationChallengeVerifyResponse, request_options=request_options, @@ -403,7 +402,7 @@ async def enroll_factor( } return await self._client.request( method="post", - path="auth/factors/enroll", + path=("auth", "factors", "enroll"), body=body, model=AuthenticationFactorEnrolled, request_options=request_options, @@ -434,7 +433,7 @@ async def get_factor( """ return await self._client.request( method="get", - path=f"auth/factors/{quote(str(id), safe='')}", + path=("auth", "factors", str(id)), model=AuthenticationFactor, request_options=request_options, ) @@ -461,7 +460,7 @@ async def delete_factor( """ await self._client.request( method="delete", - path=f"auth/factors/{quote(str(id), safe='')}", + path=("auth", "factors", str(id)), request_options=request_options, ) @@ -500,7 +499,7 @@ async def challenge_factor( } return await self._client.request( method="post", - path=f"auth/factors/{quote(str(id), safe='')}/challenge", + path=("auth", "factors", str(id), "challenge"), body=body, model=AuthenticationChallenge, request_options=request_options, @@ -549,7 +548,7 @@ async def list_user_auth_factors( } return await self._client.request_page( method="get", - path=f"user_management/users/{quote(str(userland_user_id), safe='')}/auth_factors", + path=("user_management", "users", str(userland_user_id), "auth_factors"), model=AuthenticationFactor, params=params, request_options=request_options, @@ -598,7 +597,7 @@ async def create_user_auth_factor( } return await self._client.request( method="post", - path=f"user_management/users/{quote(str(userland_user_id), safe='')}/auth_factors", + path=("user_management", "users", str(userland_user_id), "auth_factors"), body=body, model=UserAuthenticationFactorEnrollResponse, request_options=request_options, diff --git a/src/workos/organization_domains/_resource.py b/src/workos/organization_domains/_resource.py index 9c7e5d7a..19d1ba0c 100644 --- a/src/workos/organization_domains/_resource.py +++ b/src/workos/organization_domains/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, Optional -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -50,7 +49,7 @@ def create_organization_domain( } return self._client.request( method="post", - path="organization_domains", + path=("organization_domains",), body=body, model=OrganizationDomain, request_options=request_options, @@ -81,7 +80,7 @@ def get_organization_domain( """ return self._client.request( method="get", - path=f"organization_domains/{quote(str(id), safe='')}", + path=("organization_domains", str(id)), model=OrganizationDomainStandAlone, request_options=request_options, ) @@ -108,7 +107,7 @@ def delete_organization_domain( """ self._client.request( method="delete", - path=f"organization_domains/{quote(str(id), safe='')}", + path=("organization_domains", str(id)), request_options=request_options, ) @@ -137,7 +136,7 @@ def verify_organization_domain( """ return self._client.request( method="post", - path=f"organization_domains/{quote(str(id), safe='')}/verify", + path=("organization_domains", str(id), "verify"), model=OrganizationDomainStandAlone, request_options=request_options, ) @@ -180,7 +179,7 @@ async def create_organization_domain( } return await self._client.request( method="post", - path="organization_domains", + path=("organization_domains",), body=body, model=OrganizationDomain, request_options=request_options, @@ -211,7 +210,7 @@ async def get_organization_domain( """ return await self._client.request( method="get", - path=f"organization_domains/{quote(str(id), safe='')}", + path=("organization_domains", str(id)), model=OrganizationDomainStandAlone, request_options=request_options, ) @@ -238,7 +237,7 @@ async def delete_organization_domain( """ await self._client.request( method="delete", - path=f"organization_domains/{quote(str(id), safe='')}", + path=("organization_domains", str(id)), request_options=request_options, ) @@ -267,7 +266,7 @@ async def verify_organization_domain( """ return await self._client.request( method="post", - path=f"organization_domains/{quote(str(id), safe='')}/verify", + path=("organization_domains", str(id), "verify"), model=OrganizationDomainStandAlone, request_options=request_options, ) diff --git a/src/workos/organizations/_resource.py b/src/workos/organizations/_resource.py index c108ad5a..d91dab3a 100644 --- a/src/workos/organizations/_resource.py +++ b/src/workos/organizations/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -69,7 +68,7 @@ def list_organizations( } return self._client.request_page( method="get", - path="organizations", + path=("organizations",), model=Organization, params=params, request_options=request_options, @@ -126,7 +125,7 @@ def create_organization( } return self._client.request( method="post", - path="organizations", + path=("organizations",), body=body, model=Organization, request_options=request_options, @@ -157,7 +156,7 @@ def get_organization_by_external_id( """ return self._client.request( method="get", - path=f"organizations/external_id/{quote(str(external_id), safe='')}", + path=("organizations", "external_id", str(external_id)), model=Organization, request_options=request_options, ) @@ -187,7 +186,7 @@ def get_organization( """ return self._client.request( method="get", - path=f"organizations/{quote(str(id), safe='')}", + path=("organizations", str(id)), model=Organization, request_options=request_options, ) @@ -250,7 +249,7 @@ def update_organization( } return self._client.request( method="put", - path=f"organizations/{quote(str(id), safe='')}", + path=("organizations", str(id)), body=body, model=Organization, request_options=request_options, @@ -278,7 +277,7 @@ def delete_organization( """ self._client.request( method="delete", - path=f"organizations/{quote(str(id), safe='')}", + path=("organizations", str(id)), request_options=request_options, ) @@ -307,7 +306,7 @@ def get_audit_log_configuration( """ return self._client.request( method="get", - path=f"organizations/{quote(str(id), safe='')}/audit_log_configuration", + path=("organizations", str(id), "audit_log_configuration"), model=AuditLogConfiguration, request_options=request_options, ) @@ -368,7 +367,7 @@ async def list_organizations( } return await self._client.request_page( method="get", - path="organizations", + path=("organizations",), model=Organization, params=params, request_options=request_options, @@ -425,7 +424,7 @@ async def create_organization( } return await self._client.request( method="post", - path="organizations", + path=("organizations",), body=body, model=Organization, request_options=request_options, @@ -456,7 +455,7 @@ async def get_organization_by_external_id( """ return await self._client.request( method="get", - path=f"organizations/external_id/{quote(str(external_id), safe='')}", + path=("organizations", "external_id", str(external_id)), model=Organization, request_options=request_options, ) @@ -486,7 +485,7 @@ async def get_organization( """ return await self._client.request( method="get", - path=f"organizations/{quote(str(id), safe='')}", + path=("organizations", str(id)), model=Organization, request_options=request_options, ) @@ -549,7 +548,7 @@ async def update_organization( } return await self._client.request( method="put", - path=f"organizations/{quote(str(id), safe='')}", + path=("organizations", str(id)), body=body, model=Organization, request_options=request_options, @@ -577,7 +576,7 @@ async def delete_organization( """ await self._client.request( method="delete", - path=f"organizations/{quote(str(id), safe='')}", + path=("organizations", str(id)), request_options=request_options, ) @@ -606,7 +605,7 @@ async def get_audit_log_configuration( """ return await self._client.request( method="get", - path=f"organizations/{quote(str(id), safe='')}/audit_log_configuration", + path=("organizations", str(id), "audit_log_configuration"), model=AuditLogConfiguration, request_options=request_options, ) diff --git a/src/workos/passwordless.py b/src/workos/passwordless.py index 12fffb6c..0bd8ee09 100644 --- a/src/workos/passwordless.py +++ b/src/workos/passwordless.py @@ -84,7 +84,7 @@ def create_session( response = self._client.request( method="post", - path="passwordless/sessions", + path=("passwordless", "sessions"), body=body, model=PasswordlessSession, ) @@ -101,7 +101,7 @@ def send_session(self, session_id: str) -> Literal[True]: """ self._client.request( method="post", - path=f"passwordless/sessions/{session_id}/send", + path=("passwordless", "sessions", str(session_id), "send"), ) return True @@ -147,7 +147,7 @@ async def create_session( response = await self._client.request( method="post", - path="passwordless/sessions", + path=("passwordless", "sessions"), body=body, model=PasswordlessSession, ) @@ -164,6 +164,6 @@ async def send_session(self, session_id: str) -> Literal[True]: """ await self._client.request( method="post", - path=f"passwordless/sessions/{session_id}/send", + path=("passwordless", "sessions", str(session_id), "send"), ) return True diff --git a/src/workos/pipes/_resource.py b/src/workos/pipes/_resource.py index 9d834e07..9ca384d8 100644 --- a/src/workos/pipes/_resource.py +++ b/src/workos/pipes/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, Optional -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -65,7 +64,7 @@ def authorize_data_integration( } return self._client.request( method="post", - path=f"data-integrations/{quote(str(slug), safe='')}/authorize", + path=("data-integrations", str(slug), "authorize"), body=body, model=DataIntegrationAuthorizeUrlResponse, request_options=request_options, @@ -109,7 +108,7 @@ def create_data_integration_token( } return self._client.request( method="post", - path=f"data-integrations/{quote(str(slug), safe='')}/token", + path=("data-integrations", str(slug), "token"), body=body, model=DataIntegrationAccessTokenResponse, request_options=request_options, @@ -151,7 +150,13 @@ def get_user_connected_account( } return self._client.request( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/connected_accounts/{quote(str(slug), safe='')}", + path=( + "user_management", + "users", + str(user_id), + "connected_accounts", + str(slug), + ), params=params, model=ConnectedAccount, request_options=request_options, @@ -190,7 +195,13 @@ def delete_user_connected_account( } self._client.request( method="delete", - path=f"user_management/users/{quote(str(user_id), safe='')}/connected_accounts/{quote(str(slug), safe='')}", + path=( + "user_management", + "users", + str(user_id), + "connected_accounts", + str(slug), + ), params=params, request_options=request_options, ) @@ -229,7 +240,7 @@ def list_user_data_providers( } return self._client.request( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/data_providers", + path=("user_management", "users", str(user_id), "data_providers"), params=params, model=DataIntegrationsListResponse, request_options=request_options, @@ -284,7 +295,7 @@ async def authorize_data_integration( } return await self._client.request( method="post", - path=f"data-integrations/{quote(str(slug), safe='')}/authorize", + path=("data-integrations", str(slug), "authorize"), body=body, model=DataIntegrationAuthorizeUrlResponse, request_options=request_options, @@ -328,7 +339,7 @@ async def create_data_integration_token( } return await self._client.request( method="post", - path=f"data-integrations/{quote(str(slug), safe='')}/token", + path=("data-integrations", str(slug), "token"), body=body, model=DataIntegrationAccessTokenResponse, request_options=request_options, @@ -370,7 +381,13 @@ async def get_user_connected_account( } return await self._client.request( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/connected_accounts/{quote(str(slug), safe='')}", + path=( + "user_management", + "users", + str(user_id), + "connected_accounts", + str(slug), + ), params=params, model=ConnectedAccount, request_options=request_options, @@ -409,7 +426,13 @@ async def delete_user_connected_account( } await self._client.request( method="delete", - path=f"user_management/users/{quote(str(user_id), safe='')}/connected_accounts/{quote(str(slug), safe='')}", + path=( + "user_management", + "users", + str(user_id), + "connected_accounts", + str(slug), + ), params=params, request_options=request_options, ) @@ -448,7 +471,7 @@ async def list_user_data_providers( } return await self._client.request( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/data_providers", + path=("user_management", "users", str(user_id), "data_providers"), params=params, model=DataIntegrationsListResponse, request_options=request_options, diff --git a/src/workos/radar/_resource.py b/src/workos/radar/_resource.py index f90d25a4..45c16a58 100644 --- a/src/workos/radar/_resource.py +++ b/src/workos/radar/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -75,7 +74,7 @@ def create_attempt( } return self._client.request( method="post", - path="radar/attempts", + path=("radar", "attempts"), body=body, model=RadarStandaloneResponse, request_options=request_options, @@ -116,7 +115,7 @@ def update_attempt( } self._client.request( method="put", - path=f"radar/attempts/{quote(str(id), safe='')}", + path=("radar", "attempts", str(id)), body=body, request_options=request_options, ) @@ -153,7 +152,7 @@ def add_list_entry( } return self._client.request( method="post", - path=f"radar/lists/{quote(str(enum_value(type)), safe='')}/{quote(str(enum_value(action)), safe='')}", + path=("radar", "lists", str(enum_value(type)), str(enum_value(action))), body=body, model=RadarListEntryAlreadyPresentResponse, request_options=request_options, @@ -189,7 +188,7 @@ def remove_list_entry( } self._client.request( method="delete", - path=f"radar/lists/{quote(str(enum_value(type)), safe='')}/{quote(str(enum_value(action)), safe='')}", + path=("radar", "lists", str(enum_value(type)), str(enum_value(action))), body=body, request_options=request_options, ) @@ -251,7 +250,7 @@ async def create_attempt( } return await self._client.request( method="post", - path="radar/attempts", + path=("radar", "attempts"), body=body, model=RadarStandaloneResponse, request_options=request_options, @@ -292,7 +291,7 @@ async def update_attempt( } await self._client.request( method="put", - path=f"radar/attempts/{quote(str(id), safe='')}", + path=("radar", "attempts", str(id)), body=body, request_options=request_options, ) @@ -329,7 +328,7 @@ async def add_list_entry( } return await self._client.request( method="post", - path=f"radar/lists/{quote(str(enum_value(type)), safe='')}/{quote(str(enum_value(action)), safe='')}", + path=("radar", "lists", str(enum_value(type)), str(enum_value(action))), body=body, model=RadarListEntryAlreadyPresentResponse, request_options=request_options, @@ -365,7 +364,7 @@ async def remove_list_entry( } await self._client.request( method="delete", - path=f"radar/lists/{quote(str(enum_value(type)), safe='')}/{quote(str(enum_value(action)), safe='')}", + path=("radar", "lists", str(enum_value(type)), str(enum_value(action))), body=body, request_options=request_options, ) diff --git a/src/workos/session.py b/src/workos/session.py index f289c5eb..330a66bd 100644 --- a/src/workos/session.py +++ b/src/workos/session.py @@ -354,7 +354,7 @@ def refresh( auth_response = self._client.request_raw( method="post", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, ) @@ -533,7 +533,7 @@ async def refresh( auth_response = await self._client.request_raw( method="post", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, ) diff --git a/src/workos/sso/_resource.py b/src/workos/sso/_resource.py index d4e0a9ee..c088011a 100644 --- a/src/workos/sso/_resource.py +++ b/src/workos/sso/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -77,7 +76,7 @@ def list_connections( } return self._client.request_page( method="get", - path="connections", + path=("connections",), model=Connection, params=params, request_options=request_options, @@ -109,7 +108,7 @@ def get_connection( """ return self._client.request( method="get", - path=f"connections/{quote(str(id), safe='')}", + path=("connections", str(id)), model=Connection, request_options=request_options, ) @@ -137,7 +136,7 @@ def delete_connection( """ self._client.request( method="delete", - path=f"connections/{quote(str(id), safe='')}", + path=("connections", str(id)), request_options=request_options, ) @@ -207,7 +206,7 @@ def get_authorization_url( params["response_type"] = "code" if self._client.client_id is not None: params["client_id"] = self._client.client_id - return self._client.build_url("sso/authorize", params) + return self._client.build_url(("sso", "authorize"), params) def get_logout_url( self, @@ -241,7 +240,7 @@ def get_logout_url( }.items() if v is not None } - return self._client.build_url("sso/logout", params) + return self._client.build_url(("sso", "logout"), params) def authorize_logout( self, @@ -272,7 +271,7 @@ def authorize_logout( } return self._client.request( method="post", - path="sso/logout/authorize", + path=("sso", "logout", "authorize"), body=body, model=SSOLogoutAuthorizeResponse, request_options=request_options, @@ -311,7 +310,7 @@ def get_profile( } return self._client.request( method="get", - path="sso/profile", + path=("sso", "profile"), model=Profile, request_options=request_options, ) @@ -351,7 +350,7 @@ def get_profile_and_token( body["client_secret"] = self._client._api_key return self._client.request( method="post", - path="sso/token", + path=("sso", "token"), body=body, model=SSOTokenResponse, request_options=request_options, @@ -399,7 +398,7 @@ def get_authorization_url_with_pkce( }.items() if v is not None } - url = self._client.build_url("sso/authorize", params) + url = self._client.build_url(("sso", "authorize"), params) return {"url": url, "state": state, "code_verifier": pair.code_verifier} def get_profile_and_token_pkce( @@ -423,7 +422,7 @@ def get_profile_and_token_pkce( return self._client.request( method="post", - path="sso/token", + path=("sso", "token"), body=body, model=SSOTokenResponse, request_options=request_options, @@ -494,7 +493,7 @@ async def list_connections( } return await self._client.request_page( method="get", - path="connections", + path=("connections",), model=Connection, params=params, request_options=request_options, @@ -526,7 +525,7 @@ async def get_connection( """ return await self._client.request( method="get", - path=f"connections/{quote(str(id), safe='')}", + path=("connections", str(id)), model=Connection, request_options=request_options, ) @@ -554,7 +553,7 @@ async def delete_connection( """ await self._client.request( method="delete", - path=f"connections/{quote(str(id), safe='')}", + path=("connections", str(id)), request_options=request_options, ) @@ -624,7 +623,7 @@ def get_authorization_url( params["response_type"] = "code" if self._client.client_id is not None: params["client_id"] = self._client.client_id - return self._client.build_url("sso/authorize", params) + return self._client.build_url(("sso", "authorize"), params) def get_logout_url( self, @@ -658,7 +657,7 @@ def get_logout_url( }.items() if v is not None } - return self._client.build_url("sso/logout", params) + return self._client.build_url(("sso", "logout"), params) async def authorize_logout( self, @@ -689,7 +688,7 @@ async def authorize_logout( } return await self._client.request( method="post", - path="sso/logout/authorize", + path=("sso", "logout", "authorize"), body=body, model=SSOLogoutAuthorizeResponse, request_options=request_options, @@ -728,7 +727,7 @@ async def get_profile( } return await self._client.request( method="get", - path="sso/profile", + path=("sso", "profile"), model=Profile, request_options=request_options, ) @@ -768,7 +767,7 @@ async def get_profile_and_token( body["client_secret"] = self._client._api_key return await self._client.request( method="post", - path="sso/token", + path=("sso", "token"), body=body, model=SSOTokenResponse, request_options=request_options, @@ -816,7 +815,7 @@ async def get_authorization_url_with_pkce( }.items() if v is not None } - url = self._client.build_url("sso/authorize", params) + url = self._client.build_url(("sso", "authorize"), params) return {"url": url, "state": state, "code_verifier": pair.code_verifier} async def get_profile_and_token_pkce( @@ -840,7 +839,7 @@ async def get_profile_and_token_pkce( return await self._client.request( method="post", - path="sso/token", + path=("sso", "token"), body=body, model=SSOTokenResponse, request_options=request_options, diff --git a/src/workos/user_management/_resource.py b/src/workos/user_management/_resource.py index 12e34b52..c9fbd8fb 100644 --- a/src/workos/user_management/_resource.py +++ b/src/workos/user_management/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, cast -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -128,7 +127,7 @@ def get_jwks( """ return self._client.request( method="get", - path=f"sso/jwks/{quote(str(client_id), safe='')}", + path=("sso", "jwks", str(client_id)), model=JwksResponse, request_options=request_options, ) @@ -172,7 +171,7 @@ def create_authenticate( _body: Dict[str, Any] = body if isinstance(body, dict) else body.to_dict() return self._client.request( method="post", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=_body, model=AuthenticateResponse, request_options=request_options, @@ -210,7 +209,7 @@ def authenticate_with_password( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -249,7 +248,7 @@ def authenticate_with_code( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -285,7 +284,7 @@ def authenticate_with_refresh_token( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -323,7 +322,7 @@ def authenticate_with_magic_auth( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -358,7 +357,7 @@ def authenticate_with_email_verification( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -395,7 +394,7 @@ def authenticate_with_totp( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -430,7 +429,7 @@ def authenticate_with_organization_selection( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -461,7 +460,7 @@ def authenticate_with_device_code( return self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -544,7 +543,7 @@ def get_authorization_url( params["response_type"] = "code" if self._client.client_id is not None: params["client_id"] = self._client.client_id - return self._client.build_url("user_management/authorize", params) + return self._client.build_url(("user_management", "authorize"), params) def create_device( self, @@ -575,7 +574,7 @@ def create_device( } return self._client.request( method="post", - path="user_management/authorize/device", + path=("user_management", "authorize", "device"), body=body, model=DeviceAuthorizationResponse, request_options=request_options, @@ -614,7 +613,7 @@ def get_logout_url( }.items() if v is not None } - return self._client.build_url("user_management/sessions/logout", params) + return self._client.build_url(("user_management", "sessions", "logout"), params) def revoke_session( self, @@ -648,7 +647,7 @@ def revoke_session( } self._client.request( method="post", - path="user_management/sessions/revoke", + path=("user_management", "sessions", "revoke"), body=body, request_options=request_options, ) @@ -682,7 +681,7 @@ def create_cors_origin( } return self._client.request( method="post", - path="user_management/cors_origins", + path=("user_management", "cors_origins"), body=body, model=CORSOriginResponse, request_options=request_options, @@ -713,7 +712,7 @@ def get_email_verification( """ return self._client.request( method="get", - path=f"user_management/email_verification/{quote(str(id), safe='')}", + path=("user_management", "email_verification", str(id)), model=EmailVerification, request_options=request_options, ) @@ -748,7 +747,7 @@ def reset_password( } return self._client.request( method="post", - path="user_management/password_reset", + path=("user_management", "password_reset"), body=body, model=PasswordReset, request_options=request_options, @@ -788,7 +787,7 @@ def confirm_password_reset( } return self._client.request( method="post", - path="user_management/password_reset/confirm", + path=("user_management", "password_reset", "confirm"), body=body, model=ResetPasswordResponse, request_options=request_options, @@ -819,7 +818,7 @@ def get_password_reset( """ return self._client.request( method="get", - path=f"user_management/password_reset/{quote(str(id), safe='')}", + path=("user_management", "password_reset", str(id)), model=PasswordReset, request_options=request_options, ) @@ -874,7 +873,7 @@ def list_users( } return self._client.request_page( method="get", - path="user_management/users", + path=("user_management", "users"), model=User, params=params, request_options=request_options, @@ -937,7 +936,7 @@ def create_user( body["password_hash_type"] = enum_value(password.password_hash_type) return self._client.request( method="post", - path="user_management/users", + path=("user_management", "users"), body=body, model=User, request_options=request_options, @@ -968,7 +967,7 @@ def get_user_by_external_id( """ return self._client.request( method="get", - path=f"user_management/users/external_id/{quote(str(external_id), safe='')}", + path=("user_management", "users", "external_id", str(external_id)), model=User, request_options=request_options, ) @@ -998,7 +997,7 @@ def get_user( """ return self._client.request( method="get", - path=f"user_management/users/{quote(str(id), safe='')}", + path=("user_management", "users", str(id)), model=User, request_options=request_options, ) @@ -1064,7 +1063,7 @@ def update_user( body["password_hash_type"] = enum_value(password.password_hash_type) return self._client.request( method="put", - path=f"user_management/users/{quote(str(id), safe='')}", + path=("user_management", "users", str(id)), body=body, model=User, request_options=request_options, @@ -1092,7 +1091,7 @@ def delete_user( """ self._client.request( method="delete", - path=f"user_management/users/{quote(str(id), safe='')}", + path=("user_management", "users", str(id)), request_options=request_options, ) @@ -1129,7 +1128,7 @@ def confirm_email_change( } return self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_change/confirm", + path=("user_management", "users", str(id), "email_change", "confirm"), body=body, model=EmailChangeConfirmation, request_options=request_options, @@ -1167,7 +1166,7 @@ def send_email_change( } return self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_change/send", + path=("user_management", "users", str(id), "email_change", "send"), body=body, model=EmailChange, request_options=request_options, @@ -1205,7 +1204,7 @@ def verify_email( } return self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_verification/confirm", + path=("user_management", "users", str(id), "email_verification", "confirm"), body=body, model=VerifyEmailResponse, request_options=request_options, @@ -1237,7 +1236,7 @@ def send_verification_email( """ return self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_verification/send", + path=("user_management", "users", str(id), "email_verification", "send"), model=SendVerificationEmailResponse, request_options=request_options, ) @@ -1267,7 +1266,7 @@ def get_user_identities( """ raw = self._client.request_list( method="get", - path=f"user_management/users/{quote(str(id), safe='')}/identities", + path=("user_management", "users", str(id), "identities"), request_options=request_options, ) return [ @@ -1318,7 +1317,7 @@ def list_sessions( } return self._client.request_page( method="get", - path=f"user_management/users/{quote(str(id), safe='')}/sessions", + path=("user_management", "users", str(id), "sessions"), model=UserSessionsListItem, params=params, request_options=request_options, @@ -1371,7 +1370,7 @@ def list_invitations( } return self._client.request_page( method="get", - path="user_management/invitations", + path=("user_management", "invitations"), model=UserInvite, params=params, request_options=request_options, @@ -1426,7 +1425,7 @@ def send_invitation( } return self._client.request( method="post", - path="user_management/invitations", + path=("user_management", "invitations"), body=body, model=UserInvite, request_options=request_options, @@ -1457,7 +1456,7 @@ def find_invitation_by_token( """ return self._client.request( method="get", - path=f"user_management/invitations/by_token/{quote(str(token), safe='')}", + path=("user_management", "invitations", "by_token", str(token)), model=UserInvite, request_options=request_options, ) @@ -1487,7 +1486,7 @@ def get_invitation( """ return self._client.request( method="get", - path=f"user_management/invitations/{quote(str(id), safe='')}", + path=("user_management", "invitations", str(id)), model=UserInvite, request_options=request_options, ) @@ -1518,7 +1517,7 @@ def accept_invitation( """ return self._client.request( method="post", - path=f"user_management/invitations/{quote(str(id), safe='')}/accept", + path=("user_management", "invitations", str(id), "accept"), model=Invitation, request_options=request_options, ) @@ -1559,7 +1558,7 @@ def resend_invitation( } return self._client.request( method="post", - path=f"user_management/invitations/{quote(str(id), safe='')}/resend", + path=("user_management", "invitations", str(id), "resend"), body=body, model=UserInvite, request_options=request_options, @@ -1590,7 +1589,7 @@ def revoke_invitation( """ return self._client.request( method="post", - path=f"user_management/invitations/{quote(str(id), safe='')}/revoke", + path=("user_management", "invitations", str(id), "revoke"), model=Invitation, request_options=request_options, ) @@ -1615,7 +1614,7 @@ def list_jwt_template( """ return self._client.request( method="get", - path="user_management/jwt_template", + path=("user_management", "jwt_template"), model=JWTTemplateResponse, request_options=request_options, ) @@ -1648,7 +1647,7 @@ def update_jwt_template( } return self._client.request( method="put", - path="user_management/jwt_template", + path=("user_management", "jwt_template"), body=body, model=JWTTemplateResponse, request_options=request_options, @@ -1690,7 +1689,7 @@ def create_magic_auth( } return self._client.request( method="post", - path="user_management/magic_auth", + path=("user_management", "magic_auth"), body=body, model=MagicAuth, request_options=request_options, @@ -1721,7 +1720,7 @@ def get_magic_auth( """ return self._client.request( method="get", - path=f"user_management/magic_auth/{quote(str(id), safe='')}", + path=("user_management", "magic_auth", str(id)), model=MagicAuth, request_options=request_options, ) @@ -1782,7 +1781,7 @@ def list_organization_memberships( } return self._client.request_page( method="get", - path="user_management/organization_memberships", + path=("user_management", "organization_memberships"), model=UserOrganizationMembership, params=params, request_options=request_options, @@ -1830,7 +1829,7 @@ def create_organization_membership( body["role_slugs"] = role.role_slugs return self._client.request( method="post", - path="user_management/organization_memberships", + path=("user_management", "organization_memberships"), body=body, model=OrganizationMembership, request_options=request_options, @@ -1861,7 +1860,7 @@ def get_organization_membership( """ return self._client.request( method="get", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}", + path=("user_management", "organization_memberships", str(id)), model=UserOrganizationMembership, request_options=request_options, ) @@ -1900,7 +1899,7 @@ def update_organization_membership( body["role_slugs"] = role.role_slugs return self._client.request( method="put", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}", + path=("user_management", "organization_memberships", str(id)), body=body, model=UserOrganizationMembership, request_options=request_options, @@ -1928,7 +1927,7 @@ def delete_organization_membership( """ self._client.request( method="delete", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}", + path=("user_management", "organization_memberships", str(id)), request_options=request_options, ) @@ -1964,7 +1963,7 @@ def deactivate_organization_membership( """ return self._client.request( method="put", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}/deactivate", + path=("user_management", "organization_memberships", str(id), "deactivate"), model=OrganizationMembership, request_options=request_options, ) @@ -2001,7 +2000,7 @@ def reactivate_organization_membership( """ return self._client.request( method="put", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}/reactivate", + path=("user_management", "organization_memberships", str(id), "reactivate"), model=UserOrganizationMembership, request_options=request_options, ) @@ -2035,7 +2034,7 @@ def create_redirect_uri( } return self._client.request( method="post", - path="user_management/redirect_uris", + path=("user_management", "redirect_uris"), body=body, model=RedirectUri, request_options=request_options, @@ -2085,7 +2084,7 @@ def list_user_authorized_applications( } return self._client.request_page( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/authorized_applications", + path=("user_management", "users", str(user_id), "authorized_applications"), model=AuthorizedConnectApplicationListData, params=params, request_options=request_options, @@ -2115,7 +2114,13 @@ def delete_user_authorized_application( """ self._client.request( method="delete", - path=f"user_management/users/{quote(str(user_id), safe='')}/authorized_applications/{quote(str(application_id), safe='')}", + path=( + "user_management", + "users", + str(user_id), + "authorized_applications", + str(application_id), + ), request_options=request_options, ) @@ -2165,7 +2170,7 @@ def list_user_api_keys( } return self._client.request_page( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/api_keys", + path=("user_management", "users", str(user_id), "api_keys"), model=UserApiKey, params=params, request_options=request_options, @@ -2213,7 +2218,7 @@ def create_user_api_key( } return self._client.request( method="post", - path=f"user_management/users/{quote(str(user_id), safe='')}/api_keys", + path=("user_management", "users", str(user_id), "api_keys"), body=body, model=UserApiKeyWithValue, request_options=request_options, @@ -2395,7 +2400,7 @@ def authenticate_with_code_pkce( AuthenticateResponse, self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2436,7 +2441,7 @@ async def get_jwks( """ return await self._client.request( method="get", - path=f"sso/jwks/{quote(str(client_id), safe='')}", + path=("sso", "jwks", str(client_id)), model=JwksResponse, request_options=request_options, ) @@ -2480,7 +2485,7 @@ async def create_authenticate( _body: Dict[str, Any] = body if isinstance(body, dict) else body.to_dict() return await self._client.request( method="post", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=_body, model=AuthenticateResponse, request_options=request_options, @@ -2518,7 +2523,7 @@ async def authenticate_with_password( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2557,7 +2562,7 @@ async def authenticate_with_code( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2593,7 +2598,7 @@ async def authenticate_with_refresh_token( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2631,7 +2636,7 @@ async def authenticate_with_magic_auth( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2666,7 +2671,7 @@ async def authenticate_with_email_verification( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2703,7 +2708,7 @@ async def authenticate_with_totp( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2738,7 +2743,7 @@ async def authenticate_with_organization_selection( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2769,7 +2774,7 @@ async def authenticate_with_device_code( return await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, @@ -2852,7 +2857,7 @@ def get_authorization_url( params["response_type"] = "code" if self._client.client_id is not None: params["client_id"] = self._client.client_id - return self._client.build_url("user_management/authorize", params) + return self._client.build_url(("user_management", "authorize"), params) async def create_device( self, @@ -2883,7 +2888,7 @@ async def create_device( } return await self._client.request( method="post", - path="user_management/authorize/device", + path=("user_management", "authorize", "device"), body=body, model=DeviceAuthorizationResponse, request_options=request_options, @@ -2922,7 +2927,7 @@ def get_logout_url( }.items() if v is not None } - return self._client.build_url("user_management/sessions/logout", params) + return self._client.build_url(("user_management", "sessions", "logout"), params) async def revoke_session( self, @@ -2956,7 +2961,7 @@ async def revoke_session( } await self._client.request( method="post", - path="user_management/sessions/revoke", + path=("user_management", "sessions", "revoke"), body=body, request_options=request_options, ) @@ -2990,7 +2995,7 @@ async def create_cors_origin( } return await self._client.request( method="post", - path="user_management/cors_origins", + path=("user_management", "cors_origins"), body=body, model=CORSOriginResponse, request_options=request_options, @@ -3021,7 +3026,7 @@ async def get_email_verification( """ return await self._client.request( method="get", - path=f"user_management/email_verification/{quote(str(id), safe='')}", + path=("user_management", "email_verification", str(id)), model=EmailVerification, request_options=request_options, ) @@ -3056,7 +3061,7 @@ async def reset_password( } return await self._client.request( method="post", - path="user_management/password_reset", + path=("user_management", "password_reset"), body=body, model=PasswordReset, request_options=request_options, @@ -3096,7 +3101,7 @@ async def confirm_password_reset( } return await self._client.request( method="post", - path="user_management/password_reset/confirm", + path=("user_management", "password_reset", "confirm"), body=body, model=ResetPasswordResponse, request_options=request_options, @@ -3127,7 +3132,7 @@ async def get_password_reset( """ return await self._client.request( method="get", - path=f"user_management/password_reset/{quote(str(id), safe='')}", + path=("user_management", "password_reset", str(id)), model=PasswordReset, request_options=request_options, ) @@ -3182,7 +3187,7 @@ async def list_users( } return await self._client.request_page( method="get", - path="user_management/users", + path=("user_management", "users"), model=User, params=params, request_options=request_options, @@ -3245,7 +3250,7 @@ async def create_user( body["password_hash_type"] = enum_value(password.password_hash_type) return await self._client.request( method="post", - path="user_management/users", + path=("user_management", "users"), body=body, model=User, request_options=request_options, @@ -3276,7 +3281,7 @@ async def get_user_by_external_id( """ return await self._client.request( method="get", - path=f"user_management/users/external_id/{quote(str(external_id), safe='')}", + path=("user_management", "users", "external_id", str(external_id)), model=User, request_options=request_options, ) @@ -3306,7 +3311,7 @@ async def get_user( """ return await self._client.request( method="get", - path=f"user_management/users/{quote(str(id), safe='')}", + path=("user_management", "users", str(id)), model=User, request_options=request_options, ) @@ -3372,7 +3377,7 @@ async def update_user( body["password_hash_type"] = enum_value(password.password_hash_type) return await self._client.request( method="put", - path=f"user_management/users/{quote(str(id), safe='')}", + path=("user_management", "users", str(id)), body=body, model=User, request_options=request_options, @@ -3400,7 +3405,7 @@ async def delete_user( """ await self._client.request( method="delete", - path=f"user_management/users/{quote(str(id), safe='')}", + path=("user_management", "users", str(id)), request_options=request_options, ) @@ -3437,7 +3442,7 @@ async def confirm_email_change( } return await self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_change/confirm", + path=("user_management", "users", str(id), "email_change", "confirm"), body=body, model=EmailChangeConfirmation, request_options=request_options, @@ -3475,7 +3480,7 @@ async def send_email_change( } return await self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_change/send", + path=("user_management", "users", str(id), "email_change", "send"), body=body, model=EmailChange, request_options=request_options, @@ -3513,7 +3518,7 @@ async def verify_email( } return await self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_verification/confirm", + path=("user_management", "users", str(id), "email_verification", "confirm"), body=body, model=VerifyEmailResponse, request_options=request_options, @@ -3545,7 +3550,7 @@ async def send_verification_email( """ return await self._client.request( method="post", - path=f"user_management/users/{quote(str(id), safe='')}/email_verification/send", + path=("user_management", "users", str(id), "email_verification", "send"), model=SendVerificationEmailResponse, request_options=request_options, ) @@ -3575,7 +3580,7 @@ async def get_user_identities( """ raw = await self._client.request_list( method="get", - path=f"user_management/users/{quote(str(id), safe='')}/identities", + path=("user_management", "users", str(id), "identities"), request_options=request_options, ) return [ @@ -3626,7 +3631,7 @@ async def list_sessions( } return await self._client.request_page( method="get", - path=f"user_management/users/{quote(str(id), safe='')}/sessions", + path=("user_management", "users", str(id), "sessions"), model=UserSessionsListItem, params=params, request_options=request_options, @@ -3679,7 +3684,7 @@ async def list_invitations( } return await self._client.request_page( method="get", - path="user_management/invitations", + path=("user_management", "invitations"), model=UserInvite, params=params, request_options=request_options, @@ -3734,7 +3739,7 @@ async def send_invitation( } return await self._client.request( method="post", - path="user_management/invitations", + path=("user_management", "invitations"), body=body, model=UserInvite, request_options=request_options, @@ -3765,7 +3770,7 @@ async def find_invitation_by_token( """ return await self._client.request( method="get", - path=f"user_management/invitations/by_token/{quote(str(token), safe='')}", + path=("user_management", "invitations", "by_token", str(token)), model=UserInvite, request_options=request_options, ) @@ -3795,7 +3800,7 @@ async def get_invitation( """ return await self._client.request( method="get", - path=f"user_management/invitations/{quote(str(id), safe='')}", + path=("user_management", "invitations", str(id)), model=UserInvite, request_options=request_options, ) @@ -3826,7 +3831,7 @@ async def accept_invitation( """ return await self._client.request( method="post", - path=f"user_management/invitations/{quote(str(id), safe='')}/accept", + path=("user_management", "invitations", str(id), "accept"), model=Invitation, request_options=request_options, ) @@ -3867,7 +3872,7 @@ async def resend_invitation( } return await self._client.request( method="post", - path=f"user_management/invitations/{quote(str(id), safe='')}/resend", + path=("user_management", "invitations", str(id), "resend"), body=body, model=UserInvite, request_options=request_options, @@ -3898,7 +3903,7 @@ async def revoke_invitation( """ return await self._client.request( method="post", - path=f"user_management/invitations/{quote(str(id), safe='')}/revoke", + path=("user_management", "invitations", str(id), "revoke"), model=Invitation, request_options=request_options, ) @@ -3923,7 +3928,7 @@ async def list_jwt_template( """ return await self._client.request( method="get", - path="user_management/jwt_template", + path=("user_management", "jwt_template"), model=JWTTemplateResponse, request_options=request_options, ) @@ -3956,7 +3961,7 @@ async def update_jwt_template( } return await self._client.request( method="put", - path="user_management/jwt_template", + path=("user_management", "jwt_template"), body=body, model=JWTTemplateResponse, request_options=request_options, @@ -3998,7 +4003,7 @@ async def create_magic_auth( } return await self._client.request( method="post", - path="user_management/magic_auth", + path=("user_management", "magic_auth"), body=body, model=MagicAuth, request_options=request_options, @@ -4029,7 +4034,7 @@ async def get_magic_auth( """ return await self._client.request( method="get", - path=f"user_management/magic_auth/{quote(str(id), safe='')}", + path=("user_management", "magic_auth", str(id)), model=MagicAuth, request_options=request_options, ) @@ -4090,7 +4095,7 @@ async def list_organization_memberships( } return await self._client.request_page( method="get", - path="user_management/organization_memberships", + path=("user_management", "organization_memberships"), model=UserOrganizationMembership, params=params, request_options=request_options, @@ -4138,7 +4143,7 @@ async def create_organization_membership( body["role_slugs"] = role.role_slugs return await self._client.request( method="post", - path="user_management/organization_memberships", + path=("user_management", "organization_memberships"), body=body, model=OrganizationMembership, request_options=request_options, @@ -4169,7 +4174,7 @@ async def get_organization_membership( """ return await self._client.request( method="get", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}", + path=("user_management", "organization_memberships", str(id)), model=UserOrganizationMembership, request_options=request_options, ) @@ -4208,7 +4213,7 @@ async def update_organization_membership( body["role_slugs"] = role.role_slugs return await self._client.request( method="put", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}", + path=("user_management", "organization_memberships", str(id)), body=body, model=UserOrganizationMembership, request_options=request_options, @@ -4236,7 +4241,7 @@ async def delete_organization_membership( """ await self._client.request( method="delete", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}", + path=("user_management", "organization_memberships", str(id)), request_options=request_options, ) @@ -4272,7 +4277,7 @@ async def deactivate_organization_membership( """ return await self._client.request( method="put", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}/deactivate", + path=("user_management", "organization_memberships", str(id), "deactivate"), model=OrganizationMembership, request_options=request_options, ) @@ -4309,7 +4314,7 @@ async def reactivate_organization_membership( """ return await self._client.request( method="put", - path=f"user_management/organization_memberships/{quote(str(id), safe='')}/reactivate", + path=("user_management", "organization_memberships", str(id), "reactivate"), model=UserOrganizationMembership, request_options=request_options, ) @@ -4343,7 +4348,7 @@ async def create_redirect_uri( } return await self._client.request( method="post", - path="user_management/redirect_uris", + path=("user_management", "redirect_uris"), body=body, model=RedirectUri, request_options=request_options, @@ -4393,7 +4398,7 @@ async def list_user_authorized_applications( } return await self._client.request_page( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/authorized_applications", + path=("user_management", "users", str(user_id), "authorized_applications"), model=AuthorizedConnectApplicationListData, params=params, request_options=request_options, @@ -4423,7 +4428,13 @@ async def delete_user_authorized_application( """ await self._client.request( method="delete", - path=f"user_management/users/{quote(str(user_id), safe='')}/authorized_applications/{quote(str(application_id), safe='')}", + path=( + "user_management", + "users", + str(user_id), + "authorized_applications", + str(application_id), + ), request_options=request_options, ) @@ -4473,7 +4484,7 @@ async def list_user_api_keys( } return await self._client.request_page( method="get", - path=f"user_management/users/{quote(str(user_id), safe='')}/api_keys", + path=("user_management", "users", str(user_id), "api_keys"), model=UserApiKey, params=params, request_options=request_options, @@ -4521,7 +4532,7 @@ async def create_user_api_key( } return await self._client.request( method="post", - path=f"user_management/users/{quote(str(user_id), safe='')}/api_keys", + path=("user_management", "users", str(user_id), "api_keys"), body=body, model=UserApiKeyWithValue, request_options=request_options, @@ -4703,7 +4714,7 @@ async def authenticate_with_code_pkce( AuthenticateResponse, await self._client.request( method="POST", - path="user_management/authenticate", + path=("user_management", "authenticate"), body=body, model=AuthenticateResponse, request_options=request_options, diff --git a/src/workos/user_management_organization_membership_groups/_resource.py b/src/workos/user_management_organization_membership_groups/_resource.py index 5b04421e..48fd4159 100644 --- a/src/workos/user_management_organization_membership_groups/_resource.py +++ b/src/workos/user_management_organization_membership_groups/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -63,7 +62,7 @@ def list_organization_membership_groups( } return self._client.request_page( method="get", - path=f"user_management/organization_memberships/{quote(str(om_id), safe='')}/groups", + path=("user_management", "organization_memberships", str(om_id), "groups"), model=Group, params=params, request_options=request_options, @@ -119,7 +118,7 @@ async def list_organization_membership_groups( } return await self._client.request_page( method="get", - path=f"user_management/organization_memberships/{quote(str(om_id), safe='')}/groups", + path=("user_management", "organization_memberships", str(om_id), "groups"), model=Group, params=params, request_options=request_options, diff --git a/src/workos/vault.py b/src/workos/vault.py index 06740d2e..77f4058f 100644 --- a/src/workos/vault.py +++ b/src/workos/vault.py @@ -334,7 +334,7 @@ def read_object(self, *, object_id: str) -> VaultObject: """Get a Vault object with the value decrypted.""" response = self._client.request( method="get", - path=f"vault/v1/kv/{object_id}", + path=("vault", "v1", "kv", str(object_id)), model=VaultObject, ) return response @@ -343,7 +343,7 @@ def read_object_by_name(self, *, name: str) -> VaultObject: """Get a Vault object by name with the value decrypted.""" response = self._client.request( method="get", - path=f"vault/v1/kv/name/{name}", + path=("vault", "v1", "kv", "name", str(name)), model=VaultObject, ) return response @@ -352,7 +352,7 @@ def get_object_metadata(self, *, object_id: str) -> VaultObject: """Get a Vault object's metadata without decrypting the value.""" response = self._client.request( method="get", - path=f"vault/v1/kv/{object_id}/metadata", + path=("vault", "v1", "kv", str(object_id), "metadata"), model=VaultObject, ) return response @@ -373,7 +373,7 @@ def list_objects( response = self._client.request_raw( method="get", - path="vault/v1/kv", + path=("vault", "v1", "kv"), params=params, ) data: List[Dict[str, Any]] = response.get("data", []) @@ -383,7 +383,7 @@ def list_object_versions(self, *, object_id: str) -> Sequence[ObjectVersion]: """Gets a list of versions for a specific Vault object.""" response = self._client.request_raw( method="get", - path=f"vault/v1/kv/{object_id}/versions", + path=("vault", "v1", "kv", str(object_id), "versions"), ) data: List[Dict[str, Any]] = response.get("data", []) return [ObjectVersion.from_dict(v) for v in data] @@ -398,7 +398,7 @@ def create_object( """Create a new Vault encrypted object.""" response = self._client.request( method="post", - path="vault/v1/kv", + path=("vault", "v1", "kv"), body={"name": name, "value": value, "key_context": key_context}, model=ObjectMetadata, ) @@ -418,7 +418,7 @@ def update_object( response = self._client.request( method="put", - path=f"vault/v1/kv/{object_id}", + path=("vault", "v1", "kv", str(object_id)), body=body, model=VaultObject, ) @@ -428,7 +428,7 @@ def delete_object(self, *, object_id: str) -> None: """Permanently delete a Vault encrypted object.""" self._client.request( method="delete", - path=f"vault/v1/kv/{object_id}", + path=("vault", "v1", "kv", str(object_id)), ) # -- Key operations -- @@ -437,7 +437,7 @@ def create_data_key(self, *, key_context: KeyContext) -> DataKeyPair: """Generate a data key for local encryption.""" response = self._client.request_raw( method="post", - path="vault/v1/keys/data-key", + path=("vault", "v1", "keys", "data-key"), body={"context": key_context}, ) return DataKeyPair( @@ -450,7 +450,7 @@ def decrypt_data_key(self, *, keys: str) -> DataKey: """Decrypt encrypted data keys previously generated by create_data_key.""" response = self._client.request_raw( method="post", - path="vault/v1/keys/decrypt", + path=("vault", "v1", "keys", "decrypt"), body={"keys": keys}, ) return DataKey(id=response["id"], key=response["data_key"]) @@ -519,7 +519,7 @@ async def read_object(self, *, object_id: str) -> VaultObject: """Get a Vault object with the value decrypted.""" response = await self._client.request( method="get", - path=f"vault/v1/kv/{object_id}", + path=("vault", "v1", "kv", str(object_id)), model=VaultObject, ) return response @@ -528,7 +528,7 @@ async def read_object_by_name(self, *, name: str) -> VaultObject: """Get a Vault object by name with the value decrypted.""" response = await self._client.request( method="get", - path=f"vault/v1/kv/name/{name}", + path=("vault", "v1", "kv", "name", str(name)), model=VaultObject, ) return response @@ -537,7 +537,7 @@ async def get_object_metadata(self, *, object_id: str) -> VaultObject: """Get a Vault object's metadata without decrypting the value.""" response = await self._client.request( method="get", - path=f"vault/v1/kv/{object_id}/metadata", + path=("vault", "v1", "kv", str(object_id), "metadata"), model=VaultObject, ) return response @@ -557,7 +557,7 @@ async def list_objects( params["after"] = after response = await self._client.request_raw( method="get", - path="vault/v1/kv", + path=("vault", "v1", "kv"), params=params, ) data: List[Dict[str, Any]] = response.get("data", []) @@ -567,7 +567,7 @@ async def list_object_versions(self, *, object_id: str) -> Sequence[ObjectVersio """Gets a list of versions for a specific Vault object.""" response = await self._client.request_raw( method="get", - path=f"vault/v1/kv/{object_id}/versions", + path=("vault", "v1", "kv", str(object_id), "versions"), ) data: List[Dict[str, Any]] = response.get("data", []) return [ObjectVersion.from_dict(v) for v in data] @@ -582,7 +582,7 @@ async def create_object( """Create a new Vault encrypted object.""" response = await self._client.request( method="post", - path="vault/v1/kv", + path=("vault", "v1", "kv"), body={"name": name, "value": value, "key_context": key_context}, model=ObjectMetadata, ) @@ -601,7 +601,7 @@ async def update_object( body["version_check"] = version_check response = await self._client.request( method="put", - path=f"vault/v1/kv/{object_id}", + path=("vault", "v1", "kv", str(object_id)), body=body, model=VaultObject, ) @@ -611,14 +611,14 @@ async def delete_object(self, *, object_id: str) -> None: """Permanently delete a Vault encrypted object.""" await self._client.request( method="delete", - path=f"vault/v1/kv/{object_id}", + path=("vault", "v1", "kv", str(object_id)), ) async def create_data_key(self, *, key_context: KeyContext) -> DataKeyPair: """Generate a data key for local encryption.""" response = await self._client.request_raw( method="post", - path="vault/v1/keys/data-key", + path=("vault", "v1", "keys", "data-key"), body={"context": key_context}, ) return DataKeyPair( @@ -631,7 +631,7 @@ async def decrypt_data_key(self, *, keys: str) -> DataKey: """Decrypt encrypted data keys previously generated by create_data_key.""" response = await self._client.request_raw( method="post", - path="vault/v1/keys/decrypt", + path=("vault", "v1", "keys", "decrypt"), body={"keys": keys}, ) return DataKey(id=response["id"], key=response["data_key"]) diff --git a/src/workos/webhooks/_resource.py b/src/workos/webhooks/_resource.py index be93fa58..652f051e 100644 --- a/src/workos/webhooks/_resource.py +++ b/src/workos/webhooks/_resource.py @@ -3,7 +3,6 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -from urllib.parse import quote if TYPE_CHECKING: from .._client import AsyncWorkOSClient, WorkOSClient @@ -73,7 +72,7 @@ def list_webhook_endpoints( } return self._client.request_page( method="get", - path="webhook_endpoints", + path=("webhook_endpoints",), model=WebhookEndpointJson, params=params, request_options=request_options, @@ -111,7 +110,7 @@ def create_webhook_endpoint( } return self._client.request( method="post", - path="webhook_endpoints", + path=("webhook_endpoints",), body=body, model=WebhookEndpointJson, request_options=request_options, @@ -159,7 +158,7 @@ def update_webhook_endpoint( } return self._client.request( method="patch", - path=f"webhook_endpoints/{quote(str(id), safe='')}", + path=("webhook_endpoints", str(id)), body=body, model=WebhookEndpointJson, request_options=request_options, @@ -187,7 +186,7 @@ def delete_webhook_endpoint( """ self._client.request( method="delete", - path=f"webhook_endpoints/{quote(str(id), safe='')}", + path=("webhook_endpoints", str(id)), request_options=request_options, ) @@ -330,7 +329,7 @@ async def list_webhook_endpoints( } return await self._client.request_page( method="get", - path="webhook_endpoints", + path=("webhook_endpoints",), model=WebhookEndpointJson, params=params, request_options=request_options, @@ -368,7 +367,7 @@ async def create_webhook_endpoint( } return await self._client.request( method="post", - path="webhook_endpoints", + path=("webhook_endpoints",), body=body, model=WebhookEndpointJson, request_options=request_options, @@ -416,7 +415,7 @@ async def update_webhook_endpoint( } return await self._client.request( method="patch", - path=f"webhook_endpoints/{quote(str(id), safe='')}", + path=("webhook_endpoints", str(id)), body=body, model=WebhookEndpointJson, request_options=request_options, @@ -444,7 +443,7 @@ async def delete_webhook_endpoint( """ await self._client.request( method="delete", - path=f"webhook_endpoints/{quote(str(id), safe='')}", + path=("webhook_endpoints", str(id)), request_options=request_options, ) diff --git a/src/workos/widgets/_resource.py b/src/workos/widgets/_resource.py index 8d9e491d..0834e1ea 100644 --- a/src/workos/widgets/_resource.py +++ b/src/workos/widgets/_resource.py @@ -58,7 +58,7 @@ def create_token( } return self._client.request( method="post", - path="widgets/token", + path=("widgets", "token"), body=body, model=WidgetSessionTokenResponse, request_options=request_options, @@ -111,7 +111,7 @@ async def create_token( } return await self._client.request( method="post", - path="widgets/token", + path=("widgets", "token"), body=body, model=WidgetSessionTokenResponse, request_options=request_options, diff --git a/tests/test_generated_client.py b/tests/test_generated_client.py index a675cc43..3c059fcd 100644 --- a/tests/test_generated_client.py +++ b/tests/test_generated_client.py @@ -61,7 +61,7 @@ def test_raises_400(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(BadRequestError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_401(self, httpx_mock): @@ -73,7 +73,7 @@ def test_raises_401(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthenticationError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_403(self, httpx_mock): @@ -85,7 +85,7 @@ def test_raises_403(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthorizationError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_email_verification_required(self, httpx_mock): @@ -103,7 +103,7 @@ def test_raises_email_verification_required(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(EmailVerificationRequiredError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_123" assert exc_info.value.email_verification_id == "ev_123" assert exc_info.value.email == "user@example.com" @@ -123,7 +123,7 @@ def test_raises_mfa_enrollment(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(MfaEnrollmentError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_456" assert exc_info.value.user == {"id": "user_123", "email": "user@example.com"} client.close() @@ -143,7 +143,7 @@ def test_raises_mfa_challenge(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(MfaChallengeError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_789" assert exc_info.value.user == {"id": "user_123"} assert exc_info.value.authentication_factors == [{"id": "af_1", "type": "totp"}] @@ -164,7 +164,7 @@ def test_raises_organization_selection_required(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(OrganizationSelectionRequiredError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_org" assert exc_info.value.organizations == [{"id": "org_1", "name": "Acme"}] client.close() @@ -184,7 +184,7 @@ def test_raises_sso_required(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(SsoRequiredError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_sso" assert exc_info.value.email == "user@example.com" assert exc_info.value.connection_ids == ["conn_1", "conn_2"] @@ -206,7 +206,7 @@ def test_raises_org_auth_methods_required(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(OrganizationAuthMethodsRequiredError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_oam" assert exc_info.value.sso_connection_ids == ["conn_1"] assert exc_info.value.auth_methods == {"google_oauth": True, "password": False} @@ -235,7 +235,7 @@ def test_raises_simple_auth_flow_errors(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(expected_class) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_tok" client.close() @@ -252,7 +252,7 @@ def test_auth_flow_errors_are_catchable_as_authorization_error(self, httpx_mock) api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthorizationError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_auth_flow_errors_are_catchable_as_authentication_flow_error( @@ -270,7 +270,7 @@ def test_auth_flow_errors_are_catchable_as_authentication_flow_error( api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthenticationFlowError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_unknown_403_code_raises_authorization_error(self, httpx_mock): @@ -282,7 +282,7 @@ def test_unknown_403_code_raises_authorization_error(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthorizationError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert not isinstance(exc_info.value, AuthenticationFlowError) client.close() @@ -295,7 +295,7 @@ def test_raises_404(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(NotFoundError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_409(self, httpx_mock): @@ -307,7 +307,7 @@ def test_raises_409(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(ConflictError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_422(self, httpx_mock): @@ -319,7 +319,7 @@ def test_raises_422(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(UnprocessableEntityError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_429(self, httpx_mock): @@ -331,7 +331,7 @@ def test_raises_429(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(RateLimitExceededError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_raises_500(self, httpx_mock): @@ -343,13 +343,13 @@ def test_raises_500(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(ServerError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_idempotency_key_on_post(self, httpx_mock): httpx_mock.add_response(json={}) client = WorkOSClient(api_key="sk_test_123", client_id="client_test") - client.request("POST", "test") + client.request("POST", ("test",)) request = httpx_mock.get_request() assert "Idempotency-Key" in request.headers client.close() @@ -357,7 +357,7 @@ def test_idempotency_key_on_post(self, httpx_mock): def test_no_idempotency_key_on_get(self, httpx_mock): httpx_mock.add_response(json={}) client = WorkOSClient(api_key="sk_test_123", client_id="client_test") - client.request("GET", "test") + client.request("GET", ("test",)) request = httpx_mock.get_request() assert "Idempotency-Key" not in request.headers client.close() @@ -365,7 +365,7 @@ def test_no_idempotency_key_on_get(self, httpx_mock): def test_no_authorization_header_without_api_key(self, httpx_mock): httpx_mock.add_response(json={}) client = WorkOSClient(client_id="client_test") - client.request("GET", "test") + client.request("GET", ("test",)) request = httpx_mock.get_request() assert "Authorization" not in request.headers client.close() @@ -373,7 +373,7 @@ def test_no_authorization_header_without_api_key(self, httpx_mock): def test_empty_body_sends_json(self, httpx_mock): httpx_mock.add_response(json={}) client = WorkOSClient(api_key="sk_test_123", client_id="client_test") - client.request("PUT", "test", body={}) + client.request("PUT", ("test",), body={}) request = httpx_mock.get_request() assert request.content == b"{}" client.close() @@ -393,7 +393,7 @@ def test_retry_exhaustion_raises_rate_limit(self, httpx_mock, monkeypatch): api_key="sk_test_123", client_id="client_test", max_retries=3 ) with pytest.raises(RateLimitExceededError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_rate_limit_retry_after_is_parsed(self, httpx_mock): @@ -406,7 +406,7 @@ def test_rate_limit_retry_after_is_parsed(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(RateLimitExceededError) as exc_info: - client.request("GET", "test") + client.request("GET", ("test",)) assert exc_info.value.retry_after == 30.0 client.close() @@ -417,7 +417,7 @@ def test_timeout_error_is_wrapped(self, httpx_mock, monkeypatch): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(generated_client_module.WorkOSTimeoutError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_connection_error_is_wrapped(self, httpx_mock, monkeypatch): @@ -427,7 +427,7 @@ def test_connection_error_is_wrapped(self, httpx_mock, monkeypatch): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(generated_client_module.WorkOSConnectionError): - client.request("GET", "test") + client.request("GET", ("test",)) client.close() def test_documented_import_surface_exposes_resources(self): @@ -456,7 +456,7 @@ def test_request_raw_preserves_json_dict_response(self, httpx_mock): client = WorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) - result = client.request_raw("GET", "test") + result = client.request_raw("GET", ("test",)) assert result == {"ok": True} client.close() @@ -465,7 +465,7 @@ def test_request_list_preserves_json_array_response(self, httpx_mock): client = WorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) - result = client.request_list("GET", "test") + result = client.request_list("GET", ("test",)) assert result == [{"id": "item_123"}] client.close() @@ -474,7 +474,7 @@ def test_request_returns_none_for_non_json_success_without_model(self, httpx_moc client = WorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) - result = client.request("DELETE", "test") + result = client.request("DELETE", ("test",)) assert result is None client.close() @@ -507,7 +507,7 @@ async def test_request_raw_preserves_json_dict_response(self, httpx_mock): client = AsyncWorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) - result = await client.request_raw("GET", "test") + result = await client.request_raw("GET", ("test",)) assert result == {"ok": True} await client.close() @@ -516,7 +516,7 @@ async def test_request_list_preserves_json_array_response(self, httpx_mock): client = AsyncWorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) - result = await client.request_list("GET", "test") + result = await client.request_list("GET", ("test",)) assert result == [{"id": "item_123"}] await client.close() @@ -527,7 +527,7 @@ async def test_request_returns_none_for_non_json_success_without_model( client = AsyncWorkOSClient( api_key="sk_test_123", client_id="client_test", max_retries=0 ) - result = await client.request("DELETE", "test") + result = await client.request("DELETE", ("test",)) assert result is None await client.close() @@ -540,7 +540,7 @@ async def test_raises_400(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(BadRequestError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_401(self, httpx_mock): @@ -552,7 +552,7 @@ async def test_raises_401(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthenticationError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_403(self, httpx_mock): @@ -564,7 +564,7 @@ async def test_raises_403(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthorizationError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_email_verification_required(self, httpx_mock): @@ -582,7 +582,7 @@ async def test_raises_email_verification_required(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(EmailVerificationRequiredError) as exc_info: - await client.request("GET", "test") + await client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_123" assert exc_info.value.email_verification_id == "ev_123" assert exc_info.value.email == "user@example.com" @@ -603,7 +603,7 @@ async def test_raises_mfa_challenge(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(MfaChallengeError) as exc_info: - await client.request("GET", "test") + await client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_789" assert exc_info.value.authentication_factors == [{"id": "af_1", "type": "totp"}] await client.close() @@ -623,7 +623,7 @@ async def test_raises_sso_required(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(SsoRequiredError) as exc_info: - await client.request("GET", "test") + await client.request("GET", ("test",)) assert exc_info.value.pending_authentication_token == "pat_sso" assert exc_info.value.connection_ids == ["conn_1"] await client.close() @@ -641,7 +641,7 @@ async def test_auth_flow_errors_backward_compat(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthorizationError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_unknown_403_code_raises_authorization_error(self, httpx_mock): @@ -653,7 +653,7 @@ async def test_unknown_403_code_raises_authorization_error(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(AuthorizationError) as exc_info: - await client.request("GET", "test") + await client.request("GET", ("test",)) assert not isinstance(exc_info.value, AuthenticationFlowError) await client.close() @@ -666,7 +666,7 @@ async def test_raises_404(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(NotFoundError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_409(self, httpx_mock): @@ -678,7 +678,7 @@ async def test_raises_409(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(ConflictError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_422(self, httpx_mock): @@ -690,7 +690,7 @@ async def test_raises_422(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(UnprocessableEntityError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_429(self, httpx_mock): @@ -702,7 +702,7 @@ async def test_raises_429(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(RateLimitExceededError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_raises_500(self, httpx_mock): @@ -714,7 +714,7 @@ async def test_raises_500(self, httpx_mock): api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(ServerError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_timeout_error_is_wrapped(self, httpx_mock, monkeypatch): @@ -727,7 +727,7 @@ async def _sleep(_: float) -> None: api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(generated_client_module.WorkOSTimeoutError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close() async def test_connection_error_is_wrapped(self, httpx_mock, monkeypatch): @@ -740,5 +740,5 @@ async def _sleep(_: float) -> None: api_key="sk_test_123", client_id="client_test", max_retries=0 ) with pytest.raises(generated_client_module.WorkOSConnectionError): - await client.request("GET", "test") + await client.request("GET", ("test",)) await client.close()