Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e24084e
style: fix ruff formatting in test_session.py
gjtorikian Mar 27, 2026
6033b93
chore: switch type checker from mypy to pyright strict
gjtorikian Mar 27, 2026
39e7c78
chore(deps): update dev dependencies (ruff, nox, nox-uv) (#601)
gjtorikian Mar 27, 2026
33e6aec
fix: resolve 38 pre-existing pyright errors in tests and fixtures
claude Mar 28, 2026
f2196c1
fix: set asyncio_default_fixture_loop_scope to silence deprecation wa…
claude Mar 28, 2026
6e0b60f
add `pytest-httpx` to `test` group
gjtorikian Mar 30, 2026
7184dd2
update dependencies
gjtorikian Mar 30, 2026
f57e7cc
swap mypy in lint CI for pyright
gjtorikian Mar 30, 2026
dab75bd
remove legacy types/, compat layer, and handwritten service modules
gjtorikian Mar 31, 2026
1516214
remove pydantic
gjtorikian Mar 31, 2026
fda2f5e
Rewrite CLAUDE.md
gjtorikian Mar 31, 2026
567c606
lock down `oagen-ignore` files
gjtorikian Mar 31, 2026
603f860
Add generated SDK modules with hand-maintained fixes
gjtorikian Mar 31, 2026
2e21112
lock cleanup
gjtorikian Mar 31, 2026
556cba6
format what needs formatting
gjtorikian Mar 31, 2026
62bb01e
remove these weird aliases
gjtorikian Mar 31, 2026
b6de8fc
drop compat layers
gjtorikian Mar 31, 2026
d78dc9d
restore some aliases
gjtorikian Mar 31, 2026
1be98d5
chore(python): regenerate sdk from updated emitter
gjtorikian Mar 31, 2026
b2b1fbf
fix: improve method names, restore timeout, add Event fields
gjtorikian Mar 31, 2026
58a7019
chore(python): regenerate SDK with empty-model pass fix
gjtorikian Mar 31, 2026
4138e96
chore(python): regenerate SDK with ignore-block class replacement fix
gjtorikian Mar 31, 2026
f62010c
chore(python): regenerate SDK with ignore-block and fixture fixes
gjtorikian Apr 1, 2026
901c70a
Regenerate client credential handling
gjtorikian Apr 1, 2026
ed07d10
Merge branch 'next-major' into oagen
gjtorikian Apr 1, 2026
f1f0d8f
initial migration guide
gjtorikian Apr 1, 2026
c5b1a56
chore(python): regenerate SDK with exceptions and types backwards-compat
gjtorikian Apr 1, 2026
314a809
Merge branch 'next-major' into oagen
gjtorikian Apr 3, 2026
f6ab893
Remove deleted files
gjtorikian Apr 3, 2026
75d9b30
change method name
gjtorikian Apr 3, 2026
7688f27
explicit cleanup
gjtorikian Apr 3, 2026
b030245
Add vault describe_object endpoint and tests for passwordless/vault
gjtorikian Apr 3, 2026
c5c613e
Rename vault describe_object to get_object_metadata
gjtorikian Apr 3, 2026
d5e0dc0
feat: regenerate SDK with PEP 8 error naming and design fixes
gjtorikian Apr 3, 2026
30f14e8
feat(python): add non-spec helpers H01-H19
gjtorikian Apr 3, 2026
711b4ec
style: format hand-maintained files with ruff
gjtorikian Apr 3, 2026
245994b
feat: rename client classes to WorkOSClient / AsyncWorkOSClient
gjtorikian Apr 3, 2026
1f13c3a
feat: strip Dto from enum class names and file paths
gjtorikian Apr 3, 2026
541f571
cleanup + additional testing
gjtorikian Apr 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
78 changes: 45 additions & 33 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Code Generation

Most of the SDK is **auto-generated** by `oagen` (an internal OpenAPI code generator). Generated files begin with `# This file is auto-generated by oagen. Do not edit.` Hand-maintained code is fenced with `@oagen-ignore-start` / `@oagen-ignore-end` markers or marked with `# @oagen-ignore-file` (e.g., `session.py`, `passwordless.py`, `vault.py`, `public_client.py`, `pkce.py`, `actions.py`).

## Development Commands

### Installation and Setup
Expand All @@ -16,7 +20,7 @@ uv sync --locked --dev # Install package in development mode with dev dependenci
uv run ruff format . # Format code
uv run ruff format --check . # Check formatting without making changes
uv run ruff check . # Lint code
uv run mypy # Type checking
uv run pyright # Type checking
```

### Testing
Expand Down Expand Up @@ -69,50 +73,58 @@ bash scripts/build_and_upload_dist.sh # Build and upload to PyPI

The SDK provides both synchronous and asynchronous clients:

- `WorkOSClient` (sync) and `AsyncWorkOSClient` (async) are the main entry points
- Both inherit from `BaseClient` which handles configuration and module initialization
- Each feature area (SSO, Directory Sync, etc.) has dedicated module classes
- HTTP clients (`SyncHTTPClient`/`AsyncHTTPClient`) handle the actual API communication
- `WorkOSClient` (sync) and `AsyncWorkOSClient` (async) are the main entry points (exported from `workos/__init__.py`)
- Both inherit from `_BaseWorkOSClient` (in `workos/_client.py`) which handles configuration, HTTP transport, retry logic, and error mapping
- Each feature area (SSO, Organizations, etc.) is exposed as a `@functools.cached_property` on the client for lazy loading
- HTTP transport uses `httpx` directly; there is no separate HTTP client abstraction layer

### Module Structure

Each WorkOS feature has its own module following this pattern:
Each feature module follows this layout:

```
src/workos/{module_name}/
__init__.py # Re-exports from _resource and models
_resource.py # Sync and Async resource classes (e.g., SSO + AsyncSSO)
models/
__init__.py # Re-exports all model classes
{model}.py # Individual dataclass model files
```

- **Module class** (e.g., `SSO`) - main API interface
- **Types directory** (e.g., `workos/types/sso/`) - Pydantic models for API objects
- **Tests** (e.g., `tests/test_sso.py`) - comprehensive test coverage
Resource classes take a `WorkOSClient` or `AsyncWorkOSClient` client reference and call `self._client.request()` or `self._client.request_page()` for paginated endpoints.

### Type System

- All models inherit from `WorkOSModel` (extends Pydantic `BaseModel`)
- Strict typing with mypy enforcement (`strict = True` in mypy.ini)
- Support for both sync and async operations via `SyncOrAsync` typing
- All models use `@dataclass(slots=True)` — **not** Pydantic
- Each model implements `from_dict(cls, data) -> Self` for deserialization and `to_dict() -> Dict` for serialization
- The `Deserializable` protocol in `workos/_types.py` defines the `from_dict` contract
- `RequestOptions` (a `TypedDict`) allows per-call overrides for headers, timeout, retries, etc.
- Type checking uses **pyright** (configured in `pyrightconfig.json`)

### Testing Framework
### Pagination

- Uses pytest with custom fixtures for mocking HTTP clients
- `@pytest.mark.sync_and_async()` decorator runs tests for both sync/async variants
- Comprehensive fixtures in `conftest.py` for HTTP mocking and pagination testing
- Test utilities in `tests/utils/` for common patterns
- `SyncPage[T]` and `AsyncPage[T]` dataclasses in `workos/_pagination.py` represent paginated results
- Cursor-based: `before`/`after` properties, `has_more()` check
- `auto_paging_iter()` transparently fetches subsequent pages

### HTTP Client Abstraction
### Error Handling

- Base HTTP client (`_BaseHTTPClient`) with sync/async implementations
- Request helper utilities for consistent API interaction patterns
- Built-in pagination support with `WorkOSListResource` type
- Automatic retry and error handling
All exceptions live in `workos/_errors.py` and inherit from `WorkOSError`:

### Key Patterns
- `BadRequestError` (400), `AuthenticationError` (401), `AuthorizationError` (403), `NotFoundError` (404), `ConflictError` (409), `UnprocessableEntityError` (422), `RateLimitExceededError` (429), `ServerError` (5xx)
- `ConfigurationError`, `WorkOSConnectionError`, `WorkOSTimeoutError` for non-HTTP errors
- `STATUS_CODE_TO_ERROR` dict maps status codes to exception classes

### Testing Framework

- **Dual client support**: Every module supports both sync and async operations
- **Type safety**: Extensive use of Pydantic models and strict mypy checking
- **Pagination**: Consistent cursor-based pagination across list endpoints
- **Error handling**: Custom exception classes in `workos/exceptions.py`
- **Configuration**: Environment variable support (`WORKOS_API_KEY`, `WORKOS_CLIENT_ID`)
- Uses **pytest** with **pytest-httpx** for HTTP mocking (provides the `httpx_mock` fixture)
- Separate test classes for sync (`TestSSO`) and async (`TestAsyncSSO`) variants
- Async tests use `@pytest.mark.asyncio`
- JSON fixtures in `tests/fixtures/`, loaded via `load_fixture()` from `tests/generated_helpers.py`
- Client fixtures `workos` and `async_workos` defined in `tests/conftest.py`

When adding new features:
### Configuration

1. Create module class with both sync/async HTTP client support
2. Add Pydantic models in appropriate `types/` subdirectory
3. Implement comprehensive tests using the sync_and_async marker
4. Follow existing patterns for pagination, error handling, and type annotations
- Environment variable support: `WORKOS_API_KEY`, `WORKOS_CLIENT_ID`, `WORKOS_BASE_URL`, `WORKOS_REQUEST_TIMEOUT`
- Retry logic: exponential backoff with jitter, retries on 429/5xx, respects `Retry-After` headers
- Default timeout: 60 seconds
154 changes: 126 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,68 +3,166 @@
![PyPI](https://img.shields.io/pypi/v/workos)
[![Build Status](https://workos.semaphoreci.com/badges/workos-python/branches/main.svg?style=shields&key=9e4cb5bb-86a4-4938-9ec2-fc9f9fc512be)](https://workos.semaphoreci.com/projects/workos-python)

The WorkOS library for Python provides convenient access to the WorkOS API from applications written in Python, [hosted on PyPi](https://pypi.org/project/workos/)
The WorkOS library for Python provides convenient access to the WorkOS API from applications written in Python, [hosted on PyPI](https://pypi.org/project/workos/).

## Documentation

See the [API Reference](https://workos.com/docs/reference/client-libraries) for Python usage examples.

## Installation

To install from PyPi, run the following:

```
```bash
pip install workos
```

To install from source, clone the repo and run the following:
## Quick Start

```
python -m pip install .
```python
from workos import WorkOSClient

client = WorkOSClient(api_key="sk_1234", client_id="client_1234")

# List organizations
page = client.organizations.list_organizations()
for org in page.auto_paging_iter():
print(org.name)

# Create an organization
org = client.organizations.create_organizations(name="Acme Corp")
print(org.id)
```

## Configuration
### Async Client

The package will need to be configured with your [api key and client ID](https://dashboard.workos.com/api-keys).
Every method has an identical async counterpart:

```python
from workos import WorkOSClient
from workos import AsyncWorkOSClient

workos_client = WorkOSClient(
api_key="sk_1234", client_id="client_1234"
)
async_client = AsyncWorkOSClient(api_key="sk_1234", client_id="client_1234")

page = await async_client.organizations.list_organizations()
async for org in page.auto_paging_iter():
print(org.name)
```

The SDK also provides asyncio support for some SDK methods, via the async client:
### Environment Variables

The client reads credentials from the environment when not passed explicitly:

| Variable | Description |
|----------|-------------|
| `WORKOS_API_KEY` | WorkOS API key |
| `WORKOS_CLIENT_ID` | WorkOS client ID |
| `WORKOS_BASE_URL` | Override the API base URL (defaults to `https://api.workos.com/`) |
| `WORKOS_REQUEST_TIMEOUT` | HTTP timeout in seconds (defaults to `60`) |

## Available Resources

The client exposes the full WorkOS API through typed namespace properties:

| Property | Description |
|----------|-------------|
| `client.sso` | Single Sign-On connections and authorization |
| `client.organizations` | Organization management |
| `client.user_management` | Users, identities, auth methods, invitations |
| `client.directory_sync` | Directory connections and directory users/groups |
| `client.admin_portal` | Admin Portal link generation |
| `client.audit_logs` | Audit log events, exports, and schemas |
| `client.authorization` | Fine-Grained Authorization (FGA) resources, roles, permissions, and checks |
| `client.webhooks` | Webhook event verification |
| `client.feature_flags` | Feature flag evaluation |
| `client.api_keys` | Organization API key management |
| `client.connect` | OAuth application management |
| `client.widgets` | Widget session tokens |
| `client.multi_factor_auth` | MFA enrollment and verification (also available as `client.mfa`) |
| `client.pipes` | Data Integrations |
| `client.radar` | Radar risk scoring |
| `client.passwordless` | Passwordless authentication sessions |
| `client.vault` | Encrypted data vault |

## Pagination

Paginated endpoints return `SyncPage[T]` (or `AsyncPage[T]`) with built-in auto-pagination:

```python
from workos import AsyncWorkOSClient
# Iterate through all pages automatically
for user in client.user_management.list_users().auto_paging_iter():
print(user.email)

# Or work with a single page
page = client.user_management.list_users(limit=10)
print(page.data) # List of items on this page
print(page.has_more()) # Whether more pages exist
print(page.after) # Cursor for the next page
```

## Error Handling

All API errors map to typed exception classes with rich context:

```python
from workos._errors import NotFoundError, RateLimitExceededError

try:
client.organizations.get_organization("org_nonexistent")
except NotFoundError as e:
print(f"Not found: {e.message}")
print(f"Request ID: {e.request_id}")
except RateLimitExceededError as e:
print(f"Retry after: {e.retry_after} seconds")
```

| Exception | Status Code |
|-----------|-------------|
| `BadRequestError` | 400 |
| `AuthenticationError` | 401 |
| `AuthorizationError` | 403 |
| `NotFoundError` | 404 |
| `ConflictError` | 409 |
| `UnprocessableEntityError` | 422 |
| `RateLimitExceededError` | 429 |
| `ServerError` | 5xx |

async_workos_client = AsyncWorkOSClient(
api_key="sk_1234", client_id="client_1234"
## Per-Request Options

Every method accepts `request_options` for per-call overrides:

```python
result = client.organizations.list_organizations(
request_options={
"timeout": 10,
"max_retries": 5,
"extra_headers": {"X-Custom": "value"},
"idempotency_key": "my-key",
"base_url": "https://staging.workos.com/",
}
)
```

## Type Safety

This SDK ships with full type annotations (`py.typed` / PEP 561) and works with mypy, pyright, and IDE autocompletion out of the box. All models are `@dataclass(slots=True)` classes with `from_dict()` / `to_dict()` for serialization.

## SDK Versioning

For our SDKs WorkOS follows a Semantic Versioning ([SemVer](https://semver.org/)) process where all releases will have a version X.Y.Z (like 1.0.0) pattern wherein Z would be a bug fix (e.g., 1.0.1), Y would be a minor release (1.1.0) and X would be a major release (2.0.0). We permit any breaking changes to only be released in major versions and strongly recommend reading changelogs before making any major version upgrades.
WorkOS follows [Semantic Versioning](https://semver.org/). Breaking changes are only released in major versions. We strongly recommend reading changelogs before making major version upgrades.

## Beta Releases

WorkOS has features in Beta that can be accessed via Beta releases. We would love for you to try these
and share feedback with us before these features reach general availability (GA). To install a Beta version,
please follow the [installation steps](#installation) above using the Beta release version.

> Note: there can be breaking changes between Beta versions. Therefore, we recommend pinning the package version to a
> specific version. This way you can install the same version each time without breaking changes unless you are
> intentionally looking for the latest Beta version.
WorkOS has features in Beta that can be accessed via Beta releases. We would love for you to try these and share feedback with us before these features reach general availability (GA). To install a Beta version, please follow the [installation steps](#installation) above using the Beta release version.

We highly recommend keeping an eye on when the Beta feature you are interested in goes from Beta to stable so that you
can move to using the stable version.
> **Note:** there can be breaking changes between Beta versions. We recommend pinning the package version to a specific version.

## More Information

- [Single Sign-On Guide](https://workos.com/docs/sso/guide)
- [User Management Guide](https://workos.com/docs/user-management)
- [AuthKit Guide](https://workos.com/docs/authkit)
- [Directory Sync Guide](https://workos.com/docs/directory-sync/guide)
- [Admin Portal Guide](https://workos.com/docs/admin-portal/guide)
- [Magic Link Guide](https://workos.com/docs/magic-link/guide)
- [Audit Logs Guide](https://workos.com/docs/audit-logs)
- [Authorization (FGA) Guide](https://workos.com/docs/fga)
- [Feature Flags Guide](https://workos.com/docs/feature-flags)
- [Webhooks Guide](https://workos.com/docs/webhooks)
- [Radar Guide](https://workos.com/docs/radar)
Loading
Loading