Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
  •  
  •  
  •  
13 changes: 2 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
Expand All @@ -28,15 +28,6 @@ jobs:
- name: Install dependencies
run: uv sync --locked --dev

- name: Check formatting
run: uv run ruff format --check .

- name: Lint
run: uv run ruff check --extend-exclude .devbox

- name: Type Check
run: uv run mypy

- name: Test
run: uv run pytest

Expand All @@ -47,7 +38,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
Expand Down
34 changes: 34 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Python Lint

on:
push:
branches:
- "main"
pull_request: {}

defaults:
run:
shell: bash

jobs:
lint:
name: Python lint and typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
with:
python-version: "3.14"
enable-cache: true

- name: Install dependencies
run: uv sync --locked --dev
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Lint workflow installs all dev dependencies unnecessarily

uv sync --locked --dev installs every dependency group (test, nox, type_check, and lint), but this job only needs lint and type_check. Consider restricting the install to keep the job lean:

Suggested change
run: uv sync --locked --dev
run: uv sync --locked --only-group lint --only-group type_check


- name: Check formatting
run: uv run ruff format --check .

- name: Lint
run: uv run ruff check --extend-exclude .devbox

- name: Type check
run: uv run pyright
79 changes: 45 additions & 34 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,12 +20,12 @@ 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

The SDK uses [nox](https://nox.thea.codes/) with [nox-uv](https://github.com/dantebben/nox-uv) for multi-version Python testing. This ensures compatibility across all supported Python versions (3.8-3.14).
The SDK uses [nox](https://nox.thea.codes/) with [nox-uv](https://github.com/dantebben/nox-uv) for multi-version Python testing. This ensures compatibility across all supported Python versions (3.10-3.14).

**Quick testing with the test script:**

Expand Down Expand Up @@ -69,51 +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:

- **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
```
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
```

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

- **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`)
### Testing Framework

When adding new features:
- 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`

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
### Configuration

- 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