Skip to content

Fix: make python 3.12 type X = ... syntax work with dependency injection #2951

Open
StefanBRas wants to merge 4 commits intohatchet-dev:mainfrom
StefanBRas:sras/dependecy-error-type-syntax
Open

Fix: make python 3.12 type X = ... syntax work with dependency injection #2951
StefanBRas wants to merge 4 commits intohatchet-dev:mainfrom
StefanBRas:sras/dependecy-error-type-syntax

Conversation

@StefanBRas
Copy link
Contributor

Description

For the python SDK, the dependency injection framework does some type introspection to figure out the annotation and get the dependency.
This fails for types created with type X = ... which at runtime is a TypeAliasType where the actual type is available on t.__value__.

Sorry, I again just took the liberty of creating the PR without an issue but it's easier to explain with the code.

I'm not sure how to do the test. I think the type alias needs to be in two different files and then you import one depending on sys.version_info. But didn't want to go too far that way until I got some comments

You can verify the fix by removing the change and then see that the test now fails

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Documentation change (pure documentation change)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking changes to code which doesn't change any behaviour)
  • CI (any automation pipeline changes)
  • Chore (changes which are not directly related to any business logic)
  • Test changes (add, refactor, improve or change a test)
  • This change requires a documentation update

What's Changed

  • Support TypeAliasType created with python 3.12 type X = ... syntax in dependency injection framework.

@vercel
Copy link

vercel bot commented Feb 5, 2026

@StefanBRas is attempting to deploy a commit to the Hatchet Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI review requested due to automatic review settings February 11, 2026 12:19
Copy link
Contributor

Copilot AI left a comment

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 updates the Python SDK’s dependency injection type-introspection to support Python 3.12 type X = ... aliases (runtime TypeAliasType) by unwrapping to the underlying __value__.

Changes:

  • Unwrap TypeAliasType annotations during dependency parsing so DI can read Annotated[...] metadata.
  • Update the DI example to use a type alias for an injected dependency.
  • Adjust async function detection to use inspect.iscoroutinefunction.

Reviewed changes

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

File Description
sdks/python/hatchet_sdk/runnables/types.py Changes coroutine-function detection implementation.
sdks/python/hatchet_sdk/runnables/task.py Adds handling for TypeAliasType by unwrapping to __value__ before DI parsing.
sdks/python/examples/dependency_injection/worker.py Demonstrates DI with a type alias for an Annotated[..., Depends(...)] parameter.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

chained_async_dep: str


type AsyncDep = Annotated[str, Depends(async_dep)]
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Using PEP 695 type AsyncDep = ... syntax makes this example file a SyntaxError on Python < 3.12. If the SDK/examples are intended to run on older supported Python versions, switch to a backwards-compatible alias (e.g., assignment-based alias, optionally with TypeAlias) instead of the type statement, or gate the example so it’s only executed on 3.12+.

Suggested change
type AsyncDep = Annotated[str, Depends(async_dep)]
AsyncDep: TypeAlias = Annotated[str, Depends(async_dep)]

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

@StefanBRas it'd be worth testing this on 3.10 and 3.11 to confirm, because we'll need to handle this case if this is correct

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can handle TypeAlias on any version as well, but I wasn't sure how you want to organize the tests since the type X = ... is a syntax error on <3.12 so it is not even possible to import a module with the syntax.

I'm thinking to just add a module with the type that is then conditionally imported depending on the version.
Do you want a test that is just skipped with pytest skipped if version is <3.12 or just that the test only uses type syntax if >=3.12?

Copy link
Contributor

Choose a reason for hiding this comment

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

we have this in utils/typing.py - something similar might help here too?

if sys.version_info >= (3, 12):
    AwaitableLike: TypeAlias = Awaitable[_T_co]
    CoroutineLike: TypeAlias = Coroutine[Any, Any, _T_co]
else:
    AwaitableLike: TypeAlias = Generator[Any, None, _T_co] | Awaitable[_T_co]
    CoroutineLike: TypeAlias = Generator[Any, None, _T_co] | Coroutine[Any, Any, _T_co]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately that's not enough:

~/tmp/test_type$ cat test.py
import sys
from typing import Any, Awaitable, Coroutine, TypeAlias, Generator

if sys.version_info >= (3, 12):
    type AwaitableLike = Awaitable[Any]
    type CoroutineLike = Coroutine[Any, Any, Any]
else:
    AwaitableLike: TypeAlias = Generator[Any, None, Any] | Awaitable[Any]
    CoroutineLike: TypeAlias = Generator[Any, None, Any] | Coroutine[Any, Any, Any]

print("No errors")
~/tmp/test_type$ uv run --python 3.12 python test.py
No errors
~/tmp/test_type$ uv run --python 3.10 python test.py
  File "/home/sras/tmp/test_type/test.py", line 5
    type AwaitableLike = Awaitable[Any]
         ^^^^^^^^^^^^^
SyntaxError: invalid syntax

The syntax can't be in the file at all, not even in a class scope.

Maybe it's possible to construct the TypeAliasType object itself without the syntax, but that feels more brittle.

My idea is to have something like this:

~/tmp/test_type$ cat test_split.py
import sys
from typing import Any, Awaitable, Coroutine

if sys.version_info >= (3, 12):
    from test_312 import AwaitableLike, CoroutineLike
else:
    from test_310 import AwaitableLike, CoroutineLike

print("No errors")
~/tmp/test_type$ cat test_310.py
from typing import Any, Awaitable, Coroutine, Generator, TypeAlias

AwaitableLike: TypeAlias = Generator[Any, None, Any] | Awaitable[Any]
CoroutineLike: TypeAlias = Generator[Any, None, Any] | Coroutine[Any, Any, Any]
~/tmp/test_type$ cat test_312.py
from typing import Any, Awaitable, Coroutine

type AwaitableLike = Awaitable[Any]
type CoroutineLike = Coroutine[Any, Any, Any]
~/tmp/test_type$ uv run --python 3.12 python test_split.py
No errors
~/tmp/test_type$ uv run --python 3.10 python test_split.py
No errors
~/tmp/test_type$

_i: EmptyModel,
ctx: Context,
async_dep: Annotated[str, Depends(async_dep)],
async_dep: AsyncDep,
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Using PEP 695 type AsyncDep = ... syntax makes this example file a SyntaxError on Python < 3.12. If the SDK/examples are intended to run on older supported Python versions, switch to a backwards-compatible alias (e.g., assignment-based alias, optionally with TypeAlias) instead of the type statement, or gate the example so it’s only executed on 3.12+.

Copilot uses AI. Check for mistakes.
get_origin,
get_type_hints,
)
from typing_extensions import TypeAliasType
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The runtime class for type X = ... in Python 3.12 is typing.TypeAliasType. Depending on how typing_extensions.TypeAliasType is implemented in the installed version, isinstance(annotation, TypeAliasType) can be brittle across environments (e.g., if it’s not the same class object). Prefer importing TypeAliasType from typing when available with a fallback to typing_extensions, or use a small compatibility check that handles both stdlib and typing_extensions implementations.

Suggested change
from typing_extensions import TypeAliasType
try:
from typing import TypeAliasType # type: ignore[attr-defined]
except ImportError: # pragma: no cover - fallback for older Python versions
from typing_extensions import TypeAliasType

Copilot uses AI. Check for mistakes.
Comment on lines +273 to +274
if isinstance(annotation, TypeAliasType):
annotation = annotation.__value__
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The runtime class for type X = ... in Python 3.12 is typing.TypeAliasType. Depending on how typing_extensions.TypeAliasType is implemented in the installed version, isinstance(annotation, TypeAliasType) can be brittle across environments (e.g., if it’s not the same class object). Prefer importing TypeAliasType from typing when available with a fallback to typing_extensions, or use a small compatibility check that handles both stdlib and typing_extensions implementations.

Copilot uses AI. Check for mistakes.
fn: TaskFunc[TWorkflowInput, R],
) -> TypeGuard[AsyncFunc[TWorkflowInput, R]]:
return asyncio.iscoroutinefunction(fn)
return inspect.iscoroutinefunction(fn)
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

asyncio.iscoroutinefunction() and inspect.iscoroutinefunction() are not equivalent: asyncio.iscoroutinefunction() also recognizes some asyncio-specific coroutine markers (e.g., generator-based coroutines / functions marked via _is_coroutine). Replacing it with inspect.iscoroutinefunction() can regress detection for those callables. Consider reverting to asyncio.iscoroutinefunction(fn) (or otherwise preserving asyncio’s extended detection) unless you explicitly want to drop support for those cases.

Copilot uses AI. Check for mistakes.
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.

2 participants