mirror of
https://github.com/hatchet-dev/hatchet.git
synced 2026-04-22 18:19:17 -05:00
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:
@@ -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()
|
||||
|
||||
@@ -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,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(
|
||||
|
||||
Generated
+8
-11
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user