hatchet-sdk 1.16.4__py3-none-any.whl → 1.17.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hatchet-sdk might be problematic. Click here for more details.
- hatchet_sdk/__init__.py +2 -1
- hatchet_sdk/clients/events.py +5 -2
- hatchet_sdk/clients/rest/__init__.py +32 -0
- hatchet_sdk/clients/rest/api/__init__.py +1 -0
- hatchet_sdk/clients/rest/api/webhook_api.py +1551 -0
- hatchet_sdk/clients/rest/models/__init__.py +31 -0
- hatchet_sdk/clients/rest/models/v1_create_webhook_request.py +215 -0
- hatchet_sdk/clients/rest/models/v1_create_webhook_request_api_key.py +126 -0
- hatchet_sdk/clients/rest/models/v1_create_webhook_request_api_key_all_of_auth_type.py +82 -0
- hatchet_sdk/clients/rest/models/v1_create_webhook_request_base.py +98 -0
- hatchet_sdk/clients/rest/models/v1_create_webhook_request_basic_auth.py +126 -0
- hatchet_sdk/clients/rest/models/v1_create_webhook_request_basic_auth_all_of_auth_type.py +82 -0
- hatchet_sdk/clients/rest/models/v1_create_webhook_request_hmac.py +126 -0
- hatchet_sdk/clients/rest/models/v1_create_webhook_request_hmac_all_of_auth_type.py +82 -0
- hatchet_sdk/clients/rest/models/v1_event.py +7 -0
- hatchet_sdk/clients/rest/models/v1_webhook.py +126 -0
- hatchet_sdk/clients/rest/models/v1_webhook_api_key_auth.py +90 -0
- hatchet_sdk/clients/rest/models/v1_webhook_auth_type.py +38 -0
- hatchet_sdk/clients/rest/models/v1_webhook_basic_auth.py +86 -0
- hatchet_sdk/clients/rest/models/v1_webhook_hmac_algorithm.py +39 -0
- hatchet_sdk/clients/rest/models/v1_webhook_hmac_auth.py +115 -0
- hatchet_sdk/clients/rest/models/v1_webhook_hmac_encoding.py +38 -0
- hatchet_sdk/clients/rest/models/v1_webhook_list.py +110 -0
- hatchet_sdk/clients/rest/models/v1_webhook_receive200_response.py +83 -0
- hatchet_sdk/clients/rest/models/v1_webhook_source_name.py +38 -0
- hatchet_sdk/context/context.py +31 -2
- hatchet_sdk/exceptions.py +70 -5
- hatchet_sdk/hatchet.py +29 -11
- hatchet_sdk/rate_limit.py +1 -21
- hatchet_sdk/runnables/task.py +109 -19
- hatchet_sdk/runnables/workflow.py +23 -8
- hatchet_sdk/utils/typing.py +27 -0
- hatchet_sdk/worker/runner/runner.py +27 -19
- hatchet_sdk/worker/runner/utils/capture_logs.py +24 -11
- {hatchet_sdk-1.16.4.dist-info → hatchet_sdk-1.17.0.dist-info}/METADATA +2 -3
- {hatchet_sdk-1.16.4.dist-info → hatchet_sdk-1.17.0.dist-info}/RECORD +38 -19
- {hatchet_sdk-1.16.4.dist-info → hatchet_sdk-1.17.0.dist-info}/WHEEL +0 -0
- {hatchet_sdk-1.16.4.dist-info → hatchet_sdk-1.17.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Hatchet API
|
|
5
|
+
|
|
6
|
+
The Hatchet API
|
|
7
|
+
|
|
8
|
+
The version of the OpenAPI document: 1.0.0
|
|
9
|
+
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
10
|
+
|
|
11
|
+
Do not edit the class manually.
|
|
12
|
+
""" # noqa: E501
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import pprint
|
|
19
|
+
import re # noqa: F401
|
|
20
|
+
from typing import Any, ClassVar, Dict, List, Optional, Set
|
|
21
|
+
|
|
22
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictStr
|
|
23
|
+
from typing_extensions import Self
|
|
24
|
+
|
|
25
|
+
from hatchet_sdk.clients.rest.models.v1_webhook_hmac_algorithm import (
|
|
26
|
+
V1WebhookHMACAlgorithm,
|
|
27
|
+
)
|
|
28
|
+
from hatchet_sdk.clients.rest.models.v1_webhook_hmac_encoding import (
|
|
29
|
+
V1WebhookHMACEncoding,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class V1WebhookHMACAuth(BaseModel):
|
|
34
|
+
"""
|
|
35
|
+
V1WebhookHMACAuth
|
|
36
|
+
""" # noqa: E501
|
|
37
|
+
|
|
38
|
+
algorithm: V1WebhookHMACAlgorithm = Field(
|
|
39
|
+
description="The HMAC algorithm to use for the webhook"
|
|
40
|
+
)
|
|
41
|
+
encoding: V1WebhookHMACEncoding = Field(
|
|
42
|
+
description="The encoding to use for the HMAC signature"
|
|
43
|
+
)
|
|
44
|
+
signature_header_name: StrictStr = Field(
|
|
45
|
+
description="The name of the header to use for the HMAC signature",
|
|
46
|
+
alias="signatureHeaderName",
|
|
47
|
+
)
|
|
48
|
+
signing_secret: StrictStr = Field(
|
|
49
|
+
description="The secret key used to sign the HMAC signature",
|
|
50
|
+
alias="signingSecret",
|
|
51
|
+
)
|
|
52
|
+
__properties: ClassVar[List[str]] = [
|
|
53
|
+
"algorithm",
|
|
54
|
+
"encoding",
|
|
55
|
+
"signatureHeaderName",
|
|
56
|
+
"signingSecret",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
model_config = ConfigDict(
|
|
60
|
+
populate_by_name=True,
|
|
61
|
+
validate_assignment=True,
|
|
62
|
+
protected_namespaces=(),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def to_str(self) -> str:
|
|
66
|
+
"""Returns the string representation of the model using alias"""
|
|
67
|
+
return pprint.pformat(self.model_dump(by_alias=True))
|
|
68
|
+
|
|
69
|
+
def to_json(self) -> str:
|
|
70
|
+
"""Returns the JSON representation of the model using alias"""
|
|
71
|
+
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
|
|
72
|
+
return json.dumps(self.to_dict())
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_json(cls, json_str: str) -> Optional[Self]:
|
|
76
|
+
"""Create an instance of V1WebhookHMACAuth from a JSON string"""
|
|
77
|
+
return cls.from_dict(json.loads(json_str))
|
|
78
|
+
|
|
79
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
80
|
+
"""Return the dictionary representation of the model using alias.
|
|
81
|
+
|
|
82
|
+
This has the following differences from calling pydantic's
|
|
83
|
+
`self.model_dump(by_alias=True)`:
|
|
84
|
+
|
|
85
|
+
* `None` is only added to the output dict for nullable fields that
|
|
86
|
+
were set at model initialization. Other fields with value `None`
|
|
87
|
+
are ignored.
|
|
88
|
+
"""
|
|
89
|
+
excluded_fields: Set[str] = set([])
|
|
90
|
+
|
|
91
|
+
_dict = self.model_dump(
|
|
92
|
+
by_alias=True,
|
|
93
|
+
exclude=excluded_fields,
|
|
94
|
+
exclude_none=True,
|
|
95
|
+
)
|
|
96
|
+
return _dict
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
|
|
100
|
+
"""Create an instance of V1WebhookHMACAuth from a dict"""
|
|
101
|
+
if obj is None:
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
if not isinstance(obj, dict):
|
|
105
|
+
return cls.model_validate(obj)
|
|
106
|
+
|
|
107
|
+
_obj = cls.model_validate(
|
|
108
|
+
{
|
|
109
|
+
"algorithm": obj.get("algorithm"),
|
|
110
|
+
"encoding": obj.get("encoding"),
|
|
111
|
+
"signatureHeaderName": obj.get("signatureHeaderName"),
|
|
112
|
+
"signingSecret": obj.get("signingSecret"),
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
return _obj
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Hatchet API
|
|
5
|
+
|
|
6
|
+
The Hatchet API
|
|
7
|
+
|
|
8
|
+
The version of the OpenAPI document: 1.0.0
|
|
9
|
+
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
10
|
+
|
|
11
|
+
Do not edit the class manually.
|
|
12
|
+
""" # noqa: E501
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
from enum import Enum
|
|
19
|
+
|
|
20
|
+
from typing_extensions import Self
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class V1WebhookHMACEncoding(str, Enum):
|
|
24
|
+
"""
|
|
25
|
+
V1WebhookHMACEncoding
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
allowed enum values
|
|
30
|
+
"""
|
|
31
|
+
HEX = "HEX"
|
|
32
|
+
BASE64 = "BASE64"
|
|
33
|
+
BASE64URL = "BASE64URL"
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_json(cls, json_str: str) -> Self:
|
|
37
|
+
"""Create an instance of V1WebhookHMACEncoding from a JSON string"""
|
|
38
|
+
return cls(json.loads(json_str))
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Hatchet API
|
|
5
|
+
|
|
6
|
+
The Hatchet API
|
|
7
|
+
|
|
8
|
+
The version of the OpenAPI document: 1.0.0
|
|
9
|
+
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
10
|
+
|
|
11
|
+
Do not edit the class manually.
|
|
12
|
+
""" # noqa: E501
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import pprint
|
|
19
|
+
import re # noqa: F401
|
|
20
|
+
from typing import Any, ClassVar, Dict, List, Optional, Set
|
|
21
|
+
|
|
22
|
+
from pydantic import BaseModel, ConfigDict
|
|
23
|
+
from typing_extensions import Self
|
|
24
|
+
|
|
25
|
+
from hatchet_sdk.clients.rest.models.pagination_response import PaginationResponse
|
|
26
|
+
from hatchet_sdk.clients.rest.models.v1_webhook import V1Webhook
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class V1WebhookList(BaseModel):
|
|
30
|
+
"""
|
|
31
|
+
V1WebhookList
|
|
32
|
+
""" # noqa: E501
|
|
33
|
+
|
|
34
|
+
pagination: Optional[PaginationResponse] = None
|
|
35
|
+
rows: Optional[List[V1Webhook]] = None
|
|
36
|
+
__properties: ClassVar[List[str]] = ["pagination", "rows"]
|
|
37
|
+
|
|
38
|
+
model_config = ConfigDict(
|
|
39
|
+
populate_by_name=True,
|
|
40
|
+
validate_assignment=True,
|
|
41
|
+
protected_namespaces=(),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def to_str(self) -> str:
|
|
45
|
+
"""Returns the string representation of the model using alias"""
|
|
46
|
+
return pprint.pformat(self.model_dump(by_alias=True))
|
|
47
|
+
|
|
48
|
+
def to_json(self) -> str:
|
|
49
|
+
"""Returns the JSON representation of the model using alias"""
|
|
50
|
+
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
|
|
51
|
+
return json.dumps(self.to_dict())
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def from_json(cls, json_str: str) -> Optional[Self]:
|
|
55
|
+
"""Create an instance of V1WebhookList from a JSON string"""
|
|
56
|
+
return cls.from_dict(json.loads(json_str))
|
|
57
|
+
|
|
58
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
59
|
+
"""Return the dictionary representation of the model using alias.
|
|
60
|
+
|
|
61
|
+
This has the following differences from calling pydantic's
|
|
62
|
+
`self.model_dump(by_alias=True)`:
|
|
63
|
+
|
|
64
|
+
* `None` is only added to the output dict for nullable fields that
|
|
65
|
+
were set at model initialization. Other fields with value `None`
|
|
66
|
+
are ignored.
|
|
67
|
+
"""
|
|
68
|
+
excluded_fields: Set[str] = set([])
|
|
69
|
+
|
|
70
|
+
_dict = self.model_dump(
|
|
71
|
+
by_alias=True,
|
|
72
|
+
exclude=excluded_fields,
|
|
73
|
+
exclude_none=True,
|
|
74
|
+
)
|
|
75
|
+
# override the default output from pydantic by calling `to_dict()` of pagination
|
|
76
|
+
if self.pagination:
|
|
77
|
+
_dict["pagination"] = self.pagination.to_dict()
|
|
78
|
+
# override the default output from pydantic by calling `to_dict()` of each item in rows (list)
|
|
79
|
+
_items = []
|
|
80
|
+
if self.rows:
|
|
81
|
+
for _item_rows in self.rows:
|
|
82
|
+
if _item_rows:
|
|
83
|
+
_items.append(_item_rows.to_dict())
|
|
84
|
+
_dict["rows"] = _items
|
|
85
|
+
return _dict
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
|
|
89
|
+
"""Create an instance of V1WebhookList from a dict"""
|
|
90
|
+
if obj is None:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
if not isinstance(obj, dict):
|
|
94
|
+
return cls.model_validate(obj)
|
|
95
|
+
|
|
96
|
+
_obj = cls.model_validate(
|
|
97
|
+
{
|
|
98
|
+
"pagination": (
|
|
99
|
+
PaginationResponse.from_dict(obj["pagination"])
|
|
100
|
+
if obj.get("pagination") is not None
|
|
101
|
+
else None
|
|
102
|
+
),
|
|
103
|
+
"rows": (
|
|
104
|
+
[V1Webhook.from_dict(_item) for _item in obj["rows"]]
|
|
105
|
+
if obj.get("rows") is not None
|
|
106
|
+
else None
|
|
107
|
+
),
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
return _obj
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Hatchet API
|
|
5
|
+
|
|
6
|
+
The Hatchet API
|
|
7
|
+
|
|
8
|
+
The version of the OpenAPI document: 1.0.0
|
|
9
|
+
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
10
|
+
|
|
11
|
+
Do not edit the class manually.
|
|
12
|
+
""" # noqa: E501
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import pprint
|
|
19
|
+
import re # noqa: F401
|
|
20
|
+
from typing import Any, ClassVar, Dict, List, Optional, Set
|
|
21
|
+
|
|
22
|
+
from pydantic import BaseModel, ConfigDict, StrictStr
|
|
23
|
+
from typing_extensions import Self
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class V1WebhookReceive200Response(BaseModel):
|
|
27
|
+
"""
|
|
28
|
+
V1WebhookReceive200Response
|
|
29
|
+
""" # noqa: E501
|
|
30
|
+
|
|
31
|
+
message: Optional[StrictStr] = None
|
|
32
|
+
__properties: ClassVar[List[str]] = ["message"]
|
|
33
|
+
|
|
34
|
+
model_config = ConfigDict(
|
|
35
|
+
populate_by_name=True,
|
|
36
|
+
validate_assignment=True,
|
|
37
|
+
protected_namespaces=(),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def to_str(self) -> str:
|
|
41
|
+
"""Returns the string representation of the model using alias"""
|
|
42
|
+
return pprint.pformat(self.model_dump(by_alias=True))
|
|
43
|
+
|
|
44
|
+
def to_json(self) -> str:
|
|
45
|
+
"""Returns the JSON representation of the model using alias"""
|
|
46
|
+
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
|
|
47
|
+
return json.dumps(self.to_dict())
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_json(cls, json_str: str) -> Optional[Self]:
|
|
51
|
+
"""Create an instance of V1WebhookReceive200Response from a JSON string"""
|
|
52
|
+
return cls.from_dict(json.loads(json_str))
|
|
53
|
+
|
|
54
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
55
|
+
"""Return the dictionary representation of the model using alias.
|
|
56
|
+
|
|
57
|
+
This has the following differences from calling pydantic's
|
|
58
|
+
`self.model_dump(by_alias=True)`:
|
|
59
|
+
|
|
60
|
+
* `None` is only added to the output dict for nullable fields that
|
|
61
|
+
were set at model initialization. Other fields with value `None`
|
|
62
|
+
are ignored.
|
|
63
|
+
"""
|
|
64
|
+
excluded_fields: Set[str] = set([])
|
|
65
|
+
|
|
66
|
+
_dict = self.model_dump(
|
|
67
|
+
by_alias=True,
|
|
68
|
+
exclude=excluded_fields,
|
|
69
|
+
exclude_none=True,
|
|
70
|
+
)
|
|
71
|
+
return _dict
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
|
|
75
|
+
"""Create an instance of V1WebhookReceive200Response from a dict"""
|
|
76
|
+
if obj is None:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
if not isinstance(obj, dict):
|
|
80
|
+
return cls.model_validate(obj)
|
|
81
|
+
|
|
82
|
+
_obj = cls.model_validate({"message": obj.get("message")})
|
|
83
|
+
return _obj
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Hatchet API
|
|
5
|
+
|
|
6
|
+
The Hatchet API
|
|
7
|
+
|
|
8
|
+
The version of the OpenAPI document: 1.0.0
|
|
9
|
+
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
10
|
+
|
|
11
|
+
Do not edit the class manually.
|
|
12
|
+
""" # noqa: E501
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
from enum import Enum
|
|
19
|
+
|
|
20
|
+
from typing_extensions import Self
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class V1WebhookSourceName(str, Enum):
|
|
24
|
+
"""
|
|
25
|
+
V1WebhookSourceName
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
allowed enum values
|
|
30
|
+
"""
|
|
31
|
+
GENERIC = "GENERIC"
|
|
32
|
+
GITHUB = "GITHUB"
|
|
33
|
+
STRIPE = "STRIPE"
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_json(cls, json_str: str) -> Self:
|
|
37
|
+
"""Create an instance of V1WebhookSourceName from a JSON string"""
|
|
38
|
+
return cls(json.loads(json_str))
|
hatchet_sdk/context/context.py
CHANGED
|
@@ -21,10 +21,11 @@ from hatchet_sdk.conditions import (
|
|
|
21
21
|
flatten_conditions,
|
|
22
22
|
)
|
|
23
23
|
from hatchet_sdk.context.worker_context import WorkerContext
|
|
24
|
+
from hatchet_sdk.exceptions import TaskRunError
|
|
24
25
|
from hatchet_sdk.features.runs import RunsClient
|
|
25
26
|
from hatchet_sdk.logger import logger
|
|
26
27
|
from hatchet_sdk.utils.timedelta_to_expression import Duration, timedelta_to_expr
|
|
27
|
-
from hatchet_sdk.utils.typing import JSONSerializableMapping
|
|
28
|
+
from hatchet_sdk.utils.typing import JSONSerializableMapping, LogLevel
|
|
28
29
|
from hatchet_sdk.worker.runner.utils.capture_logs import AsyncLogSender, LogRecord
|
|
29
30
|
|
|
30
31
|
if TYPE_CHECKING:
|
|
@@ -211,7 +212,9 @@ class Context:
|
|
|
211
212
|
line = str(line)
|
|
212
213
|
|
|
213
214
|
logger.info(line)
|
|
214
|
-
self.log_sender.publish(
|
|
215
|
+
self.log_sender.publish(
|
|
216
|
+
LogRecord(message=line, step_run_id=self.step_run_id, level=LogLevel.INFO)
|
|
217
|
+
)
|
|
215
218
|
|
|
216
219
|
def release_slot(self) -> None:
|
|
217
220
|
"""
|
|
@@ -360,15 +363,41 @@ class Context:
|
|
|
360
363
|
task: "Task[TWorkflowInput, R]",
|
|
361
364
|
) -> str | None:
|
|
362
365
|
"""
|
|
366
|
+
**DEPRECATED**: Use `get_task_run_error` instead.
|
|
367
|
+
|
|
363
368
|
A helper intended to be used in an on-failure step to retrieve the error that occurred in a specific upstream task run.
|
|
364
369
|
|
|
365
370
|
:param task: The task whose error you want to retrieve.
|
|
366
371
|
:return: The error message of the task run, or None if no error occurred.
|
|
367
372
|
"""
|
|
373
|
+
warn(
|
|
374
|
+
"`fetch_task_run_error` is deprecated. Use `get_task_run_error` instead.",
|
|
375
|
+
DeprecationWarning,
|
|
376
|
+
stacklevel=2,
|
|
377
|
+
)
|
|
368
378
|
errors = self.data.step_run_errors
|
|
369
379
|
|
|
370
380
|
return errors.get(task.name)
|
|
371
381
|
|
|
382
|
+
def get_task_run_error(
|
|
383
|
+
self,
|
|
384
|
+
task: "Task[TWorkflowInput, R]",
|
|
385
|
+
) -> TaskRunError | None:
|
|
386
|
+
"""
|
|
387
|
+
A helper intended to be used in an on-failure step to retrieve the error that occurred in a specific upstream task run.
|
|
388
|
+
|
|
389
|
+
:param task: The task whose error you want to retrieve.
|
|
390
|
+
:return: The error message of the task run, or None if no error occurred.
|
|
391
|
+
"""
|
|
392
|
+
errors = self.data.step_run_errors
|
|
393
|
+
|
|
394
|
+
error = errors.get(task.name)
|
|
395
|
+
|
|
396
|
+
if not error:
|
|
397
|
+
return None
|
|
398
|
+
|
|
399
|
+
return TaskRunError.deserialize(error)
|
|
400
|
+
|
|
372
401
|
|
|
373
402
|
class DurableContext(Context):
|
|
374
403
|
def __init__(
|
hatchet_sdk/exceptions.py
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import traceback
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InvalidDependencyError(Exception):
|
|
7
|
+
pass
|
|
2
8
|
|
|
3
9
|
|
|
4
10
|
class NonRetryableException(Exception): # noqa: N818
|
|
@@ -9,28 +15,42 @@ class DedupeViolationError(Exception):
|
|
|
9
15
|
"""Raised by the Hatchet library to indicate that a workflow has already been run with this deduplication value."""
|
|
10
16
|
|
|
11
17
|
|
|
18
|
+
TASK_RUN_ERROR_METADATA_KEY = "__hatchet_error_metadata__"
|
|
19
|
+
|
|
20
|
+
|
|
12
21
|
class TaskRunError(Exception):
|
|
13
22
|
def __init__(
|
|
14
23
|
self,
|
|
15
24
|
exc: str,
|
|
16
25
|
exc_type: str,
|
|
17
26
|
trace: str,
|
|
27
|
+
task_run_external_id: str | None,
|
|
18
28
|
) -> None:
|
|
19
29
|
self.exc = exc
|
|
20
30
|
self.exc_type = exc_type
|
|
21
31
|
self.trace = trace
|
|
32
|
+
self.task_run_external_id = task_run_external_id
|
|
22
33
|
|
|
23
34
|
def __str__(self) -> str:
|
|
24
|
-
return self.serialize()
|
|
35
|
+
return self.serialize(include_metadata=False)
|
|
25
36
|
|
|
26
37
|
def __repr__(self) -> str:
|
|
27
38
|
return str(self)
|
|
28
39
|
|
|
29
|
-
def serialize(self) -> str:
|
|
40
|
+
def serialize(self, include_metadata: bool) -> str:
|
|
30
41
|
if not self.exc_type or not self.exc:
|
|
31
42
|
return ""
|
|
32
43
|
|
|
33
|
-
|
|
44
|
+
metadata = json.dumps(
|
|
45
|
+
{
|
|
46
|
+
TASK_RUN_ERROR_METADATA_KEY: {
|
|
47
|
+
"task_run_external_id": self.task_run_external_id,
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
indent=None,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
result = (
|
|
34
54
|
self.exc_type.replace(": ", ":::")
|
|
35
55
|
+ ": "
|
|
36
56
|
+ self.exc.replace("\n", "\\\n")
|
|
@@ -38,6 +58,40 @@ class TaskRunError(Exception):
|
|
|
38
58
|
+ self.trace
|
|
39
59
|
)
|
|
40
60
|
|
|
61
|
+
if include_metadata:
|
|
62
|
+
return result + "\n\n" + metadata
|
|
63
|
+
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def _extract_metadata(cls, serialized: str) -> tuple[str, dict[str, str | None]]:
|
|
68
|
+
metadata = serialized.split("\n")[-1]
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
parsed = json.loads(metadata)
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
TASK_RUN_ERROR_METADATA_KEY in parsed
|
|
75
|
+
and "task_run_external_id" in parsed[TASK_RUN_ERROR_METADATA_KEY]
|
|
76
|
+
):
|
|
77
|
+
serialized = serialized.replace(metadata, "").strip()
|
|
78
|
+
return serialized, cast(
|
|
79
|
+
dict[str, str | None], parsed[TASK_RUN_ERROR_METADATA_KEY]
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return serialized, {}
|
|
83
|
+
except json.JSONDecodeError:
|
|
84
|
+
return serialized, {}
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def _unpack_serialized_error(cls, serialized: str) -> tuple[str | None, str, str]:
|
|
88
|
+
serialized, metadata = cls._extract_metadata(serialized)
|
|
89
|
+
|
|
90
|
+
external_id = metadata.get("task_run_external_id", None)
|
|
91
|
+
header, trace = serialized.split("\n", 1)
|
|
92
|
+
|
|
93
|
+
return external_id, header, trace
|
|
94
|
+
|
|
41
95
|
@classmethod
|
|
42
96
|
def deserialize(cls, serialized: str) -> "TaskRunError":
|
|
43
97
|
if not serialized:
|
|
@@ -45,10 +99,16 @@ class TaskRunError(Exception):
|
|
|
45
99
|
exc="",
|
|
46
100
|
exc_type="",
|
|
47
101
|
trace="",
|
|
102
|
+
task_run_external_id=None,
|
|
48
103
|
)
|
|
49
104
|
|
|
105
|
+
task_run_external_id = None
|
|
106
|
+
|
|
50
107
|
try:
|
|
51
|
-
header, trace =
|
|
108
|
+
task_run_external_id, header, trace = cls._unpack_serialized_error(
|
|
109
|
+
serialized
|
|
110
|
+
)
|
|
111
|
+
|
|
52
112
|
exc_type, exc = header.split(": ", 1)
|
|
53
113
|
except ValueError:
|
|
54
114
|
## If we get here, we saw an error that was not serialized how we expected,
|
|
@@ -57,6 +117,7 @@ class TaskRunError(Exception):
|
|
|
57
117
|
exc=serialized,
|
|
58
118
|
exc_type="HatchetError",
|
|
59
119
|
trace="",
|
|
120
|
+
task_run_external_id=task_run_external_id,
|
|
60
121
|
)
|
|
61
122
|
|
|
62
123
|
exc_type = exc_type.replace(":::", ": ")
|
|
@@ -66,16 +127,20 @@ class TaskRunError(Exception):
|
|
|
66
127
|
exc=exc,
|
|
67
128
|
exc_type=exc_type,
|
|
68
129
|
trace=trace,
|
|
130
|
+
task_run_external_id=task_run_external_id,
|
|
69
131
|
)
|
|
70
132
|
|
|
71
133
|
@classmethod
|
|
72
|
-
def from_exception(
|
|
134
|
+
def from_exception(
|
|
135
|
+
cls, exc: Exception, task_run_external_id: str | None
|
|
136
|
+
) -> "TaskRunError":
|
|
73
137
|
return cls(
|
|
74
138
|
exc=str(exc),
|
|
75
139
|
exc_type=type(exc).__name__,
|
|
76
140
|
trace="".join(
|
|
77
141
|
traceback.format_exception(type(exc), exc, exc.__traceback__)
|
|
78
142
|
),
|
|
143
|
+
task_run_external_id=task_run_external_id,
|
|
79
144
|
)
|
|
80
145
|
|
|
81
146
|
|