Skip to content

Conversation

@birdcar
Copy link
Contributor

@birdcar birdcar commented Jan 14, 2026

Description

This will update the Audit Logs module to complete the endpoints available in the API reference.

Important review callout

This change creates an AuditLogAction type, which uses schema as a field name directly. Why is this important to call out? Because in previous versions of Pydantic, schema was a reserved word used internally by the Pydantic BaseModel. In v2 or Pydantic, they replaced the use of schema with model_json_schema() and added an internal user warning about shadowing that field being deprecated.

Given these changes, and the fact that that warning will be removed, I opted to use the schema field directly and catch/silence the warning at runtime to avoid any noise for our library consumers.

The benefit here is that now, a developer can use action.schema.version when working with an AuditLogAction object (which matches our API response exactly) as opposed to having to use an alias like action.action_schema.version.

Alternates I considered

As just mentioned, I originally avoided the issue by using a Field with a defined alias in cf5ce9b. That would enable the library to ingest the API response, which uses schema as the key, without issue, but would require developers using the library to use action_schema as the object property.

To my mind, this tradeoff is worth it and when they remove the warning in that internal class field at some point we can remove the warning and continue normally. Having said that, I made the change in a separate commit so it would be easily dropped if we decide action_schema is more appropriate.

Documentation

Does this require changes to the WorkOS Docs? E.g. the API Reference or code snippets need updates.

[x] Yes

If yes, link a related docs PR and add a docs maintainer as a reviewer. Their approval is required.

@birdcar birdcar self-assigned this Jan 14, 2026
@birdcar birdcar requested a review from a team as a code owner January 14, 2026 16:12
@birdcar birdcar requested a review from gjtorikian January 14, 2026 16:12
@birdcar birdcar added the enhancement New feature or request label Jan 14, 2026
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 14, 2026

Greptile Summary

This PR completes the Audit Logs API implementation by adding support for schemas, actions, retention configuration, and audit log configuration endpoints. The implementation follows the SDK's established patterns with both sync and async variants for all new methods.

Key Changes:

  • Added AsyncAuditLogs class implementation and enabled it in AsyncClient
  • Implemented 7 new methods across sync/async variants: create_schema, list_schemas, list_actions, get_retention, set_retention, get_configuration
  • Created 5 new Pydantic models: AuditLogAction, AuditLogSchema, AuditLogConfiguration, AuditLogStream, AuditLogRetention
  • Leveraged Pydantic v2's schema field handling by suppressing the deprecation warning for BaseModel.schema(), allowing the AuditLogAction.schema field to match the API response exactly
  • Updated test suite to use @pytest.mark.sync_and_async decorator with comprehensive test coverage for all new endpoints
  • Replaced asyncio.iscoroutinefunction with inspect.iscoroutinefunction in test utilities for better compatibility

Implementation Quality:

  • Consistent with existing codebase patterns (type hints, error handling, pagination)
  • Comprehensive test coverage including success cases, error cases, and parameter validation
  • Proper use of WorkOSListResource for paginated endpoints
  • Type-safe with proper Literal types for constrained values (retention periods, states)
  • Documentation strings on all new models and methods

Confidence Score: 5/5

  • This PR is safe to merge with no concerns
  • The implementation is thorough, well-tested, and follows all established patterns in the codebase. The Pydantic v2 schema field handling is appropriate and well-documented. All new endpoints have comprehensive test coverage for both sync and async variants, and the code follows the SDK's architecture consistently.
  • No files require special attention

Important Files Changed

Filename Overview
src/workos/async_client.py Enabled async audit logs support by replacing NotImplementedError with AsyncAuditLogs initialization
src/workos/audit_logs.py Added complete audit logs API implementation with sync/async variants for schemas, actions, retention, and configuration endpoints
src/workos/types/audit_logs/audit_log_action.py Introduced AuditLogAction type with 'schema' field, handled Pydantic v2 warning suppression
tests/test_audit_logs.py Comprehensive test coverage for all new audit logs endpoints with sync/async variants

Sequence Diagram

sequenceDiagram
    participant Client as Client (Sync/Async)
    participant AuditLogs as AuditLogs Module
    participant HTTPClient as HTTP Client
    participant API as WorkOS API

    Note over Client,API: Create Event Flow
    Client->>AuditLogs: create_event(organization_id, event, idempotency_key)
    AuditLogs->>HTTPClient: POST /audit_logs/events
    HTTPClient->>API: HTTP Request with event data
    API-->>HTTPClient: 200 OK
    HTTPClient-->>AuditLogs: Response
    AuditLogs-->>Client: None

    Note over Client,API: Schema Management Flow
    Client->>AuditLogs: create_schema(action, targets, actor, metadata)
    AuditLogs->>HTTPClient: POST /audit_logs/actions/{action}/schemas
    HTTPClient->>API: HTTP Request with schema definition
    API-->>HTTPClient: 201 Created
    HTTPClient-->>AuditLogs: AuditLogSchema JSON
    AuditLogs->>AuditLogs: AuditLogSchema.model_validate()
    AuditLogs-->>Client: AuditLogSchema object

    Note over Client,API: List Actions Flow
    Client->>AuditLogs: list_actions(limit, before, after, order)
    AuditLogs->>HTTPClient: GET /audit_logs/actions with params
    HTTPClient->>API: HTTP Request
    API-->>HTTPClient: List response with actions
    HTTPClient-->>AuditLogs: Response with data array
    AuditLogs->>AuditLogs: WorkOSListResource creation
    Note over AuditLogs: Includes AuditLogAction objects<br/>with nested AuditLogSchema
    AuditLogs-->>Client: Paginated list resource

    Note over Client,API: Retention Configuration Flow
    Client->>AuditLogs: set_retention(organization_id, retention_period_in_days)
    AuditLogs->>HTTPClient: PUT /organizations/{org_id}/audit_logs_retention
    HTTPClient->>API: HTTP Request with retention period
    API-->>HTTPClient: 200 OK with retention config
    HTTPClient-->>AuditLogs: AuditLogRetention JSON
    AuditLogs->>AuditLogs: AuditLogRetention.model_validate()
    AuditLogs-->>Client: AuditLogRetention object

    Note over Client,API: Configuration Retrieval Flow
    Client->>AuditLogs: get_configuration(organization_id)
    AuditLogs->>HTTPClient: GET /organizations/{org_id}/audit_log_configuration
    HTTPClient->>API: HTTP Request
    API-->>HTTPClient: 200 OK with full config
    HTTPClient-->>AuditLogs: AuditLogConfiguration JSON
    Note over AuditLogs: Includes optional log_stream<br/>with state and type info
    AuditLogs->>AuditLogs: AuditLogConfiguration.model_validate()
    AuditLogs-->>Client: AuditLogConfiguration object
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Additional Comments (1)

  1. tests/test_audit_logs.py, line 7 (link)

    style: Check that AsyncAuditLogs is tested. Other test modules use @pytest.mark.sync_and_async(AuditLogs, AsyncAuditLogs) decorator to test both sync and async implementations. Without this marker, only sync tests run.

11 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@birdcar
Copy link
Contributor Author

birdcar commented Jan 14, 2026

Gonna update with that feedback from greptile, una momento

@birdcar
Copy link
Contributor Author

birdcar commented Jan 14, 2026

@greptileai

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

11 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@birdcar
Copy link
Contributor Author

birdcar commented Jan 14, 2026

Alright, this is ready to go whenever ✌️ Gonna move on to other improvements 🔜

Starting in Python 3.14, use of `asyncio.iscoroutinefunction` emits a
deprecation warning (c.f. python/cpython#122858). The recommendation is
to use inspect.iscoroutinefunction instead.
@birdcar birdcar force-pushed the birdcar/complete-audit-log-endpoints branch from 95e10a3 to 733353f Compare January 14, 2026 23:26
Copy link
Contributor

@gjtorikian gjtorikian left a comment

Choose a reason for hiding this comment

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

asked some clarifying questions, neither approving nor rejecting!

action: str,
targets: Sequence[Mapping[str, Any]],
actor: Optional[Mapping[str, Any]] = None,
metadata: Optional[Mapping[str, Any]] = None,
Copy link
Contributor

Choose a reason for hiding this comment

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

For targets, actor, and metadata, couldn't these use the schemas AuditLogSchemaTarget, AuditLogSchemaActor, and AuditLogSchemaMetadata, rather than the open-ended mapping of str: Any? (Note that these params are reused in other places in this PR, too, I just picked one example.)

Copy link
Contributor Author

@birdcar birdcar Jan 22, 2026

Choose a reason for hiding this comment

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

So, my initial thinking when implementing this that these arguments were exactly as flexible as the types indicated (e.g. action is literally an arbitrary string, metadata is an arbitrary mapping). Leaving them this way enabled folks to write bog-standard/flexible Python code like:

workos.audit_logs.create_schema(
    action="document.updated",
    targets=[
        {
            "type": "document",
            "metadata": {"title": "string"},
        }
    ],
    actor={"metadata": {"role": "string"}},
    metadata={"invoice_id": "string"},
)

This did lack type safety, but it felt like a type alias was un-necessary redirection (and created the false impression of type safety), and using the direct model (e.g. AuditLogSchemaTarget) would mean folks need to both import those models and then construct the call like so:

from workos.types.audit_logs import (
    AuditLogSchemaTarget,
    AuditLogSchemaActor,
    AuditLogSchemaMetadata,
    AuditLogSchemaMetadataProperty,
)

workos.audit_logs.create_schema(
    action="document.updated",
    targets=[
        AuditLogSchemaTarget(
            type="document",
            metadata=AuditLogSchemaMetadata(
                type="object",
                properties={
                    "title": AuditLogSchemaMetadataProperty(type="string")
                },
            ),
        )
    ],
    actor=AuditLogSchemaActor(
        metadata=AuditLogSchemaMetadata(
            type="object",
            properties={
                "role": AuditLogSchemaMetadataProperty(type="string")
            },
        )
    ),
)

I think you'll agree, this feels bad, especially compared with the Node/Typescript implementation.

Having said that, your comment (and the subsequent digging it triggered) reminded me that I can actually provide just a little extra help via a TypedDict, which led me to create types like:

class AuditLogSchemaTargetInput(TypedDict):
    type: str
    metadata: NotRequired[MetadataSchemaInput]

class AuditLogSchemaActorInput(TypedDict):
    metadata: MetadataSchemaInput

Which means that now folks get value suggestions in their IDE and also validation that they're constructing the mappings correctly, while still passing normal mappings in:

workos.audit_logs.create_schema(
    action="document.updated",
    targets=[
        {
            "type": "document",
            "metadata": {"title": "string"},
        }
    ],
    actor={"metadata": {"role": "string"}},
    metadata={"invoice_id": "string"},
)

I think this ultimately provides the balance of ergonomics and safety we want.

Copy link
Contributor

Choose a reason for hiding this comment

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

sick 🤙

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(Note that these params are reused in other places in this PR, too, I just picked one example.)

Almost forgot to comment on this as well. The other functions I examined didn't, in practice, benefit from the kind of type safety the create_schema endpoint specifically did. But I'm happy to dig deeper there if you have feelings (or just think we should have type aliases in general).

warnings.filterwarnings(
"ignore",
message='Field name "schema" in "AuditLogAction" shadows an attribute',
category=UserWarning,
Copy link
Contributor

Choose a reason for hiding this comment

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

Given these changes, and the fact that that warning will be removed,

As of v2.12, this warning was removed! Do you think we can update our pydantic dependency (from 2.10.4, bleh) and remove this warning? Or does updating our dependency affect downstream users (necessitating a major version bump)?

Copy link
Contributor Author

@birdcar birdcar Jan 22, 2026

Choose a reason for hiding this comment

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

does updating our dependency affect downstream users (necessitating a major version bump)?

Imo, bumping up the minimum Pydantic version wouldn't, on the face of it, require a major SDK version bump, but given the popularity of Pydantic, it's possible that it could break downstream users who are pinned to a lower version.

My 2¢ is that we should leave it for now and then revisit a version bump later when we're dropping support for EOL Python versions.

That said, I'm happy to make that change in a separate PR if you feel strongly about it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, let's roll it into the next major bump.

Introduces TypedDict definitions for the simplified schema input format:
- AuditLogSchemaTargetInput for target definitions
- AuditLogSchemaActorInput for actor definitions
- MetadataSchemaInput for metadata field type mappings

Also adds serialize_schema_options() to transform the simplified format
(e.g., {"status": "string"}) to the full JSON Schema format expected
by the API.
Replaces generic Mapping[str, Any] parameters with the new typed inputs.
Uses shared serialize_schema_options() function instead of inline JSON
building, reducing duplication between sync and async implementations.
The API can return null for actor in AuditLogSchema (when no custom
metadata defined) and for retention_period_in_days in AuditLogRetention
(when not configured). Updated models to accept None.
Updates create_schema tests to use the new simplified input format and
verifies the serialization to full JSON Schema format. Adds edge case
tests for nullable fields (actor in schema, retention_period_in_days).
@gjtorikian gjtorikian merged commit 4483636 into main Jan 22, 2026
11 checks passed
@gjtorikian gjtorikian deleted the birdcar/complete-audit-log-endpoints branch January 22, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Development

Successfully merging this pull request may close these issues.

3 participants