Skip to content

fix(app): support register_lifespan_task as a decorator#6343

Open
BABTUNA wants to merge 2 commits intoreflex-dev:mainfrom
BABTUNA:fix-6330-register-lifespan-decorator
Open

fix(app): support register_lifespan_task as a decorator#6343
BABTUNA wants to merge 2 commits intoreflex-dev:mainfrom
BABTUNA:fix-6330-register-lifespan-decorator

Conversation

@BABTUNA
Copy link
Copy Markdown

@BABTUNA BABTUNA commented Apr 19, 2026

β€’ ### All Submissions:

  • Have you followed the guidelines stated in [CONTRIBUTING.md](https://
    github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md) file?
  • Have you checked to ensure there aren't any other open Pull
    Requests
    for the desired
    changed?

Type of change

  • Bug fix (non-breaking change which fixes an issue)

New Feature Submission:

  • Does your submission pass the tests?
  • Have you linted your code locally prior to submission?

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd
    like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

Description

This fixes App.register_lifespan_task so it can be used as a decorator.

Before this change:

  • @app.register_lifespan_task bound the function name to None because
    the method did not return the task.
  • @app.register_lifespan_task(timeout=10) raised because the method
    required task as a positional arg.

What changed:

  • register_lifespan_task now accepts task: Callable | asyncio.Task | None = None.
  • If called with no task and kwargs only, it returns a decorator via
    functools.partial(...).
  • When registration succeeds, it now returns the original task callable.
  • Internal registration behavior is unchanged: kwargs still produce a
    wrapped functools.partial that is stored in _lifespan_tasks.

Tests

Added:

  • tests/units/app_mixins/test_lifespan.py
    • test_register_lifespan_task_can_be_used_as_decorator
    • test_register_lifespan_task_with_kwargs_can_be_used_as_decorator

Validation run:

  • uv run pytest tests/units/app_mixins/test_lifespan.py -q
  • uv run ruff check reflex/app_mixins/lifespan.py tests/units/app_mixins/ test_lifespan.py

Closes #6330

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 19, 2026

Greptile Summary

This PR fixes register_lifespan_task so it can be used as a plain decorator (@app.register_lifespan_task) or as a decorator factory (@app.register_lifespan_task(timeout=10)). The fix makes task optional, returns a functools.partial when called with kwargs only, and returns the original callable on registration so the decorated name is correctly preserved.

Confidence Score: 5/5

Safe to merge β€” the logic is correct, tests cover both decorator patterns, and internal registration behavior is unchanged.

Both decorator patterns work correctly: the plain case returns the original callable and stores it directly, and the kwargs case returns a functools.partial decorator that then triggers registration with proper guard checks. The _lifespan_tasks_started guard and generator-function validation are preserved on the registration path. No P0/P1 issues found.

No files require special attention.

Important Files Changed

Filename Overview
reflex/app_mixins/lifespan.py Added decorator support to register_lifespan_task: accepts task=None, returns a functools.partial decorator when called with kwargs only, and returns the original callable on direct registration.
tests/units/app_mixins/test_lifespan.py New test file with two tests covering the plain-decorator and decorator-with-kwargs patterns; both are well-structured and verify registration behavior and callable identity correctly.

Sequence Diagram

sequenceDiagram
    participant U as User Code
    participant R as register_lifespan_task
    participant T as _lifespan_tasks dict

    Note over U,T: Plain decorator usage
    U->>R: @app.register_lifespan_task (task=fn)
    R->>R: task is not None β†’ proceed
    R->>R: check _lifespan_tasks_started
    R->>R: check isgeneratorfunction
    R->>T: _lifespan_tasks[fn] = None
    R-->>U: returns fn (original callable)

    Note over U,T: Decorator-with-kwargs usage
    U->>R: @app.register_lifespan_task(timeout=10)
    R->>R: task is None β†’ return partial(register_lifespan_task, timeout=10)
    R-->>U: returns partial (decorator)
    U->>R: partial(fn) β†’ register_lifespan_task(fn, timeout=10)
    R->>R: check _lifespan_tasks_started
    R->>R: check isgeneratorfunction
    R->>R: registered_task = partial(fn, timeout=10)
    R->>T: _lifespan_tasks[registered_task] = None
    R-->>U: returns fn (original callable)
Loading

Reviews (1): Last reviewed commit: "Support register_lifespan_task decorator..." | Re-trigger Greptile

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 20, 2026

Merging this PR will not alter performance

βœ… 9 untouched benchmarks


Comparing BABTUNA:fix-6330-register-lifespan-decorator (8b97bdd) with main (cc7436c)

Open in CodSpeed

Replace the compound return-type union with @overload stubs + a
TypeVar so decorated functions keep their exact signature. Reject
asyncio.Task + kwargs early with a clear error (previously would
silently build a broken partial), which also eliminates the two
pyright ignores. Swap the test's implementation-detail asserts
(functools.partial internals) for a behavioral call-the-task check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App.register_lifespan_task should work as a decorator

2 participants