Skip to content

Add sync and async client parity - #213

Merged
berndverst merged 16 commits into
mainfrom
andystaples-client-parity-features
Jul 27, 2026
Merged

Add sync and async client parity#213
berndverst merged 16 commits into
mainfrom
andystaples-client-parity-features

Conversation

@andystaples

@andystaples andystaples commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add asynchronous scheduled-task and history-export client APIs to durabletask
  • add native SyncDurableFunctionsClient bindings alongside async Functions clients
  • make Functions history-export activities use the native sync binding, removing the async-to-sync bridge

Closes #180
Closes #181

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0
Copilot AI review requested due to automatic review settings July 27, 2026 18:09
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0

Copilot AI left a comment

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.

Pull request overview

This PR adds sync/async parity across the durabletask sub-clients and the azure-functions-durable client binding surface, removing the need for async→sync “bridge” helpers in Functions scenarios (notably for scheduled tasks and history export).

Changes:

  • Added async-first APIs in durabletask for scheduled tasks (AsyncScheduledTaskClient / AsyncScheduleClient) and history export (AsyncExportHistoryClient / AsyncExportHistoryJobClient).
  • Added a native synchronous Functions durable client (SyncDurableFunctionsClient) plus a sync binding/decorator path, and updated Functions history-export activities to use it.
  • Reworked the durable-client binding conversion path so the binding converter returns the raw host config string and the decorator constructs/disposes the requested rich client.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/azure-functions-durable/test_history_export_compat.py Updates tests for per-invocation (non-cached) history export context resolution.
tests/azure-functions-durable/test_decorator_compat.py Adds tests validating async vs sync durable-client injection behavior.
tests/azure-functions-durable/test_converters.py Updates tests to reflect durable-client decode now returning raw host configuration.
durabletask/scheduled/client.py Introduces async scheduled-task client façade types.
durabletask/scheduled/__init__.py Exports new async scheduled-task client types.
durabletask/extensions/history_export/client.py Introduces async history-export client façade types.
durabletask/extensions/history_export/__init__.py Exports new async history-export façade types.
CHANGELOG.md Documents new async scheduled-task and history-export APIs in core SDK.
azure-functions-durable/CHANGELOG.md Documents new sync durable client binding/decorator in Functions package.
azure-functions-durable/azure/durable_functions/internal/history_export_compat.py Switches history-export context to use the invocation’s native sync client.
azure-functions-durable/azure/durable_functions/internal/converters.py Adjusts durable-client decode behavior to return raw host config string.
azure-functions-durable/azure/durable_functions/decorators/durable_app.py Adds sync durable-client injection support and wraps functions to build/close the rich client.
azure-functions-durable/azure/durable_functions/client.py Adds SyncDurableFunctionsClient built on TaskHubGrpcClient.
azure-functions-durable/azure/durable_functions/__init__.py Exports SyncDurableFunctionsClient from the package root.
Comments suppressed due to low confidence (1)

azure-functions-durable/azure/durable_functions/internal/history_export_compat.py:223

  • _context_for now expects a native synchronous TaskHubGrpcClient and returns a fresh HistoryExportContext per invocation, but this module still contains (and documents) the old per-process cached context, atexit close path, and async->sync bridge helpers that appear unused after this change. Keeping those stale globals/docs makes the new per-invocation design harder to reason about.
    parsed = dt_from_iso(raw) if raw else None
    return parsed or datetime.now(timezone.utc)


class FunctionsExportJobEntity(ExportJobEntity):

Comment thread azure-functions-durable/azure/durable_functions/decorators/durable_app.py Outdated
Comment thread durabletask/scheduled/__init__.py Outdated
Comment thread durabletask/scheduled/client.py Outdated
Comment thread durabletask/extensions/history_export/client.py
Copilot AI review requested due to automatic review settings July 27, 2026 18:18

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (5)

azure-functions-durable/azure/durable_functions/internal/converters.py:220

  • register_durable_converters() docstring still claims the durable-client binding is decoded to a DurableFunctionsClient, but DurableClientConverter.decode() now returns the raw host configuration string. Updating the docstring will prevent confusion about where the client object is created (converter vs decorator).
    def decode(cls, data: meta.Datum, *,
               trigger_metadata: _TriggerMetadata) -> Any:
        # The decorator turns this host configuration into the requested sync or
        # async rich client. Keeping conversion here as a string lets one binding
        # type support both client shapes without a hidden sync bridge.
        return data.value

durabletask/scheduled/client.py:161

  • New async scheduled-task surface area (AsyncScheduleClient / AsyncScheduledTaskClient) is introduced here, but there are no unit tests exercising the async behaviors (create/update/pause/resume/delete/describe/list) or validating parity with the sync clients' semantics (timeouts, failures, filter semantics). The existing scheduled-task test suite suggests this should be covered.
class AsyncScheduleClient:
    """Asynchronous client for managing a single schedule instance."""

    def __init__(self, client: AsyncTaskHubGrpcClient, schedule_id: str,
                 *, operation_timeout: float = 60):

durabletask/extensions/history_export/client.py:488

  • Async history-export APIs (AsyncExportHistoryClient / AsyncExportHistoryJobClient) are added, but the current history-export client tests appear to cover only the synchronous clients. Adding async tests for create/get/list/wait/delete (and per-job façade behavior) would help ensure parity and prevent regressions.
class AsyncExportHistoryClient:
    """Asynchronous façade for creating and inspecting export jobs."""

    def __init__(
        self,

azure-functions-durable/azure/durable_functions/decorators/durable_app.py:410

  • The binding-registration comment in durable_client_input() still says DurableClientConverter constructs a rich DurableFunctionsClient during decode, but the new wrapper below constructs the rich client from a raw config string per invocation. Updating that comment will avoid confusion about where the client object is created and how it’s closed.
            # user function is registered directly and ``.client_function``
            # points back at it. Internal callers pass an already-built
            # ``FunctionBuilder`` (e.g. history export), which is left untouched.
            from ..client import DurableFunctionsClient, SyncDurableFunctionsClient

azure-functions-durable/azure/durable_functions/decorators/durable_app.py:409

  • This comment block still claims there is no client middleware wrapper and that the converter builds the rich client during decode. With the new client_bound wrapper below, that’s no longer true and may mislead future maintenance (e.g., around close semantics).
            # user function is registered directly and ``.client_function``
            # points back at it. Internal callers pass an already-built
            # ``FunctionBuilder`` (e.g. history export), which is left untouched.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0
Copilot AI review requested due to automatic review settings July 27, 2026 18:31
andystaples and others added 2 commits July 27, 2026 12:32
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

durabletask/scheduled/client.py:237

  • The create_schedule signature uses an oddly indented continuation line; please format it consistently with the rest of the codebase’s multi-line signatures to avoid flake8 indentation warnings and improve readability.
    async def create_schedule(
            self, options: ScheduleCreationOptions) -> AsyncScheduleClient:
        """Create a new schedule and return a client for managing it."""

durabletask/scheduled/client.py:251

  • The list_schedules signature continuation indentation is inconsistent with other multi-line function signatures in this repo (see ExportHistoryClient.list_jobs for the common pattern). Reformatting helps readability and avoids potential flake8 E12x indentation issues.
    async def list_schedules(
            self, schedule_query: ScheduleQuery | None = None) -> list[ScheduleDescription]:
        """List schedules matching the given filter criteria."""

Comment thread azure-functions-durable/azure/durable_functions/decorators/durable_app.py Outdated
Comment thread durabletask/scheduled/client.py Outdated
Comment thread durabletask/extensions/history_export/client.py
Copilot AI review requested due to automatic review settings July 27, 2026 18:37

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comment thread azure-functions-durable/azure/durable_functions/client.py
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0
Copilot AI review requested due to automatic review settings July 27, 2026 18:48

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comment thread durabletask/extensions/history_export/client.py
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0
Copilot AI review requested due to automatic review settings July 27, 2026 18:53

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

durabletask/scheduled/client.py:200

  • AsyncScheduleClient.update() is newly introduced but there is no async E2E test exercising the update path. The sync suite covers update behavior (interval/input changes), but the async client could regress unnoticed without at least one await schedule.update(...) test asserting the updated description fields.
    async def update(self, options: ScheduleUpdateOptions) -> None:
        """Update this schedule's configuration."""
        await self._run_operation(transitions.UPDATE_SCHEDULE, options)

Comment thread durabletask/extensions/history_export/client.py
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0
Copilot AI review requested due to automatic review settings July 27, 2026 19:12

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0
Copilot AI review requested due to automatic review settings July 27, 2026 19:20

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 27, 2026 19:51

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Comment thread CHANGELOG.md Outdated
Comment thread azure-functions-durable/CHANGELOG.md Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0
Copilot AI review requested due to automatic review settings July 27, 2026 20:10

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comment thread azure-functions-durable/azure/durable_functions/decorators/durable_app.py Outdated

@berndverst berndverst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The async tests cover happy paths but not failed/timed-out schedule operations, export wait timeout/not-found behavior, or delete_job handling of gRPC NOT_FOUND. Focused mocked tests would protect these new error contracts.

Comment thread azure-functions-durable/azure/durable_functions/decorators/durable_app.py Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0
Copilot AI review requested due to automatic review settings July 27, 2026 20:28
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Comment thread durabletask/scheduled/client.py
Comment thread azure-functions-durable/azure/durable_functions/decorators/durable_app.py Outdated
Copilot AI review requested due to automatic review settings July 27, 2026 20:35
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 76f2163a-0773-4889-b627-1bd4e5c7bea0

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comment thread azure-functions-durable/CHANGELOG.md Outdated
Copilot AI review requested due to automatic review settings July 27, 2026 20:41

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

azure-functions-durable/azure/durable_functions/decorators/durable_app.py:440

  • The durable-client wrapper always replaces the binding value (a host configuration string) with a rich client instance, but set_client_metadata() copies the original function's annotation for client_name (or even forces it to str). This can leave the registered wrapper annotated as str or DurableFunctionsClient even when a sync function actually receives SyncDurableFunctionsClient, which is misleading for introspection and type checking (and contradicts the docstring above). Update the wrapper's annotation to the concrete injected client type based on whether the function is async or sync.
            def set_client_metadata(client_bound: Callable[..., Any]) -> None:
                annotations = dict(function.__annotations__)
                supported_client_types = (
                    str,
                    bytes,
                    DurableFunctionsClient,
                    SyncDurableFunctionsClient,
                )
                if annotations.get(client_name) not in supported_client_types:
                    annotations[client_name] = str
                client_bound.__annotations__ = annotations
                setattr(client_bound, "client_function", function)

@berndverst
berndverst merged commit 8356fe2 into main Jul 27, 2026
25 checks passed
@berndverst
berndverst deleted the andystaples-client-parity-features branch July 27, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants