feat: add async client base, AsyncHTTPClient, AsyncDescopeClient, and async TOTP#1562
feat: add async client base, AsyncHTTPClient, AsyncDescopeClient, and async TOTP#1562LioriE wants to merge 18 commits into
Conversation
46a82d0 to
99839cc
Compare
Coverage reportThe coverage rate went from
Diff Coverage details (click to unfold)descope/management/access_key.py
descope/_client_base.py
descope/_auth_base.py
descope/authmethod/magiclink_async.py
descope/authmethod/password.py
descope/management/user_async.py
descope/management/_outbound_application_base.py
descope/authmethod/sso.py
descope/http_client_async.py
descope/authmethod/saml_async.py
descope/management/_audit_base.py
descope/authmethod/webauthn.py
descope/management/audit_async.py
descope/_http_client_base.py
descope/authmethod/_totp_base.py
descope/management/sso_settings_async.py
descope/management/jwt_async.py
descope/authmethod/_oauth_base.py
descope/management/tenant_async.py
descope/authmethod/otp.py
descope/authmethod/webauthn_async.py
descope/init.py
descope/authmethod/oauth.py
descope/authmethod/sso_async.py
descope/management/_user_base.py
descope/authmethod/_saml_base.py
descope/management/access_key_async.py
descope/management/role_async.py
descope/authmethod/_otp_base.py
descope/management/sso_application_async.py
descope/management/permission_async.py
descope/management/license_async.py
descope/authmethod/saml.py
descope/management/authz_async.py
descope/management/fga_async.py
descope/authmethod/magiclink.py
descope/management/_access_key_base.py
descope/authmethod/_enchantedlink_base.py
descope/management/sso_application.py
descope/management/group_async.py
descope/descope_client.py
descope/management/outbound_application.py
descope/management/sso_settings.py
descope/management/user.py
descope/management/_jwt_base.py
descope/authmethod/totp_async.py
descope/management/management_key_async.py
descope/http_client.py
descope/management/_sso_application_base.py
descope/authmethod/enchantedlink.py
descope/authmethod/_webauthn_base.py
descope/management/tenant.py
descope/authmethod/_password_base.py
descope/_http_base.py
descope/management/audit.py
descope/management/descoper_async.py
descope/mgmt_async.py
descope/authmethod/otp_async.py
descope/management/_tenant_base.py
descope/authmethod/totp.py
descope/management/project_async.py
descope/authmethod/oauth_async.py
descope/authmethod/password_async.py
descope/authmethod/_sso_base.py
descope/management/flow_async.py
descope/management/_sso_settings_base.py
descope/authmethod/_magiclink_base.py
descope/descope_client_async.py
descope/authmethod/enchantedlink_async.py
descope/management/outbound_application_async.py
descope/management/jwt.py
|
Move PUBLIC_KEY_DICT, VALID_REFRESH_TOKEN, VALID_SESSION_TOKEN, and EXPIRED_SESSION_TOKEN from individual test files into testutils.py. Move assert_http_called into conftest.py. Replace old test files with the unified sync/async parity versions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace ~45 three-line dash-banner comment blocks across 8 files with nothing — class names, method names, and docstrings already describe what those banners labelled. The only banner-style block kept is the SOCKS-proxy workaround doc in conftest.py, which is real documentation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@shuni review this async implementation will it scale to all other methods? |
|
🐕 Review complete — View session on Shuni Portal 🐾 |
There was a problem hiding this comment.
🐕 Shuni's Review
POC that extracts shared sync/async base classes and adds AsyncHTTPClient, AsyncDescopeClient, and async TOTP. Clean refactor — the base-class extraction preserves behaviour and sync/async parity is faithful (verified against Auth methods).
Sniffed out 3 issues:
- 1 🟡 MEDIUM: async validate/refresh path runs blocking sync I/O on the event loop
- 2 🟢 LOW: committed sandbox workaround in conftest; verbose
putparity gap
No bugs that break — good bones for a POC! Woof!
| params=params, | ||
| ) | ||
| ) | ||
| self._raise_from_response(response) |
There was a problem hiding this comment.
🟢 LOW: put skips the if self.verbose: self._last_response_var.set(...) capture that get/post/patch/delete all do, so get_last_response() returns a stale response after a PUT. (The sync HTTPClient.put has the same gap, so this preserves parity — but since this is new code it's a cheap spot to fix both.)
|
@shuni follow the example set in this PR follow conventions and patterns set in this PR. perform this in another branch and open a pull request to this branch. |
|
🐕 Uh oh! I ran into an error. Please check the logs for details. |
For each method (OAuth, SSO, SAML, OTP, MagicLink, EnchantedLink, Password, WebAuthn): extract an I/O-free _<name>_base.py with shared static helpers, refactor the sync class to inherit it, add a parallel async class using AsyncAuthBase, and rewrite the test file to the parametrized client_factory harness so every test runs under both sync and async. Wires all new async classes into DescopeClientAsync.
…hon-sdk into feat/poc-async-base-and-totp
- Add missing `from datetime import datetime` to audit.py / audit_async.py - Rename camelCase params in tenant_async.py (JITDisabled→jit_disabled) and user_async.py (withRefreshToken/forceRefresh→snake_case); `from __future__ import annotations` caused ruff N803 to fire only on the async files - Fix N806 variable names in _user_base.py (usersBody→users_body, uBody→u_body) - Wire JWTBase into jwt.py / jwt_async.py; UserBase._validate_search_pagination into user.py / user_async.py; complete sso_settings_async.py dedup - Remove unused imports (Any from fga_async, url_params_to_dict from outbound_application)
| mock_delete.assert_called_with( | ||
| f"{common.DEFAULT_BASE_URL}{MgmtV1.descoper_delete_path}", | ||
| with client.mock_mgmt_delete(make_response()) as mock_delete: | ||
| assert await client.invoke(client.mgmt.descoper.delete("U2111111111111111111111111")) is None |
|
@shuni review this full implementation. does it follow the example of totp? are there any issues with the management implementation? |
|
🐕 Done! No changes were needed. 📝 AnalysisHere's a summary of the deep-dive review: Does it follow the TOTP pattern?Yes, consistently across the entire codebase. The TOTP pattern is:
All 9 auth methods and all 18 management modules follow this dual-inheritance pattern. The Issues with the management implementation1 real bug found (HIGH): The 1 design concern (MEDIUM): 📋 Actions TakenNo tasks recorded |
There was a problem hiding this comment.
🐕 Shuni's Review — Deep Dive
Thorough review of async infrastructure, TOTP pattern compliance, and management wiring.
Pattern compliance: solid. All 9 auth methods and all 18 management modules follow the TOTP dual-inheritance pattern (XBase + AuthBase/AsyncAuthBase for auth, XBase + HTTPBase/AsyncHTTPBase for mgmt). DescopeClientBase, HTTPClientBase, MGMTAsync — all properly mirror their sync counterparts.
Sniffed out 4 issues:
- 1 🟠 HIGH: mgmt test incorrectly skipped for async (stale skip)
- 1 🟡 MEDIUM: sync blocking I/O in async constructor
- 2 🟢 LOW: conftest scaffold; verbose
putgap
See inline comments. Good bones on this refactor — the base-class extraction is clean! Woof!
| async def test_mgmt(self, descope_client): | ||
| if descope_client.mode != "sync": | ||
| pytest.skip("mgmt not available on DescopeClientAsync") |
There was a problem hiding this comment.
🟠 HIGH: This skip is stale — DescopeClientAsync does wire up MGMTAsync now (line 94–98 of descope_client_async.py). The management-key guard test (descope_client.mgmt.tenant raising AuthException without a key) is a sync property access that works identically for both sync and async clients. This skip means the async management guard is never tested.
Remove the skip so the test runs for both modes. The descope_client fixture creates both variants without a management key, so every mgmt.* property access should raise AuthException regardless of mode.
| async def test_mgmt(self, descope_client): | |
| if descope_client.mode != "sync": | |
| pytest.skip("mgmt not available on DescopeClientAsync") | |
| async def test_mgmt(self, descope_client): |
- Remove stale pytest.skip from test_mgmt (MGMTAsync raises AuthException without key, same as sync) - Add per-method one-liner docstrings to all 8 async auth classes to match TOTPAsync pattern - Fix response param shadowing in webauthn sign_up_finish/sign_in_finish (sync + async) - Add gitleaks fingerprints for test-fixture JWTs in test_descope_client_parity.py
Summary
_ClientBaseand_AuthBaseto extract shared sync/async logicAsyncHTTPClientwithhttpx.AsyncClientfor non-blocking HTTPAsyncDescopeClientas the async counterpart toDescopeClientAsyncTOTPbacked by a shared_TOTPBaseDescopeClientandTOTPto reuse the new base classesStatus
Draft / POC — async infrastructure is in place; remaining auth methods need to be ported to async.
Test plan
tests/test_async_http_client.py— AsyncHTTPClient unit teststests/test_descope_client_parity.py— sync/async client behaviour paritytests/test_totp_parity.py— sync/async TOTP parity