[Bug] An activity method with an annotated `self` receives its arguments without their types

Fermée
#2,063 0 commentaires 0 réactions 1 personne assignée Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

@VegetarianOrc y travaille déjà.

Depuis le 2/11/2026.

  • #2065 par @VegetarianOrc — ouverte

Évaluation

Cette issue n'a pas encore été évaluée.

Description

bug sdk-python

Package: temporalio 1.35.0, the latest release, and main (d488ff5).

What happens

An activity defined as a method, async def m(self, loc: Loc), receives loc as a Loc. The same method with an annotated receiver, such as self: Self in a module with from __future__ import annotations, receives loc as a plain dict.

_type_hints_from_func in temporalio/common.py skips the first parameter only when it is named self and is unannotated, or its annotation's text is typing.Self:

if (
    index == 0
    and value.name == "self"
    and (
        value.annotation is inspect.Parameter.empty
        or str(value.annotation) == "typing.Self"
    )
):
    continue

Under postponed annotations the text is Self, so arg_types gets an entry for the receiver. The Worker then has two types for one argument, and worker/_activity.py falls back to decoding without types (arg_types is not None and len(arg_types) != len(start.input)). The same holds for self: T with a TypeVar bound to the class, self: "Svc", and typing_extensions.Self on Python 3.10. It affects workflow.execute_activity_method and anything else that runs a registered bound method.

Reproduction

One file. It uses a local dev server (temporal CLI on PATH) and exits 1 when the method annotated self: Self receives a dict.

"""An activity method whose `self` is annotated receives its other arguments without their types.

Exits 1 when the method annotated `self: Self` receives a dict instead of a `Loc`.
"""

from __future__ import annotations

import asyncio
import dataclasses
import shutil
import sys
import uuid
from datetime import timedelta
from typing import Self

from temporalio import activity, workflow
from temporalio.testing import WorkflowEnvironment
from temporalio.worker import UnsandboxedWorkflowRunner, Worker


@dataclasses.dataclass
class Loc:
    city: str


class Svc:
    @activity.defn
    async def plain(self, loc: Loc) -> str:
        return type(loc).__name__

    @activity.defn
    async def annotated(self: Self, loc: Loc) -> str:
        return type(loc).__name__


@workflow.defn
class LocWorkflow:
    @workflow.run
    async def run(self) -> list[str]:
        timeout = timedelta(seconds=10)
        return [
            await workflow.execute_activity_method(
                Svc.plain, Loc("Rome"), start_to_close_timeout=timeout
            ),
            await workflow.execute_activity_method(
                Svc.annotated, Loc("Rome"), start_to_close_timeout=timeout
            ),
        ]


async def main() -> None:
    svc = Svc()
    async with await WorkflowEnvironment.start_local(
        dev_server_existing_path=shutil.which("temporal")
    ) as env:
        async with Worker(
            env.client,
            task_queue="tq",
            workflows=[LocWorkflow],
            activities=[svc.plain, svc.annotated],
            workflow_runner=UnsandboxedWorkflowRunner(),
        ):
            plain, annotated = await env.client.execute_workflow(
                LocWorkflow.run, id=str(uuid.uuid4()), task_queue="tq"
            )
    print(f"plain(self, loc: Loc) received: {plain}")
    print(f"annotated(self: Self, loc: Loc) received: {annotated}")
    sys.exit(0 if annotated == "Loc" else 1)


if __name__ == "__main__":
    asyncio.run(main())

Output:

Python 3.13.16 temporalio 1.35.0
$ python annotated_self.py
plain(self, loc: Loc) received: Loc
annotated(self: Self, loc: Loc) received: dict
exit 1

Expected

The Worker registers the method bound to an instance, which supplies self however it is annotated. So annotated should receive a Loc, as plain does.

Proposed fix

When the first parameter is named self, skip it whatever its annotation, as for an unannotated self.

Found while reviewing temporalio/ai-integrations#206, which builds agent tools from activity methods.

Langage dominant
Python
Étoiles
1.2k
Forks
285
Merge moyen
1 j 21 h
PR mergées (30 j)
80

Préparer son environnement

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de temporalio/sdk-python

Toutes les issues de temporalio/sdk-python

Issues similaires

Plus d'issues Python

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.