Pyramid-Temporal integration library.

This package provides automatic transaction management for Temporal activities using pyramid_tm, similar to how it works for web requests.

Main components: - Worker: Pyramid-aware Temporal Worker with automatic context binding - PyramidEnvironment: Wrapper for Pyramid bootstrap environment - activity: Decorator module for defining pyramid-temporal activities - ActivityContext: Context object providing real Pyramid requests to activities

Activities receive real Pyramid Request objects (via Pyramid's request factory), so all request methods configured via add_request_method work automatically (dbsession, tm, etc.).

Each activity execution owns its request and its transaction, so activities can run concurrently. Activities may be written as async def or as plain def; sync ones run in Temporal's activity executor, which keeps a blocking body off the worker's event loop.

Example

from pyramid.paster import bootstrap from pyramid_temporal import Worker, activity, ActivityContext, PyramidEnvironment

@activity.defn async def enrich_user(context: ActivityContext, user_id: int) -> bool: # Real Pyramid request with all configured methods session = context.request.dbsession user = session.query(User).get(user_id) user.enriched = True return True

In worker setup:

env = PyramidEnvironment.from_bootstrap(bootstrap('development.ini')) worker = Worker( client, env, task_queue="my-queue", activities=[enrich_user], workflows=[MyWorkflow], ) await worker.run()

ActivityContext

Context object providing Pyramid integration for one activity execution.

A context belongs to a single execution. The bound activity creates one per invocation, so concurrent executions never share a request, and therefore never share a dbsession or a transaction.

The request has all the same properties and methods as a web request, including any configured via add_request_method (like dbsession, tm, etc.).

Example

@activity.defn async def my_activity(context: ActivityContext, user_id: int) -> bool: # Real Pyramid request with all configured methods session = context.request.dbsession user = session.query(User).get(user_id) return user is not None

Source code in pyramid_temporal/context.py
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
class ActivityContext:
    """Context object providing Pyramid integration for one activity execution.

    A context belongs to a single execution. The bound activity creates one per
    invocation, so concurrent executions never share a request, and therefore
    never share a dbsession or a transaction.

    The request has all the same properties and methods as a web request,
    including any configured via add_request_method (like dbsession, tm, etc.).

    Example:
        @activity.defn
        async def my_activity(context: ActivityContext, user_id: int) -> bool:
            # Real Pyramid request with all configured methods
            session = context.request.dbsession
            user = session.query(User).get(user_id)
            return user is not None
    """

    def __init__(self, env: PyramidEnvironment) -> None:
        """Initialize the activity context.

        Args:
            env: PyramidEnvironment instance
        """
        self._env = env
        self._request: Optional[Request] = None
        self._threadlocal_context: Optional[Union[RequestContext, RegistryContext]] = None

    @property
    def env(self) -> PyramidEnvironment:
        """Get the Pyramid environment."""
        return self._env

    @property
    def registry(self) -> "Registry":
        """Get the Pyramid registry."""
        return self._env.registry

    @property
    def settings(self) -> dict:
        """Get application settings (shortcut to registry.settings)."""
        return self._env.registry.settings

    @property
    def request(self) -> Request:
        """Get this execution's request.

        This is a real Pyramid Request object with all configured
        request methods (dbsession, tm, etc.) available.

        Raises:
            RuntimeError: If accessed outside of activity execution
        """
        if self._request is None:
            raise RuntimeError(
                "ActivityContext.request accessed outside of activity execution. "
                "The request is only available during activity execution."
            )
        return self._request

    def create_request(self, *, threadlocal_request: bool = True) -> Request:
        """Create the Pyramid Request for this activity execution.

        Uses Pyramid's request factory to create a real request, applies request
        extensions (add_request_method), and opens a threadlocal scope.

        Args:
            threadlocal_request: Publish the request on Pyramid's threadlocal
                stack, so ``get_current_request`` returns it. Only correct when
                the execution owns its thread, which is the case for a sync
                activity running in Temporal's activity executor. Concurrent
                async executions share the event loop thread, so they publish
                the registry alone instead.

        Returns:
            A real Pyramid Request instance

        Raises:
            RuntimeError: If this context already has a request
        """
        if self._request is not None:
            raise RuntimeError(
                "ActivityContext already has a request. A context belongs to a "
                "single activity execution and cannot be reused."
            )

        registry = self._env.registry
        request_factory = registry.queryUtility(IRequestFactory, default=Request)
        request = request_factory.blank("/")
        request.registry = registry

        if self._env.request is not None:
            request.environ.update(self._env.request.environ)

        self._threadlocal_context = RequestContext(request) if threadlocal_request else RegistryContext(registry)
        self._threadlocal_context.begin()
        apply_request_extensions(request)

        self._request = request

        logger.debug(
            "Created Pyramid Request for activity (request id: %s)",
            id(self._request),
        )
        return self._request

    def close_request(self) -> None:
        """Close this execution's request and clean up resources.

        Processes finished callbacks and tears down the threadlocal scope.
        """
        request = self._request
        threadlocal_context = self._threadlocal_context

        if request is None or threadlocal_context is None:
            return

        try:
            if request.finished_callbacks:
                request._process_finished_callbacks()
            threadlocal_context.end()
            logger.debug("Closed Pyramid Request context")
        except Exception as e:
            logger.warning("Error closing Pyramid Request context: %s", e)
        finally:
            self._threadlocal_context = None
            self._request = None

env property

Get the Pyramid environment.

registry property

Get the Pyramid registry.

request property

Get this execution's request.

This is a real Pyramid Request object with all configured request methods (dbsession, tm, etc.) available.

Raises:
  • RuntimeError

    If accessed outside of activity execution

settings property

Get application settings (shortcut to registry.settings).

__init__(env)

Initialize the activity context.

Parameters:
Source code in pyramid_temporal/context.py
65
66
67
68
69
70
71
72
73
def __init__(self, env: PyramidEnvironment) -> None:
    """Initialize the activity context.

    Args:
        env: PyramidEnvironment instance
    """
    self._env = env
    self._request: Optional[Request] = None
    self._threadlocal_context: Optional[Union[RequestContext, RegistryContext]] = None

close_request()

Close this execution's request and clean up resources.

Processes finished callbacks and tears down the threadlocal scope.

Source code in pyramid_temporal/context.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
def close_request(self) -> None:
    """Close this execution's request and clean up resources.

    Processes finished callbacks and tears down the threadlocal scope.
    """
    request = self._request
    threadlocal_context = self._threadlocal_context

    if request is None or threadlocal_context is None:
        return

    try:
        if request.finished_callbacks:
            request._process_finished_callbacks()
        threadlocal_context.end()
        logger.debug("Closed Pyramid Request context")
    except Exception as e:
        logger.warning("Error closing Pyramid Request context: %s", e)
    finally:
        self._threadlocal_context = None
        self._request = None

create_request(*, threadlocal_request=True)

Create the Pyramid Request for this activity execution.

Uses Pyramid's request factory to create a real request, applies request extensions (add_request_method), and opens a threadlocal scope.

Parameters:
  • threadlocal_request (bool, default: True ) –

    Publish the request on Pyramid's threadlocal stack, so get_current_request returns it. Only correct when the execution owns its thread, which is the case for a sync activity running in Temporal's activity executor. Concurrent async executions share the event loop thread, so they publish the registry alone instead.

Returns:
  • Request

    A real Pyramid Request instance

Raises:
  • RuntimeError

    If this context already has a request

Source code in pyramid_temporal/context.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
def create_request(self, *, threadlocal_request: bool = True) -> Request:
    """Create the Pyramid Request for this activity execution.

    Uses Pyramid's request factory to create a real request, applies request
    extensions (add_request_method), and opens a threadlocal scope.

    Args:
        threadlocal_request: Publish the request on Pyramid's threadlocal
            stack, so ``get_current_request`` returns it. Only correct when
            the execution owns its thread, which is the case for a sync
            activity running in Temporal's activity executor. Concurrent
            async executions share the event loop thread, so they publish
            the registry alone instead.

    Returns:
        A real Pyramid Request instance

    Raises:
        RuntimeError: If this context already has a request
    """
    if self._request is not None:
        raise RuntimeError(
            "ActivityContext already has a request. A context belongs to a "
            "single activity execution and cannot be reused."
        )

    registry = self._env.registry
    request_factory = registry.queryUtility(IRequestFactory, default=Request)
    request = request_factory.blank("/")
    request.registry = registry

    if self._env.request is not None:
        request.environ.update(self._env.request.environ)

    self._threadlocal_context = RequestContext(request) if threadlocal_request else RegistryContext(registry)
    self._threadlocal_context.begin()
    apply_request_extensions(request)

    self._request = request

    logger.debug(
        "Created Pyramid Request for activity (request id: %s)",
        id(self._request),
    )
    return self._request

PyramidActivity

Wrapper for pyramid-temporal activities.

This class wraps an activity function and provides the ability to bind it to a Pyramid environment for execution.

The wrapper is what workflow code references, so it carries a Temporal activity definition and is callable: workflow.execute_activity(my_activity, ...) reads the registered name from that definition. What actually runs is the bound wrapper bind gives the Worker, and both carry the same name.

Source code in pyramid_temporal/activity.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
class PyramidActivity:
    """Wrapper for pyramid-temporal activities.

    This class wraps an activity function and provides the ability to bind it to
    a Pyramid environment for execution.

    The wrapper is what workflow code references, so it carries a Temporal
    activity definition and is callable: ``workflow.execute_activity(my_activity,
    ...)`` reads the registered name from that definition. What actually runs is
    the bound wrapper ``bind`` gives the Worker, and both carry the same name.
    """

    def __init__(
        self,
        fn: Callable[..., Any],
        name: Optional[str] = None,
        no_thread_cancel_exception: bool = False,
    ) -> None:
        """Initialize the pyramid activity wrapper.

        Args:
            fn: The original activity function
            name: Optional custom activity name
            no_thread_cancel_exception: Thread cancellation setting, sync only
        """
        self._fn = fn
        self._name = name or fn.__name__
        self._no_thread_cancel_exception = no_thread_cancel_exception

        # Copy function metadata for better debugging
        functools.update_wrapper(self, fn)

        # Mark as pyramid-temporal activity
        setattr(self, PYRAMID_ACTIVITY_MARKER, True)

        # Let workflow code reference this activity instead of its name
        setattr(self, TEMPORAL_ACTIVITY_DEFINITION, self._temporal_definition())

    def _temporal_definition(self) -> temporal_activity._Definition:
        """Build the definition Temporal reads when a workflow references this.

        The types are passed explicitly rather than left to Temporal's own
        inference for two reasons: the context argument belongs to the binding
        and never travels over the wire, so it must not appear in ``arg_types``;
        and inference on a callable instance would look at ``__call__`` and lose
        the declared return type, which is what converts an activity result back
        into the type the body declared.
        """
        arg_types, ret_type = self._type_hints()
        self._check_context_parameter(arg_types)

        return temporal_activity._Definition(
            name=self._name,
            fn=self._fn,
            is_async=self.is_async,
            no_thread_cancel_exception=self._no_thread_cancel_exception,
            arg_types=None if arg_types is None else arg_types[1:],
            ret_type=ret_type,
        )

    def _type_hints(self) -> Tuple[Optional[List[type]], Optional[type]]:
        """Resolve the body's annotations, the way Temporal resolves an activity's.

        Raises:
            TypeError: If an annotation cannot be resolved, which is what a type
                imported only under ``TYPE_CHECKING`` leaves behind
        """
        try:
            return temporal_common._type_hints_from_func(self._fn)
        except NameError as error:
            raise TypeError(
                f"Activity '{self._name}' has an annotation that cannot be resolved: {error}. "
                "Temporal reads these to convert arguments and results, so every type an "
                "activity annotates must be importable at runtime, not only under TYPE_CHECKING."
            ) from error

    def _check_context_parameter(self, arg_types: Optional[List[type]]) -> None:
        """Refuse a body that cannot receive the injected context.

        Every execution calls the body with its context first, and the workflow
        facing definition describes only the arguments that follow it. A body
        without that parameter breaks both quietly: the definition would claim
        one argument fewer than the activity takes, and the mistake would
        surface as an activity failure rather than here, where it was made.

        Raises:
            TypeError: If the first parameter cannot be an ActivityContext
        """
        positional = {
            inspect.Parameter.POSITIONAL_ONLY,
            inspect.Parameter.POSITIONAL_OR_KEYWORD,
            inspect.Parameter.VAR_POSITIONAL,
        }
        parameters = inspect.signature(self._fn).parameters.values()

        if not any(param.kind in positional for param in parameters):
            raise TypeError(
                f"Activity '{self._name}' takes no positional argument, so it cannot "
                "receive an ActivityContext. Declare it as the first parameter."
            )

        # A first parameter annotated with nothing, or with Any, claims nothing to
        # contradict. Any needs saying explicitly because it became a class in 3.11,
        # and the check would otherwise refuse it there and accept it on 3.10.
        declared = arg_types[0] if arg_types else None
        if declared is None or declared is Any:
            return

        if isinstance(declared, type) and not issubclass(declared, ActivityContext):
            raise TypeError(
                f"Activity '{self._name}' declares {declared.__name__} as its first "
                "parameter, which must be an ActivityContext."
            )

    def __call__(self, context: ActivityContext, *args: Any, **kwargs: Any) -> Any:
        """Run the activity body inside an execution that already exists.

        This is how an activity body is exercised as a plain function, given a
        context from ``activity_execution``. Async bodies come back as their
        coroutine, for the caller to await.

        Raises:
            TypeError: If the first argument is not an ActivityContext, which
                means nothing bound the activity to a Pyramid environment.
        """
        if not isinstance(context, ActivityContext):
            raise TypeError(
                f"Activity '{self._name}' takes an ActivityContext as its first argument, "
                f"got {type(context).__name__}. Register it with pyramid_temporal.Worker, "
                "which binds the Pyramid environment, not with temporalio.worker.Worker."
            )

        return self._fn(context, *args, **kwargs)

    @property
    def name(self) -> str:
        """Get the activity name."""
        return self._name

    @property
    def fn(self) -> Callable[..., Any]:
        """Get the original function."""
        return self._fn

    @property
    def is_async(self) -> bool:
        """Whether the activity body is a coroutine function."""
        return inspect.iscoroutinefunction(self._fn)

    def bind(self, env: PyramidEnvironment) -> Callable[..., Any]:
        """Bind this activity to a Pyramid environment for Temporal registration.

        Every call of the returned activity is one execution, and owns its own
        ActivityContext, Pyramid request, and transaction. Nothing is shared
        between executions, so they are safe to run concurrently.

        An async activity binds to a coroutine function, which Temporal runs on
        the worker's event loop. A sync activity binds to a plain function, which
        Temporal runs in its activity executor, so a blocking body leaves the
        event loop free.

        Args:
            env: The PyramidEnvironment each execution builds its request from

        Returns:
            An activity that can be registered with a Temporal Worker
        """
        fn = self._fn
        name = self._name

        if self.is_async:

            @temporal_activity.defn(name=name)
            async def execute_async(*args: Any, **kwargs: Any) -> Any:
                """Execute one async activity execution with context injection."""
                with activity_execution(env, threadlocal_request=False) as context:
                    return await fn(context, *args, **kwargs)

            return execute_async

        @temporal_activity.defn(name=name, no_thread_cancel_exception=self._no_thread_cancel_exception)
        def execute_sync(*args: Any, **kwargs: Any) -> Any:
            """Execute one sync activity execution with context injection."""
            with activity_execution(env, threadlocal_request=True) as context:
                return fn(context, *args, **kwargs)

        return execute_sync

    def __repr__(self) -> str:
        return f"<PyramidActivity '{self._name}'>"

fn property

Get the original function.

is_async property

Whether the activity body is a coroutine function.

name property

Get the activity name.

__call__(context, *args, **kwargs)

Run the activity body inside an execution that already exists.

This is how an activity body is exercised as a plain function, given a context from activity_execution. Async bodies come back as their coroutine, for the caller to await.

Raises:
  • TypeError

    If the first argument is not an ActivityContext, which means nothing bound the activity to a Pyramid environment.

Source code in pyramid_temporal/activity.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
def __call__(self, context: ActivityContext, *args: Any, **kwargs: Any) -> Any:
    """Run the activity body inside an execution that already exists.

    This is how an activity body is exercised as a plain function, given a
    context from ``activity_execution``. Async bodies come back as their
    coroutine, for the caller to await.

    Raises:
        TypeError: If the first argument is not an ActivityContext, which
            means nothing bound the activity to a Pyramid environment.
    """
    if not isinstance(context, ActivityContext):
        raise TypeError(
            f"Activity '{self._name}' takes an ActivityContext as its first argument, "
            f"got {type(context).__name__}. Register it with pyramid_temporal.Worker, "
            "which binds the Pyramid environment, not with temporalio.worker.Worker."
        )

    return self._fn(context, *args, **kwargs)

__init__(fn, name=None, no_thread_cancel_exception=False)

Initialize the pyramid activity wrapper.

Parameters:
  • fn (Callable[..., Any]) –

    The original activity function

  • name (Optional[str], default: None ) –

    Optional custom activity name

  • no_thread_cancel_exception (bool, default: False ) –

    Thread cancellation setting, sync only

Source code in pyramid_temporal/activity.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
def __init__(
    self,
    fn: Callable[..., Any],
    name: Optional[str] = None,
    no_thread_cancel_exception: bool = False,
) -> None:
    """Initialize the pyramid activity wrapper.

    Args:
        fn: The original activity function
        name: Optional custom activity name
        no_thread_cancel_exception: Thread cancellation setting, sync only
    """
    self._fn = fn
    self._name = name or fn.__name__
    self._no_thread_cancel_exception = no_thread_cancel_exception

    # Copy function metadata for better debugging
    functools.update_wrapper(self, fn)

    # Mark as pyramid-temporal activity
    setattr(self, PYRAMID_ACTIVITY_MARKER, True)

    # Let workflow code reference this activity instead of its name
    setattr(self, TEMPORAL_ACTIVITY_DEFINITION, self._temporal_definition())

bind(env)

Bind this activity to a Pyramid environment for Temporal registration.

Every call of the returned activity is one execution, and owns its own ActivityContext, Pyramid request, and transaction. Nothing is shared between executions, so they are safe to run concurrently.

An async activity binds to a coroutine function, which Temporal runs on the worker's event loop. A sync activity binds to a plain function, which Temporal runs in its activity executor, so a blocking body leaves the event loop free.

Parameters:
  • env (PyramidEnvironment) –

    The PyramidEnvironment each execution builds its request from

Returns:
  • Callable[..., Any]

    An activity that can be registered with a Temporal Worker

Source code in pyramid_temporal/activity.py
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
def bind(self, env: PyramidEnvironment) -> Callable[..., Any]:
    """Bind this activity to a Pyramid environment for Temporal registration.

    Every call of the returned activity is one execution, and owns its own
    ActivityContext, Pyramid request, and transaction. Nothing is shared
    between executions, so they are safe to run concurrently.

    An async activity binds to a coroutine function, which Temporal runs on
    the worker's event loop. A sync activity binds to a plain function, which
    Temporal runs in its activity executor, so a blocking body leaves the
    event loop free.

    Args:
        env: The PyramidEnvironment each execution builds its request from

    Returns:
        An activity that can be registered with a Temporal Worker
    """
    fn = self._fn
    name = self._name

    if self.is_async:

        @temporal_activity.defn(name=name)
        async def execute_async(*args: Any, **kwargs: Any) -> Any:
            """Execute one async activity execution with context injection."""
            with activity_execution(env, threadlocal_request=False) as context:
                return await fn(context, *args, **kwargs)

        return execute_async

    @temporal_activity.defn(name=name, no_thread_cancel_exception=self._no_thread_cancel_exception)
    def execute_sync(*args: Any, **kwargs: Any) -> Any:
        """Execute one sync activity execution with context injection."""
        with activity_execution(env, threadlocal_request=True) as context:
            return fn(context, *args, **kwargs)

    return execute_sync

PyramidEnvironment

Wrapper for Pyramid bootstrap environment.

This class wraps the output of pyramid.paster.bootstrap, providing structured access to the Pyramid application components.

Attributes:
  • registry (Registry) –

    The Pyramid registry

  • app (Optional[Any]) –

    The WSGI application

  • request (Optional[Any]) –

    The base request object

  • root (Optional[Any]) –

    The root object (for traversal-based applications)

Example

from pyramid.paster import bootstrap from pyramid_temporal import PyramidEnvironment

Create from bootstrap output

env_dict = bootstrap('development.ini') env = PyramidEnvironment.from_bootstrap(env_dict)

Access components

settings = env.settings registry = env.registry

Clean up when done

env.close()

Source code in pyramid_temporal/environment.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
class PyramidEnvironment:
    """Wrapper for Pyramid bootstrap environment.

    This class wraps the output of pyramid.paster.bootstrap, providing
    structured access to the Pyramid application components.

    Attributes:
        registry: The Pyramid registry
        app: The WSGI application
        request: The base request object
        root: The root object (for traversal-based applications)

    Example:
        from pyramid.paster import bootstrap
        from pyramid_temporal import PyramidEnvironment

        # Create from bootstrap output
        env_dict = bootstrap('development.ini')
        env = PyramidEnvironment.from_bootstrap(env_dict)

        # Access components
        settings = env.settings
        registry = env.registry

        # Clean up when done
        env.close()
    """

    def __init__(
        self,
        registry: "Registry",
        app: Optional[Any] = None,
        request: Optional[Any] = None,
        root: Optional[Any] = None,
        closer: Optional[Callable[[], None]] = None,
    ) -> None:
        """Initialize the Pyramid environment.

        Args:
            registry: Pyramid registry instance (required)
            app: WSGI application instance
            request: Base request object from bootstrap
            root: Root object for traversal-based applications
            closer: Cleanup callable from bootstrap
        """
        self._registry = registry
        self._app = app
        self._request = request
        self._root = root
        self._closer = closer

        logger.debug("Created PyramidEnvironment with registry: %s", registry)

    @classmethod
    def from_bootstrap(cls, env: dict) -> "PyramidEnvironment":
        """Create a PyramidEnvironment from bootstrap output.

        This is the preferred way to create a PyramidEnvironment when
        using pyramid.paster.bootstrap.

        Args:
            env: Dictionary returned by pyramid.paster.bootstrap()

        Returns:
            PyramidEnvironment instance

        Example:
            from pyramid.paster import bootstrap
            from pyramid_temporal import PyramidEnvironment

            env = PyramidEnvironment.from_bootstrap(bootstrap('development.ini'))
        """
        return cls(
            registry=env["registry"],
            app=env.get("app"),
            request=env.get("request"),
            root=env.get("root"),
            closer=env.get("closer"),
        )

    @property
    def registry(self) -> "Registry":
        """Get the Pyramid registry."""
        return self._registry

    @property
    def app(self) -> Optional[Any]:
        """Get the WSGI application."""
        return self._app

    @property
    def request(self) -> Optional[Any]:
        """Get the base request object from bootstrap."""
        return self._request

    @property
    def root(self) -> Optional[Any]:
        """Get the root object for traversal-based applications."""
        return self._root

    @property
    def settings(self) -> dict:
        """Get application settings (shortcut to registry.settings)."""
        return self._registry.settings

    def close(self) -> None:
        """Clean up resources.

        This calls the closer function from bootstrap to properly
        clean up the Pyramid application.
        """
        if self._closer is not None:
            logger.debug("Closing PyramidEnvironment")
            self._closer()

    def __repr__(self) -> str:
        """Return string representation."""
        return f"<PyramidEnvironment registry={self._registry}>"

app property

Get the WSGI application.

registry property

Get the Pyramid registry.

request property

Get the base request object from bootstrap.

root property

Get the root object for traversal-based applications.

settings property

Get application settings (shortcut to registry.settings).

__init__(registry, app=None, request=None, root=None, closer=None)

Initialize the Pyramid environment.

Parameters:
  • registry (Registry) –

    Pyramid registry instance (required)

  • app (Optional[Any], default: None ) –

    WSGI application instance

  • request (Optional[Any], default: None ) –

    Base request object from bootstrap

  • root (Optional[Any], default: None ) –

    Root object for traversal-based applications

  • closer (Optional[Callable[[], None]], default: None ) –

    Cleanup callable from bootstrap

Source code in pyramid_temporal/environment.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def __init__(
    self,
    registry: "Registry",
    app: Optional[Any] = None,
    request: Optional[Any] = None,
    root: Optional[Any] = None,
    closer: Optional[Callable[[], None]] = None,
) -> None:
    """Initialize the Pyramid environment.

    Args:
        registry: Pyramid registry instance (required)
        app: WSGI application instance
        request: Base request object from bootstrap
        root: Root object for traversal-based applications
        closer: Cleanup callable from bootstrap
    """
    self._registry = registry
    self._app = app
    self._request = request
    self._root = root
    self._closer = closer

    logger.debug("Created PyramidEnvironment with registry: %s", registry)

__repr__()

Return string representation.

Source code in pyramid_temporal/environment.py
131
132
133
def __repr__(self) -> str:
    """Return string representation."""
    return f"<PyramidEnvironment registry={self._registry}>"

close()

Clean up resources.

This calls the closer function from bootstrap to properly clean up the Pyramid application.

Source code in pyramid_temporal/environment.py
121
122
123
124
125
126
127
128
129
def close(self) -> None:
    """Clean up resources.

    This calls the closer function from bootstrap to properly
    clean up the Pyramid application.
    """
    if self._closer is not None:
        logger.debug("Closing PyramidEnvironment")
        self._closer()

from_bootstrap(env) classmethod

Create a PyramidEnvironment from bootstrap output.

This is the preferred way to create a PyramidEnvironment when using pyramid.paster.bootstrap.

Parameters:
  • env (dict) –

    Dictionary returned by pyramid.paster.bootstrap()

Returns:
Example

from pyramid.paster import bootstrap from pyramid_temporal import PyramidEnvironment

env = PyramidEnvironment.from_bootstrap(bootstrap('development.ini'))

Source code in pyramid_temporal/environment.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
@classmethod
def from_bootstrap(cls, env: dict) -> "PyramidEnvironment":
    """Create a PyramidEnvironment from bootstrap output.

    This is the preferred way to create a PyramidEnvironment when
    using pyramid.paster.bootstrap.

    Args:
        env: Dictionary returned by pyramid.paster.bootstrap()

    Returns:
        PyramidEnvironment instance

    Example:
        from pyramid.paster import bootstrap
        from pyramid_temporal import PyramidEnvironment

        env = PyramidEnvironment.from_bootstrap(bootstrap('development.ini'))
    """
    return cls(
        registry=env["registry"],
        app=env.get("app"),
        request=env.get("request"),
        root=env.get("root"),
        closer=env.get("closer"),
    )

Worker

Pyramid-aware Temporal Worker.

This worker wraps the standard Temporal Worker and binds pyramid-temporal activities to the Pyramid environment. Each activity execution gets its own Pyramid request and its own transaction, so max_concurrent_activities greater than 1 is safe.

Activities written as plain def functions run in Temporal's activity executor. The worker creates a thread pool for them unless the caller passes its own activity_executor.

Example

from pyramid.paster import bootstrap from pyramid_temporal import Worker, activity, PyramidEnvironment

@activity.defn async def my_activity(context: ActivityContext, user_id: int) -> bool: session = context.request.dbsession # ... do work ... return True

Create environment from bootstrap

env = PyramidEnvironment.from_bootstrap(bootstrap('development.ini'))

worker = Worker( client, env, task_queue="my-queue", activities=[my_activity], workflows=[MyWorkflow], max_concurrent_activities=10, )

Run the worker

await worker.run()

Source code in pyramid_temporal/worker.py
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
class Worker:
    """Pyramid-aware Temporal Worker.

    This worker wraps the standard Temporal Worker and binds pyramid-temporal
    activities to the Pyramid environment. Each activity execution gets its own
    Pyramid request and its own transaction, so ``max_concurrent_activities``
    greater than 1 is safe.

    Activities written as plain ``def`` functions run in Temporal's activity
    executor. The worker creates a thread pool for them unless the caller passes
    its own ``activity_executor``.

    Example:
        from pyramid.paster import bootstrap
        from pyramid_temporal import Worker, activity, PyramidEnvironment

        @activity.defn
        async def my_activity(context: ActivityContext, user_id: int) -> bool:
            session = context.request.dbsession
            # ... do work ...
            return True

        # Create environment from bootstrap
        env = PyramidEnvironment.from_bootstrap(bootstrap('development.ini'))

        worker = Worker(
            client,
            env,
            task_queue="my-queue",
            activities=[my_activity],
            workflows=[MyWorkflow],
            max_concurrent_activities=10,
        )

        # Run the worker
        await worker.run()
    """

    def __init__(
        self,
        client: Client,
        env: PyramidEnvironment,
        *,
        task_queue: str,
        activities: Sequence[Any] = (),
        workflows: Sequence[type] = (),
        interceptors: Sequence["Interceptor"] = (),
        **kwargs: Any,
    ) -> None:
        """Initialize the Pyramid-aware worker.

        Args:
            client: Temporal client instance
            env: PyramidEnvironment instance (from bootstrap)
            task_queue: Name of the task queue to poll
            activities: List of activities (both pyramid-temporal and plain Temporal)
            workflows: List of workflow classes
            interceptors: Interceptors to include
            **kwargs: Additional arguments passed to Temporal Worker
        """
        self._client = client
        self._env = env
        self._task_queue = task_queue
        self._workflows = workflows
        self._interceptors = list(interceptors)
        self._extra_kwargs = kwargs

        # Bind pyramid activities to the environment and pass others through
        self._bound_activities = self._bind_activities(activities)

        # Sync activities need an executor, and the worker owns the one it creates
        self._owned_activity_executor = self._create_activity_executor()

        # Create the underlying Temporal worker
        self._worker = self._create_worker()

        logger.info(
            "Created Pyramid Worker for task queue '%s' with %d activities and %d workflows",
            task_queue,
            len(self._bound_activities),
            len(workflows),
        )

    def _bind_activities(self, activities: Sequence[Any]) -> list:
        """Bind pyramid-temporal activities to the environment, pass through others.

        Args:
            activities: List of activities (mixed pyramid and plain)

        Returns:
            List of bound/processed activities
        """
        bound = []
        for act in activities:
            if is_pyramid_activity(act):
                pyramid_act: PyramidActivity = act
                bound_act = pyramid_act.bind(self._env)
                logger.debug(
                    "Bound pyramid activity: %s (%s)",
                    pyramid_act.name,
                    "async" if pyramid_act.is_async else "sync",
                )
                bound.append(bound_act)
            else:
                # Pass through plain Temporal activity
                logger.debug("Passing through plain activity: %s", getattr(act, "__name__", act))
                bound.append(act)
        return bound

    def _create_activity_executor(self) -> Optional[ThreadPoolExecutor]:
        """Create the executor Temporal requires for sync activities.

        Temporal refuses to register a non-async activity without an
        ``activity_executor``, so the worker provides one. Callers that pass
        their own executor keep full control and own its lifetime.

        Returns:
            The created executor, or None when none is needed or wanted
        """
        if "activity_executor" in self._extra_kwargs:
            return None

        if all(_is_async_activity(act) for act in self._bound_activities):
            return None

        max_workers = self._extra_kwargs.get("max_concurrent_activities") or DEFAULT_ACTIVITY_SLOTS
        logger.info("Creating activity executor with %d threads for sync activities", max_workers)
        return ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix=ACTIVITY_THREAD_NAME_PREFIX)

    def _activity_executor_kwargs(self) -> dict:
        """Return the executor keyword argument, when the worker created one."""
        if self._owned_activity_executor is None:
            return {}
        return {"activity_executor": self._owned_activity_executor}

    def _create_worker(self) -> TemporalWorker:
        """Create the underlying Temporal worker.

        Returns:
            Configured Temporal Worker instance
        """
        return TemporalWorker(
            self._client,
            task_queue=self._task_queue,
            activities=self._bound_activities,
            workflows=list(self._workflows),
            interceptors=self._interceptors,
            **self._activity_executor_kwargs(),
            **self._extra_kwargs,
        )

    def _shutdown_activity_executor(self) -> None:
        """Shut down the executor the worker created, if any."""
        if self._owned_activity_executor is not None:
            self._owned_activity_executor.shutdown(wait=True)
            logger.debug("Activity executor shut down")

    @property
    def env(self) -> PyramidEnvironment:
        """Get the Pyramid environment."""
        return self._env

    @property
    def task_queue(self) -> str:
        """Get the task queue name."""
        return self._task_queue

    @property
    def activity_executor(self) -> Optional[ThreadPoolExecutor]:
        """Get the activity executor the worker created for sync activities."""
        return self._owned_activity_executor

    async def run(self) -> None:
        """Run the worker until shutdown is requested.

        This is the main entry point for running the worker.
        It will poll the task queue and execute activities/workflows.
        """
        logger.info("Starting Pyramid Worker on task queue '%s'", self._task_queue)
        try:
            await self._worker.run()
        finally:
            self._shutdown_activity_executor()

    async def __aenter__(self) -> "Worker":
        """Async context manager entry."""
        await self._worker.__aenter__()
        return self

    async def __aexit__(self, *args: Any) -> None:
        """Async context manager exit."""
        try:
            await self._worker.__aexit__(*args)
        finally:
            self._shutdown_activity_executor()

activity_executor property

Get the activity executor the worker created for sync activities.

env property

Get the Pyramid environment.

task_queue property

Get the task queue name.

__aenter__() async

Async context manager entry.

Source code in pyramid_temporal/worker.py
226
227
228
229
async def __aenter__(self) -> "Worker":
    """Async context manager entry."""
    await self._worker.__aenter__()
    return self

__aexit__(*args) async

Async context manager exit.

Source code in pyramid_temporal/worker.py
231
232
233
234
235
236
async def __aexit__(self, *args: Any) -> None:
    """Async context manager exit."""
    try:
        await self._worker.__aexit__(*args)
    finally:
        self._shutdown_activity_executor()

__init__(client, env, *, task_queue, activities=(), workflows=(), interceptors=(), **kwargs)

Initialize the Pyramid-aware worker.

Parameters:
  • client (Client) –

    Temporal client instance

  • env (PyramidEnvironment) –

    PyramidEnvironment instance (from bootstrap)

  • task_queue (str) –

    Name of the task queue to poll

  • activities (Sequence[Any], default: () ) –

    List of activities (both pyramid-temporal and plain Temporal)

  • workflows (Sequence[type], default: () ) –

    List of workflow classes

  • interceptors (Sequence[Interceptor], default: () ) –

    Interceptors to include

  • **kwargs (Any, default: {} ) –

    Additional arguments passed to Temporal Worker

Source code in pyramid_temporal/worker.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def __init__(
    self,
    client: Client,
    env: PyramidEnvironment,
    *,
    task_queue: str,
    activities: Sequence[Any] = (),
    workflows: Sequence[type] = (),
    interceptors: Sequence["Interceptor"] = (),
    **kwargs: Any,
) -> None:
    """Initialize the Pyramid-aware worker.

    Args:
        client: Temporal client instance
        env: PyramidEnvironment instance (from bootstrap)
        task_queue: Name of the task queue to poll
        activities: List of activities (both pyramid-temporal and plain Temporal)
        workflows: List of workflow classes
        interceptors: Interceptors to include
        **kwargs: Additional arguments passed to Temporal Worker
    """
    self._client = client
    self._env = env
    self._task_queue = task_queue
    self._workflows = workflows
    self._interceptors = list(interceptors)
    self._extra_kwargs = kwargs

    # Bind pyramid activities to the environment and pass others through
    self._bound_activities = self._bind_activities(activities)

    # Sync activities need an executor, and the worker owns the one it creates
    self._owned_activity_executor = self._create_activity_executor()

    # Create the underlying Temporal worker
    self._worker = self._create_worker()

    logger.info(
        "Created Pyramid Worker for task queue '%s' with %d activities and %d workflows",
        task_queue,
        len(self._bound_activities),
        len(workflows),
    )

run() async

Run the worker until shutdown is requested.

This is the main entry point for running the worker. It will poll the task queue and execute activities/workflows.

Source code in pyramid_temporal/worker.py
214
215
216
217
218
219
220
221
222
223
224
async def run(self) -> None:
    """Run the worker until shutdown is requested.

    This is the main entry point for running the worker.
    It will poll the task queue and execute activities/workflows.
    """
    logger.info("Starting Pyramid Worker on task queue '%s'", self._task_queue)
    try:
        await self._worker.run()
    finally:
        self._shutdown_activity_executor()

activity_execution(env, *, threadlocal_request)

Give one activity execution its own request and its own transaction.

The transaction commits when the body returns and aborts when it raises, and the request is closed either way. Nothing is shared with any other execution, so activities are safe to run concurrently.

Parameters:
  • env (PyramidEnvironment) –

    PyramidEnvironment the request is built from

  • threadlocal_request (bool) –

    Publish the request on Pyramid's threadlocal stack. See ActivityContext.create_request.

Yields:
Source code in pyramid_temporal/execution.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@contextmanager
def activity_execution(env: PyramidEnvironment, *, threadlocal_request: bool) -> Iterator[ActivityContext]:
    """Give one activity execution its own request and its own transaction.

    The transaction commits when the body returns and aborts when it raises, and
    the request is closed either way. Nothing is shared with any other
    execution, so activities are safe to run concurrently.

    Args:
        env: PyramidEnvironment the request is built from
        threadlocal_request: Publish the request on Pyramid's threadlocal stack.
            See ``ActivityContext.create_request``.

    Yields:
        The ActivityContext for this execution
    """
    context = ActivityContext(env=env)
    request = context.create_request(threadlocal_request=threadlocal_request)
    tm = None

    try:
        tm = _begin_transaction(request)
        yield context
    except Exception as e:
        if tm is not None:
            logger.warning("Activity failed with exception: %s, aborting transaction", e)
            safe_abort(tm)
        raise
    else:
        if tm is not None:
            safe_commit(tm)
    finally:
        context.close_request()

defn(fn=None, *, name=None, no_thread_cancel_exception=False)

Decorator to define a pyramid-temporal activity.

This decorator marks a function as a pyramid-temporal activity that will receive an ActivityContext as its first argument. The context is automatically injected when the activity is executed via the pyramid-temporal Worker.

The decorated function should have ActivityContext as its first parameter:

@activity.defn
async def my_activity(context: ActivityContext, user_id: int) -> bool:
    session = context.request.dbsession
    # ... do work ...
    return True

A plain def works too, and is the better choice whenever the body blocks. Temporal runs sync activities in its activity executor, so a blocking call never occupies the worker's event loop:

@activity.defn
def my_blocking_activity(context: ActivityContext, user_id: int) -> bool:
    session = context.request.dbsession
    # ... blocking HTTP, gRPC, or database work ...
    return True

Workflow code references the decorated activity itself, and Temporal reads the registered name from it:

await workflow.execute_activity(
    my_activity, user_id, start_to_close_timeout=timedelta(seconds=30)
)
Parameters:
  • fn (Optional[F], default: None ) –

    The activity function (when used without parentheses)

  • name (Optional[str], default: None ) –

    Optional custom name for the activity. Defaults to function name.

  • no_thread_cancel_exception (bool, default: False ) –

    Whether Temporal should skip raising the cancellation exception in the activity thread. Sync activities only.

Returns:
  • Any

    A decorated activity that the Worker binds to its Pyramid environment,

  • Any

    and that workflow code can pass to workflow.execute_activity.

Example

@activity.defn async def process_order(context: ActivityContext, order_id: int) -> bool: session = context.request.dbsession order = session.query(Order).get(order_id) # Process the order... return True

Or with custom name:

@activity.defn(name="custom-activity-name") async def my_activity(context: ActivityContext, data: str) -> str: return data.upper()

Source code in pyramid_temporal/activity.py
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def defn(
    fn: Optional[F] = None,
    *,
    name: Optional[str] = None,
    no_thread_cancel_exception: bool = False,
) -> Any:
    """Decorator to define a pyramid-temporal activity.

    This decorator marks a function as a pyramid-temporal activity that
    will receive an ActivityContext as its first argument. The context
    is automatically injected when the activity is executed via the
    pyramid-temporal Worker.

    The decorated function should have ActivityContext as its first parameter:

        @activity.defn
        async def my_activity(context: ActivityContext, user_id: int) -> bool:
            session = context.request.dbsession
            # ... do work ...
            return True

    A plain ``def`` works too, and is the better choice whenever the body
    blocks. Temporal runs sync activities in its activity executor, so a
    blocking call never occupies the worker's event loop:

        @activity.defn
        def my_blocking_activity(context: ActivityContext, user_id: int) -> bool:
            session = context.request.dbsession
            # ... blocking HTTP, gRPC, or database work ...
            return True

    Workflow code references the decorated activity itself, and Temporal reads
    the registered name from it:

        await workflow.execute_activity(
            my_activity, user_id, start_to_close_timeout=timedelta(seconds=30)
        )

    Args:
        fn: The activity function (when used without parentheses)
        name: Optional custom name for the activity. Defaults to function name.
        no_thread_cancel_exception: Whether Temporal should skip raising the
            cancellation exception in the activity thread. Sync activities only.

    Returns:
        A decorated activity that the Worker binds to its Pyramid environment,
        and that workflow code can pass to ``workflow.execute_activity``.

    Example:
        @activity.defn
        async def process_order(context: ActivityContext, order_id: int) -> bool:
            session = context.request.dbsession
            order = session.query(Order).get(order_id)
            # Process the order...
            return True

        # Or with custom name:
        @activity.defn(name="custom-activity-name")
        async def my_activity(context: ActivityContext, data: str) -> str:
            return data.upper()
    """

    def decorator(func: F) -> "PyramidActivity":
        activity = PyramidActivity(
            func,
            name=name,
            no_thread_cancel_exception=no_thread_cancel_exception,
        )
        return activity

    # Handle both @activity.defn and @activity.defn() syntax
    if fn is not None:
        return decorator(fn)
    return decorator

includeme(config)

Pyramid configuration include function.

This function can be called via config.include('pyramid_temporal') to register pyramid-temporal with a Pyramid application.

Configuration settings: - pyramid_temporal.temporal_host: Temporal server host (default: localhost:7233) - pyramid_temporal.temporal_namespace: Temporal namespace (default: default). The alias pyramid_temporal.namespace is also accepted and takes precedence. - pyramid_temporal.task_queue: Default task queue for started workflows (default: default) - pyramid_temporal.log_level: Logging level (default: INFO) - pyramid_temporal.auto_connect: Auto-connect to Temporal on startup (default: True)

Request methods registered: - request.temporal_client: The connected async Temporal client (or None). - request.temporal_start_workflow(workflow_run, arg, , id, task_queue=None) -> run_id - request.temporal_signal_workflow(workflow_id, run_id, signal, args) -> None

Parameters:
  • config (Configurator) –

    Pyramid configurator instance

Example
from pyramid.config import Configurator

def main():
    config = Configurator()
    config.include('pyramid_temporal')

    # Optional: Configure Temporal connection
    config.registry.settings['pyramid_temporal.temporal_host'] = 'localhost:7233'

    # ... rest of configuration
Source code in pyramid_temporal/__init__.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def includeme(config: "Configurator") -> None:
    """Pyramid configuration include function.

    This function can be called via config.include('pyramid_temporal')
    to register pyramid-temporal with a Pyramid application.

    Configuration settings:
    - pyramid_temporal.temporal_host: Temporal server host (default: localhost:7233)
    - pyramid_temporal.temporal_namespace: Temporal namespace (default: default).
      The alias pyramid_temporal.namespace is also accepted and takes precedence.
    - pyramid_temporal.task_queue: Default task queue for started workflows (default: default)
    - pyramid_temporal.log_level: Logging level (default: INFO)
    - pyramid_temporal.auto_connect: Auto-connect to Temporal on startup (default: True)

    Request methods registered:
    - request.temporal_client: The connected async Temporal client (or None).
    - request.temporal_start_workflow(workflow_run, arg, *, id, task_queue=None) -> run_id
    - request.temporal_signal_workflow(workflow_id, run_id, signal, *args) -> None

    Args:
        config: Pyramid configurator instance

    Example:
        ```python
        from pyramid.config import Configurator

        def main():
            config = Configurator()
            config.include('pyramid_temporal')

            # Optional: Configure Temporal connection
            config.registry.settings['pyramid_temporal.temporal_host'] = 'localhost:7233'

            # ... rest of configuration
        ```
    """
    logger.info("Including pyramid-temporal configuration")

    # Get settings
    settings = config.get_settings()

    # Set default settings for pyramid-temporal if they don't exist
    if "pyramid_temporal.log_level" not in settings:
        settings["pyramid_temporal.log_level"] = "INFO"

    if "pyramid_temporal.temporal_host" not in settings:
        settings["pyramid_temporal.temporal_host"] = "localhost:7233"

    if "pyramid_temporal.temporal_namespace" not in settings:
        settings["pyramid_temporal.temporal_namespace"] = "default"

    if "pyramid_temporal.task_queue" not in settings:
        settings["pyramid_temporal.task_queue"] = "default"

    if "pyramid_temporal.auto_connect" not in settings:
        settings["pyramid_temporal.auto_connect"] = "true"

    # Configure logging level
    log_level = settings.get("pyramid_temporal.log_level", "INFO").upper()
    pyramid_temporal_logger = logging.getLogger("pyramid_temporal")
    pyramid_temporal_logger.setLevel(getattr(logging, log_level, logging.INFO))

    # pyramid-temporal configuration is now complete

    # Setup Temporal client if auto_connect is enabled
    auto_connect = settings.get("pyramid_temporal.auto_connect", "true").lower() == "true"

    if auto_connect:
        _setup_temporal_client(config, settings)

    # Add request method to get Temporal client
    config.add_request_method(_get_temporal_client, "temporal_client", reify=True)

    # Add request methods to start/signal workflows from synchronous code
    config.add_request_method(temporal_start_workflow, "temporal_start_workflow")
    config.add_request_method(temporal_signal_workflow, "temporal_signal_workflow")

    logger.info("pyramid-temporal configuration complete")

is_pyramid_activity(obj)

Check if an object is a pyramid-temporal activity.

Parameters:
  • obj (Any) –

    Object to check

Returns:
  • bool

    True if the object is a pyramid-temporal activity

Source code in pyramid_temporal/activity.py
302
303
304
305
306
307
308
309
310
311
def is_pyramid_activity(obj: Any) -> bool:
    """Check if an object is a pyramid-temporal activity.

    Args:
        obj: Object to check

    Returns:
        True if the object is a pyramid-temporal activity
    """
    return getattr(obj, PYRAMID_ACTIVITY_MARKER, False)

signal_workflow(*, temporal_host, namespace, workflow_id, run_id, signal, args=())

Connect to Temporal and signal a running workflow.

Source code in pyramid_temporal/client.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def signal_workflow(
    *,
    temporal_host: str,
    namespace: str,
    workflow_id: str,
    run_id: str,
    signal: str,
    args: Sequence[Any] = (),
) -> None:
    """Connect to Temporal and signal a running workflow."""

    async def _signal() -> None:
        client = await Client.connect(temporal_host, namespace=namespace)
        handle = client.get_workflow_handle(workflow_id, run_id=run_id)
        await handle.signal(signal, *args)

    _run_sync(_signal())

start_workflow(*, temporal_host, namespace, task_queue, workflow_run, arg, id, wait=False)

start_workflow(*, temporal_host: str, namespace: str, task_queue: str, workflow_run: Any, arg: Any, id: str, wait: Literal[False] = False) -> str
start_workflow(*, temporal_host: str, namespace: str, task_queue: str, workflow_run: Any, arg: Any, id: str, wait: Literal[True]) -> Any

Connect to Temporal and start a workflow.

By default this returns the run_id of the started workflow without waiting. Pass wait=True to block until the workflow completes and return its result instead (equivalent to Temporal's execute_workflow).

Source code in pyramid_temporal/client.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def start_workflow(
    *,
    temporal_host: str,
    namespace: str,
    task_queue: str,
    workflow_run: Any,
    arg: Any,
    id: str,  # noqa: A002 - mirrors temporalio's start_workflow(id=...) API
    wait: bool = False,
) -> Any:
    """Connect to Temporal and start a workflow.

    By default this returns the ``run_id`` of the started workflow without waiting.
    Pass ``wait=True`` to block until the workflow completes and return its result
    instead (equivalent to Temporal's ``execute_workflow``).
    """

    async def _run() -> Any:
        client = await Client.connect(temporal_host, namespace=namespace)
        if wait:
            return await client.execute_workflow(workflow_run, arg, id=id, task_queue=task_queue)
        handle = await client.start_workflow(workflow_run, arg, id=id, task_queue=task_queue)
        run_id = handle.first_execution_run_id
        if run_id is None:
            raise RuntimeError(f"Temporal did not return a run id for workflow '{id}'")
        return run_id

    return _run_sync(_run())

temporal_signal_workflow(request, workflow_id, run_id, signal, *args)

Signal a running Temporal workflow using registry connection settings.

Source code in pyramid_temporal/__init__.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
def temporal_signal_workflow(
    request: "Request",
    workflow_id: str,
    run_id: str,
    signal: str,
    *args: Any,
) -> None:
    """Signal a running Temporal workflow using registry connection settings."""
    host, namespace, _ = _client_settings(request)
    signal_workflow(
        temporal_host=host,
        namespace=namespace,
        workflow_id=workflow_id,
        run_id=run_id,
        signal=signal,
        args=args,
    )

temporal_start_workflow(request, workflow_run, arg, *, id, task_queue=None)

Start a Temporal workflow using connection settings from the registry.

The task queue defaults to the pyramid_temporal.task_queue setting, so callers can omit it. Pass task_queue to override the configured queue for a single call. Returns the started workflow run_id.

Source code in pyramid_temporal/__init__.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
def temporal_start_workflow(
    request: "Request",
    workflow_run: Any,
    arg: Any,
    *,
    id: str,  # noqa: A002 - mirrors temporalio's start_workflow(id=...) API
    task_queue: Optional[str] = None,
) -> str:
    """Start a Temporal workflow using connection settings from the registry.

    The task queue defaults to the ``pyramid_temporal.task_queue`` setting, so callers
    can omit it. Pass ``task_queue`` to override the configured queue for a single call.
    Returns the started workflow ``run_id``.
    """
    host, namespace, default_queue = _client_settings(request)
    return start_workflow(
        temporal_host=host,
        namespace=namespace,
        task_queue=task_queue or default_queue,
        workflow_run=workflow_run,
        arg=arg,
        id=id,
    )