Skip to content

Class based actions: Mypy not happy with defined parameters in the run method #457

Closed

Description

@shun-liang

For class-based actions, mypy is not happy with parameters to be defined in the method signature of the run method.

Current behavior

I have copied the class-based action example from doc:

from burr.core import Action, State, ApplicationContext

class Counter(Action):
    @property
    def reads(self) -> list[str]:
        return ["counter"]

    def run(self,
            state: State,
            increment_by: int,
            # add __context here
            __context: ApplicationContext) -> dict:
        # can access the app_id from the __context
        print(__context.app_id, __context.partition_key, __context.sequence_id)
        return {"counter": state["counter"] + increment_by}

    @property
    def writes(self) -> list[str]:
        return ["counter"]

    def update(self, result: dict, state: State) -> State:
        return state.update(**result)

    @property
    def inputs(self) -> list[str]:
        # add __context here
        return ["increment_by", "__context"]

Then mypy is not happy with it with errors:

mypy src/demo_application.py
src/demo_application.py:8: error: Signature of "run" incompatible with supertype "Function"  [override]
src/demo_application.py:8: note:      Superclass:
src/demo_application.py:8: note:          def run(self, state: State[Any], **run_kwargs: Any) -> dict[Any, Any]
src/demo_application.py:8: note:      Subclass:
src/demo_application.py:8: note:          def run(self, state: State[Any], increment_by: int, ApplicationContext, /) -> dict[Any, Any]
Found 1 error in 1 file (checked 1 source file)

Library & System Information

uv pip list | grep mypy
mypy               1.13.0
mypy-extensions    1.0.0

uv pip list | grep burr
burr               0.34.1

python --version
Python 3.12.5

Activity

  1. elijahbenizzy commented on Dec 5, 2024

    @elijahbenizzy
    Contributor

    Thanks for the report! So this is an inherent mypy limitation/conscious decisiom. Mypy has no way of knowing that the fields are dynamically set by the inputs() property. To work aroudn it, you would want to use **kwargs as the signature and access the parameters dynamically. E.G.

     def run(self,
            state: State,
            **kwargs) -> dict:
            # can access the app_id from the __context
            __context = kwargs["context"]
           increment_by = kwargs["increment_by"]
            print(__context.app_id, __context.partition_key, __context.sequence_id)
            return {"counter": state["counter"] + increment_by}

    We chose not to have that as it's harder to read, and I'm less a believer in strict type-checking in python, but it's there if you want it! You may be able to do something with a typed dict/type-hinting kwargs as well -- haven't tested it out -- see https://peps.python.org/pep-0692/

  2. shun-liang commented on Dec 5, 2024

    @shun-liang
    ContributorAuthor

    Thanks for the response. For now have considered the options, I probably will just put type: ignore[override] to each individual occassion of overriding the run method in a class based action. Being a strong believer of type checking in Python myself this is quite a lot of frictions.

  3. elijahbenizzy commented on Dec 10, 2024

    @elijahbenizzy
    Contributor

    Thanks for the response. For now have considered the options, I probably will just put type: ignore[override] to each individual occassion of overriding the run method in a class based action. Being a strong believer of type checking in Python myself this is quite a lot of frictions.

    Hey! Glad you found a workaround. Sorry for the delay in responding! TBH I think this is an area in which mypy could improve, and also an area in which python/strict type-checking falls a bit flat. That said, I wouldn't be surprised if there's a better way to define the superclass, and I'm happy to dig in a bit. It quite annoys me as well... Will report back!
    image

  4. elijahbenizzy commented on Dec 10, 2024

    @elijahbenizzy
    Contributor

    OK, so I dug in a bit. This is not something we're going to be able to solve at a framework level but I think you have two good options:

    1. Just use **kwargs -- this is "safer" (you're doing the validation, not trusting the framework), but less typed/less fun
    2. Use this decorator which works for me. There are probably better ways to do this (it really loosens the type-checking), but it's kind of clever:
    from functools import wraps
    from typing import Callable, Any
    
    
    def adapt_types(func: Callable[..., Any]) -> Callable[..., Any]:
        @wraps(func)
        def wrapper(*args, **kwargs) -> Any:
            return func(*args, **kwargs)
    
        return wrapper
    
    
    from burr.core import Action, State, ApplicationContext
    
    
    class Counter(Action):
        @property
        def reads(self) -> list[str]:
            return ["counter"]
        @adapt_types
        def run(self,
                state: State,
                increment_by: int,
                # add __context here
                __context: ApplicationContext) -> dict:
            # can access the app_id from the __context
            print(__context.app_id, __context.partition_key, __context.sequence_id)
            return {"counter": state["counter"] + increment_by}
    
        @property
        def writes(self) -> list[str]:
            return ["counter"]
    
        def update(self, result: dict, state: State) -> State:
            return state.update(**result)
    
        @property
        def inputs(self) -> list[str]:
            # add __context here
            return ["increment_by", "__context"]

    Happy to consider adding adapt_types to the framework itself (we'd use it internally), and thinking about how to ensure it doesn't loose too much. But it is extra code.

    This does not work, it has the same error (understandibly so) https://peps.python.org/pep-0692/.

  5. added a commit that references this issue on Mar 18, 2026
    8ff1dfc
  6. andreahlert commented on Mar 18, 2026

    @andreahlert
    Collaborator

    @elijahbenizzy @stefan-ernst @shun-liang opened #683 with a flexible_api decorator to fix this. Tested on Python 3.12 + mypy 1.19.1 in an isolated Docker environment and the override error is gone. Would appreciate a review.

  7. added a commit that references this issue on Mar 28, 2026
    f884adf
  8. added
    kind/bugSomething is broken
    area/typingMypy, type hints, pydantic
    area/coreApplication, State, Graph, Actions
    priority/highAffects many users, needs action within weeks
    and removed on Mar 28, 2026
  9. andreahlert commented on Mar 28, 2026

    @andreahlert
    Collaborator

    Labels migrated to new taxonomy (#692).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/coreApplication, State, Graph, Actionsarea/typingMypy, type hints, pydantickind/bugSomething is brokenpriority/highAffects many users, needs action within weeks

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions