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

* fix: work with type syntax

* fix: one more instance of asyncio.iscoroutinefunction

* test: add tests for type alias with type syntax

* chore: remove type ignores

* chore: changelog + bump
This commit is contained in:
Stefan
2026-02-24 23:28:15 +01:00
committed by GitHub
parent f3ec9597a6
commit 1f15da6d43
10 changed files with 124 additions and 15 deletions
+10
View File
@@ -5,6 +5,16 @@ All notable changes to Hatchet's Python SDK will be documented in this changelog
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.25.3] - 2026-02-24
### Fixed
- Fixes dependencies not working when using `type Dependency = Annotated[..., ...]` syntax for annotations.
### Changed
- Changes one function in the python SDK to use `inspect.iscoroutinefunction` instead of `asyncio.iscoroutinefunction` which is deprecated.
## [1.25.2] - 2026-02-19
### Fixed
@@ -0,0 +1,25 @@
from hatchet_sdk.runnables.task import Depends
from hatchet_sdk import Context
from hatchet_sdk.runnables.types import EmptyModel
from typing import Annotated, TypeAlias
async def async_dep(input: EmptyModel, ctx: Context) -> bool:
return True
def sync_dep(input: EmptyModel, ctx: Context) -> bool:
return True
AsyncDepNoTypeAlias = Annotated[bool, Depends(async_dep)]
AsyncDepTypeAlias: TypeAlias = Annotated[bool, Depends(async_dep)]
AsyncDepTypeSyntax: TypeAlias = (
AsyncDepTypeAlias # python <3.12 doesn't support `type` syntax for type alias so we use type alias again
)
SyncDepNoTypeAlias = Annotated[bool, Depends(sync_dep)]
SyncDepTypeAlias: TypeAlias = Annotated[bool, Depends(sync_dep)]
SyncDepTypeSyntax: TypeAlias = (
SyncDepTypeAlias # python <3.12 doesn't support `type` syntax for type alias so we use type alias again
)
@@ -0,0 +1,21 @@
from hatchet_sdk.runnables.task import Depends
from hatchet_sdk import Context
from hatchet_sdk.runnables.types import EmptyModel
from typing import Annotated, TypeAlias
async def async_dep(input: EmptyModel, ctx: Context) -> bool:
return True
def sync_dep(input: EmptyModel, ctx: Context) -> bool:
return True
AsyncDepNoTypeAlias = Annotated[bool, Depends(async_dep)]
AsyncDepTypeAlias: TypeAlias = Annotated[bool, Depends(async_dep)]
type AsyncDepTypeSyntax = Annotated[bool, Depends(async_dep)]
SyncDepNoTypeAlias = Annotated[bool, Depends(sync_dep)]
SyncDepTypeAlias: TypeAlias = Annotated[bool, Depends(sync_dep)]
type SyncDepTypeSyntax = Annotated[bool, Depends(sync_dep)]
@@ -15,6 +15,7 @@ from examples.dependency_injection.worker import (
durable_sync_task_with_dependencies,
sync_dep,
sync_task_with_dependencies,
task_with_type_aliases,
)
from hatchet_sdk import EmptyModel
from hatchet_sdk.runnables.workflow import Standalone
@@ -66,3 +67,11 @@ async def test_di_workflows() -> None:
)
assert parsed.chained_dep == "chained_" + CHAINED_CM_VALUE
assert parsed.chained_async_dep == "chained_" + CHAINED_ASYNC_CM_VALUE
@pytest.mark.asyncio(loop_scope="session")
async def test_type_aliases() -> None:
result = await task_with_type_aliases.aio_run(EmptyModel())
assert result
for key in result:
assert result[key] is True
@@ -1,5 +1,6 @@
from contextlib import asynccontextmanager, contextmanager
from typing import Annotated, AsyncGenerator, Generator
import sys
from pydantic import BaseModel
@@ -14,6 +15,46 @@ ASYNC_CM_DEPENDENCY_VALUE = "async_cm_dependency_value"
CHAINED_CM_VALUE = "chained_cm_value"
CHAINED_ASYNC_CM_VALUE = "chained_async_cm_value"
if sys.version_info >= (3, 12):
from examples.dependency_injection.dependency_annotations312 import (
AsyncDepNoTypeAlias,
AsyncDepTypeAlias,
SyncDepNoTypeAlias,
AsyncDepTypeSyntax,
SyncDepTypeAlias,
SyncDepTypeSyntax,
)
else:
from examples.dependency_injection.dependency_annotations310 import (
AsyncDepNoTypeAlias,
AsyncDepTypeAlias,
SyncDepNoTypeAlias,
AsyncDepTypeSyntax,
SyncDepTypeAlias,
SyncDepTypeSyntax,
)
@hatchet.task()
async def task_with_type_aliases(
_i: EmptyModel,
ctx: Context,
async_dep_no_type_alias: AsyncDepNoTypeAlias,
async_dep_type_alias: AsyncDepTypeAlias,
async_dep_type_syntax: AsyncDepTypeSyntax,
sync_dep_no_type_alias: SyncDepNoTypeAlias,
sync_dep_type_alias: SyncDepTypeAlias,
sync_dep_type_syntax: SyncDepTypeSyntax,
) -> dict[str, bool]:
return {
"async_dep_no_type_alias": async_dep_no_type_alias,
"async_dep_type_alias": async_dep_type_alias,
"async_dep_type_syntax": async_dep_type_syntax,
"sync_dep_no_type_alias": sync_dep_no_type_alias,
"sync_dep_type_alias": sync_dep_type_alias,
"sync_dep_type_syntax": sync_dep_type_syntax,
}
# > Declare dependencies
async def async_dep(input: EmptyModel, ctx: Context) -> str:
@@ -274,6 +315,7 @@ def main() -> None:
durable_async_task_with_dependencies,
durable_sync_task_with_dependencies,
di_workflow,
task_with_type_aliases,
],
)
worker.start()
+2
View File
@@ -29,6 +29,7 @@ from examples.dependency_injection.worker import (
durable_async_task_with_dependencies,
durable_sync_task_with_dependencies,
sync_task_with_dependencies,
task_with_type_aliases,
)
from examples.dict_input.worker import say_hello_unsafely
from examples.durable.worker import durable_workflow, wait_for_sleep_twice
@@ -107,6 +108,7 @@ def main() -> None:
sync_task_with_dependencies,
durable_async_task_with_dependencies,
durable_sync_task_with_dependencies,
task_with_type_aliases,
say_hello,
say_hello_unsafely,
serde_workflow,
@@ -24,6 +24,7 @@ from typing import (
)
from pydantic import BaseModel, ConfigDict, TypeAdapter
from typing_extensions import TypeAliasType
from hatchet_sdk.conditions import (
Action,
@@ -273,6 +274,8 @@ class Task(Generic[TWorkflowInput, R]):
) = None,
) -> DependencyToInject | None:
annotation = param.annotation
if isinstance(annotation, TypeAliasType):
annotation = annotation.__value__
if get_origin(annotation) is Annotated:
args = get_args(annotation)
+1 -2
View File
@@ -1,4 +1,3 @@
import asyncio
import inspect
import json
from collections.abc import Callable, Mapping
@@ -148,7 +147,7 @@ TaskFunc = AsyncFunc[TWorkflowInput, R] | SyncFunc[TWorkflowInput, R]
def is_async_fn(
fn: TaskFunc[TWorkflowInput, R],
) -> TypeGuard[AsyncFunc[TWorkflowInput, R]]:
return asyncio.iscoroutinefunction(fn)
return inspect.iscoroutinefunction(fn)
def is_sync_fn(
+8 -11
View File
@@ -325,21 +325,19 @@ version = "2025.11.12"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.7"
groups = ["main", "docs"]
groups = ["main", "docs", "test"]
files = [
{file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"},
{file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"},
]
markers = {main = "extra == \"otel\""}
[[package]]
name = "charset-normalizer"
version = "3.4.4"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = true
optional = false
python-versions = ">=3.7"
groups = ["main"]
markers = "extra == \"otel\""
groups = ["main", "test"]
files = [
{file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"},
{file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"},
@@ -981,7 +979,7 @@ version = "3.11"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.8"
groups = ["main", "docs"]
groups = ["main", "docs", "test"]
files = [
{file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"},
{file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"},
@@ -2778,10 +2776,9 @@ pyyaml = "*"
name = "requests"
version = "2.32.5"
description = "Python HTTP for Humans."
optional = true
optional = false
python-versions = ">=3.9"
groups = ["main"]
markers = "extra == \"otel\""
groups = ["main", "test"]
files = [
{file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"},
{file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"},
@@ -3130,7 +3127,7 @@ version = "2.6.3"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.9"
groups = ["main", "lint"]
groups = ["main", "lint", "test"]
files = [
{file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"},
{file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"},
@@ -3470,4 +3467,4 @@ v0-sdk = []
[metadata]
lock-version = "2.1"
python-versions = "^3.10"
content-hash = "258e0c23b44c9d791c366483aa941e1ffda6ae89af9f18e35b1adb8c70f139ff"
content-hash = "418d80219c5db27a8b6476a69256ca5fb51541ad9167ed991a848e7671216817"
+3 -2
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "hatchet-sdk"
version = "1.25.2"
version = "1.25.3"
description = "This is the official Python SDK for Hatchet, a distributed, fault-tolerant task queue. The SDK allows you to easily integrate Hatchet's task scheduling and workflow orchestration capabilities into your Python applications."
authors = [
"Alexander Belanger <alexander@hatchet.run>",
@@ -51,6 +51,7 @@ pytest-env = "^1.1.5"
pytest-retry = "^1.7.0"
psycopg = { extras = ["pool"], version = "^3.2.6" }
pytest-xdist = "^3.7.0"
requests = "^2.32.5"
[tool.poetry.group.docs.dependencies]
@@ -201,7 +202,7 @@ exclude = [
[tool.pydoclint]
style = 'sphinx'
exclude = 'v0|clients/rest/*|contracts/*|.venv|site/*'
exclude = 'v0|clients/rest/*|contracts/*|.venv|site/*|examples/dependency_injection/dependency_annotations312.py'
arg-type-hints-in-docstring = false # Automatically checked by mypy and mkdocs
check-return-types = false # Automatically checked by mypy and mkdocs