horsies 0.1.0a2__py3-none-any.whl → 0.1.0a3__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.
horsies/__init__.py CHANGED
@@ -14,6 +14,7 @@ from .core.models.tasks import (
14
14
  LibraryErrorCode,
15
15
  SubWorkflowError,
16
16
  RetryPolicy,
17
+ TaskInfo,
17
18
  )
18
19
  from .core.models.queues import QueueMode, CustomQueueConfig
19
20
  from .core.models.workflow import (
@@ -67,6 +68,7 @@ __all__ = [
67
68
  'LibraryErrorCode',
68
69
  'SubWorkflowError',
69
70
  'RetryPolicy',
71
+ 'TaskInfo',
70
72
  'QueueMode',
71
73
  'CustomQueueConfig',
72
74
  'TaskStatus',
@@ -16,6 +16,7 @@ from horsies.core.codec.serde import (
16
16
  loads_json,
17
17
  task_result_from_json,
18
18
  )
19
+ from horsies.core.models.tasks import TaskInfo
19
20
  from horsies.core.utils.loop_runner import LoopRunner
20
21
  from horsies.core.logging import get_logger
21
22
 
@@ -854,6 +855,134 @@ class PostgresBroker:
854
855
  """
855
856
  return self._loop_runner.call(self.get_result_async, task_id, timeout_ms)
856
857
 
858
+ async def get_task_info_async(
859
+ self,
860
+ task_id: str,
861
+ *,
862
+ include_result: bool = False,
863
+ include_failed_reason: bool = False,
864
+ ) -> 'TaskInfo | None':
865
+ """Fetch metadata for a task by ID."""
866
+ await self._ensure_initialized()
867
+
868
+ async with self.session_factory() as session:
869
+ base_columns = [
870
+ 'id',
871
+ 'task_name',
872
+ 'status',
873
+ 'queue_name',
874
+ 'priority',
875
+ 'retry_count',
876
+ 'max_retries',
877
+ 'next_retry_at',
878
+ 'sent_at',
879
+ 'claimed_at',
880
+ 'started_at',
881
+ 'completed_at',
882
+ 'failed_at',
883
+ 'worker_hostname',
884
+ 'worker_pid',
885
+ 'worker_process_name',
886
+ ]
887
+ if include_result:
888
+ base_columns.append('result')
889
+ if include_failed_reason:
890
+ base_columns.append('failed_reason')
891
+
892
+ query = text(
893
+ f"""
894
+ SELECT {', '.join(base_columns)}
895
+ FROM horsies_tasks
896
+ WHERE id = :id
897
+ """
898
+ )
899
+ result = await session.execute(query, {'id': task_id})
900
+ row = result.fetchone()
901
+ if row is None:
902
+ return None
903
+
904
+ result_value = None
905
+ failed_reason = None
906
+
907
+ idx = 0
908
+ task_id_value = row[idx]
909
+ idx += 1
910
+ task_name = row[idx]
911
+ idx += 1
912
+ status = TaskStatus(row[idx])
913
+ idx += 1
914
+ queue_name = row[idx]
915
+ idx += 1
916
+ priority = row[idx]
917
+ idx += 1
918
+ retry_count = row[idx] or 0
919
+ idx += 1
920
+ max_retries = row[idx] or 0
921
+ idx += 1
922
+ next_retry_at = row[idx]
923
+ idx += 1
924
+ sent_at = row[idx]
925
+ idx += 1
926
+ claimed_at = row[idx]
927
+ idx += 1
928
+ started_at = row[idx]
929
+ idx += 1
930
+ completed_at = row[idx]
931
+ idx += 1
932
+ failed_at = row[idx]
933
+ idx += 1
934
+ worker_hostname = row[idx]
935
+ idx += 1
936
+ worker_pid = row[idx]
937
+ idx += 1
938
+ worker_process_name = row[idx]
939
+ idx += 1
940
+
941
+ if include_result:
942
+ raw_result = row[idx]
943
+ idx += 1
944
+ if raw_result:
945
+ result_value = task_result_from_json(loads_json(raw_result))
946
+
947
+ if include_failed_reason:
948
+ failed_reason = row[idx]
949
+
950
+ return TaskInfo(
951
+ task_id=task_id_value,
952
+ task_name=task_name,
953
+ status=status,
954
+ queue_name=queue_name,
955
+ priority=priority,
956
+ retry_count=retry_count,
957
+ max_retries=max_retries,
958
+ next_retry_at=next_retry_at,
959
+ sent_at=sent_at,
960
+ claimed_at=claimed_at,
961
+ started_at=started_at,
962
+ completed_at=completed_at,
963
+ failed_at=failed_at,
964
+ worker_hostname=worker_hostname,
965
+ worker_pid=worker_pid,
966
+ worker_process_name=worker_process_name,
967
+ result=result_value,
968
+ failed_reason=failed_reason,
969
+ )
970
+
971
+ def get_task_info(
972
+ self,
973
+ task_id: str,
974
+ *,
975
+ include_result: bool = False,
976
+ include_failed_reason: bool = False,
977
+ ) -> 'TaskInfo | None':
978
+ """Synchronous wrapper for get_task_info_async()."""
979
+ return self._loop_runner.call(
980
+ self.get_task_info_async,
981
+ task_id,
982
+ include_result=include_result,
983
+ include_failed_reason=include_failed_reason,
984
+ )
985
+
857
986
  def close(self) -> None:
858
987
  """
859
988
  Synchronous cleanup (runs close_async in background loop).
@@ -1,6 +1,7 @@
1
1
  # app/core/models/tasks.py
2
2
  from __future__ import annotations
3
3
  import datetime
4
+ from dataclasses import dataclass
4
5
  from typing import (
5
6
  TYPE_CHECKING,
6
7
  Any,
@@ -20,6 +21,7 @@ from enum import Enum
20
21
  if TYPE_CHECKING:
21
22
  from horsies.core.models.workflow import SubWorkflowSummary
22
23
 
24
+ from horsies.core.types.status import TaskStatus
23
25
  T = TypeVar('T') # success payload
24
26
  E = TypeVar('E') # error payload (TaskError )
25
27
 
@@ -219,6 +221,30 @@ class TaskResult(Generic[T, E]):
219
221
  raise ValueError('Result is not error - check is_err() first')
220
222
 
221
223
 
224
+ @dataclass
225
+ class TaskInfo:
226
+ """Metadata for a broker-backed task."""
227
+
228
+ task_id: str
229
+ task_name: str
230
+ status: TaskStatus
231
+ queue_name: str
232
+ priority: int
233
+ retry_count: int
234
+ max_retries: int
235
+ next_retry_at: datetime.datetime | None
236
+ sent_at: datetime.datetime | None
237
+ claimed_at: datetime.datetime | None
238
+ started_at: datetime.datetime | None
239
+ completed_at: datetime.datetime | None
240
+ failed_at: datetime.datetime | None
241
+ worker_hostname: str | None
242
+ worker_pid: int | None
243
+ worker_process_name: str | None
244
+ result: TaskResult[Any, TaskError] | None = None
245
+ failed_reason: str | None = None
246
+
247
+
222
248
  class RetryPolicy(BaseModel):
223
249
  """
224
250
  Retry policy configuration for tasks.
@@ -1374,6 +1374,7 @@ class WorkflowContext(BaseModel):
1374
1374
  class WorkflowTaskInfo:
1375
1375
  """Information about a task within a workflow."""
1376
1376
 
1377
+ node_id: str | None
1377
1378
  index: int
1378
1379
  name: str
1379
1380
  status: WorkflowTaskStatus
@@ -1700,7 +1701,7 @@ class WorkflowHandle:
1700
1701
  async with self.broker.session_factory() as session:
1701
1702
  result = await session.execute(
1702
1703
  text("""
1703
- SELECT task_index, task_name, status, result, started_at, completed_at
1704
+ SELECT node_id, task_index, task_name, status, result, started_at, completed_at
1704
1705
  FROM horsies_workflow_tasks
1705
1706
  WHERE workflow_id = :wf_id
1706
1707
  ORDER BY task_index
@@ -1710,14 +1711,15 @@ class WorkflowHandle:
1710
1711
 
1711
1712
  return [
1712
1713
  WorkflowTaskInfo(
1713
- index=row[0],
1714
- name=row[1],
1715
- status=WorkflowTaskStatus(row[2]),
1716
- result=task_result_from_json(loads_json(row[3]))
1717
- if row[3]
1714
+ node_id=row[0],
1715
+ index=row[1],
1716
+ name=row[2],
1717
+ status=WorkflowTaskStatus(row[3]),
1718
+ result=task_result_from_json(loads_json(row[4]))
1719
+ if row[4]
1718
1720
  else None,
1719
- started_at=row[4],
1720
- completed_at=row[5],
1721
+ started_at=row[5],
1722
+ completed_at=row[6],
1721
1723
  )
1722
1724
  for row in result.fetchall()
1723
1725
  ]
@@ -23,6 +23,7 @@ if TYPE_CHECKING:
23
23
  from horsies.core.app import Horsies
24
24
  from horsies.core.models.tasks import TaskOptions
25
25
  from horsies.core.models.tasks import TaskError, TaskResult
26
+ from horsies.core.models.tasks import TaskInfo
26
27
 
27
28
  from horsies.core.models.tasks import TaskResult, TaskError, LibraryErrorCode
28
29
  from horsies.core.models.workflow import WorkflowContextMissingIdError
@@ -191,6 +192,46 @@ class TaskHandle(Generic[T]):
191
192
  case result:
192
193
  return result
193
194
 
195
+ def info(
196
+ self,
197
+ *,
198
+ include_result: bool = False,
199
+ include_failed_reason: bool = False,
200
+ ) -> 'TaskInfo | None':
201
+ """Fetch metadata for this task from the broker."""
202
+ if not self._broker_mode or not self._app:
203
+ raise RuntimeError(
204
+ 'TaskHandle.info() requires a broker-backed task handle '
205
+ '(use .send() or .send_async())'
206
+ )
207
+
208
+ broker = self._app.get_broker()
209
+ return broker.get_task_info(
210
+ self.task_id,
211
+ include_result=include_result,
212
+ include_failed_reason=include_failed_reason,
213
+ )
214
+
215
+ async def info_async(
216
+ self,
217
+ *,
218
+ include_result: bool = False,
219
+ include_failed_reason: bool = False,
220
+ ) -> 'TaskInfo | None':
221
+ """Async version of info()."""
222
+ if not self._broker_mode or not self._app:
223
+ raise RuntimeError(
224
+ 'TaskHandle.info_async() requires a broker-backed task handle '
225
+ '(use .send() or .send_async())'
226
+ )
227
+
228
+ broker = self._app.get_broker()
229
+ return await broker.get_task_info_async(
230
+ self.task_id,
231
+ include_result=include_result,
232
+ include_failed_reason=include_failed_reason,
233
+ )
234
+
194
235
  def set_immediate_result(
195
236
  self,
196
237
  result: TaskResult[T, TaskError],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: horsies
3
- Version: 0.1.0a2
3
+ Version: 0.1.0a3
4
4
  Summary: A Python library for distributed task execution
5
5
  Author: Suleyman Ozkeskin
6
6
  License-Expression: MIT
@@ -1,4 +1,4 @@
1
- horsies/__init__.py,sha256=Fr_4G82B68cs3p7wShUum2fQlmey1hBf4pAxDz7M6D0,2739
1
+ horsies/__init__.py,sha256=rLxes5IuyzggohtXKRsHoddeyqEy0LsgwDlk09itKXk,2769
2
2
  horsies/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  horsies/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  horsies/core/app.py,sha256=sRLVto5oFH0Mb3cwrzJXd2WoPEjoYL2ByJc764DW238,21523
@@ -6,10 +6,10 @@ horsies/core/banner.py,sha256=eCp8crZZWS6JjWoTn6RtCTEpvGmkpr7hWke_Av5-bvI,5716
6
6
  horsies/core/cli.py,sha256=XPMRRvZqNg3ph96gWCfo0NJDOsqqe0Cuj_8Wx80sQP0,21057
7
7
  horsies/core/errors.py,sha256=M0PMSdsqbOMPOsZsEOmfKTvxdAD5E7BZ0IuDus7HWtk,16640
8
8
  horsies/core/logging.py,sha256=p2utHDeOHgvwtSzKTKXh0RwUNCk38ODmgQQkmsD1tWA,2934
9
- horsies/core/task_decorator.py,sha256=kn1xD76dqF4mjXAut5GWUHwHZ4JIBsavNvW1-8Fr-OA,22449
9
+ horsies/core/task_decorator.py,sha256=mqQYV8iLt9TwsrNQDDsBx3Vo8T5HeLFv6iiWbhF3l-c,23799
10
10
  horsies/core/brokers/__init__.py,sha256=a_5xkHhRKY-1PRq6UidMBGq1bX-zeuSdxIvI6GdSiSQ,94
11
11
  horsies/core/brokers/listener.py,sha256=pxnnAgLAWqODPV_J0XwUqAhBSrHstL77PSHYEfGoVhc,18637
12
- horsies/core/brokers/postgres.py,sha256=n_WjM7-Fbg9R5Vao9RJgDSy7eBIL1_pVvnKrjMayzd4,35134
12
+ horsies/core/brokers/postgres.py,sha256=YZSJiQY1Spc2pNF8fjPy_0K9EAD9WAxVnFaV2P6Qf5Q,39073
13
13
  horsies/core/codec/serde.py,sha256=-dUEwyE4-DcAnP2CTbJYxBW3rOzXIUMsX_OSZSjNnpU,20043
14
14
  horsies/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  horsies/core/models/app.py,sha256=I3HKztjNzii75bDwlbB7Jl9CPrFMfjLInUJRacQ39_s,11863
@@ -18,8 +18,8 @@ horsies/core/models/queues.py,sha256=brB8rk_PQZds_VgavpZH3fkgqzsBAvRTySNNAtzikSw
18
18
  horsies/core/models/recovery.py,sha256=sI9h7Kbx_Jv23-J_0fTZRrrMgwNqmkX0-dWKTnpikvA,4964
19
19
  horsies/core/models/schedule.py,sha256=5mCMsX-12OPz6Gs-nr_GtaJgjxDzuyjuhSTil2XIq50,8113
20
20
  horsies/core/models/task_pg.py,sha256=v5U1huJwASh2Saf8GDaRX-IM4mXcD8Fu43kfrFSFDLg,12304
21
- horsies/core/models/tasks.py,sha256=NBGBw80XniOG3_r1nOB4SE_-N83h8WHQ7FH0tPlr41M,10863
22
- horsies/core/models/workflow.py,sha256=KZE7l3cjePpTyKqjTOq5erKyptcAia08ai4v59LVtNo,71688
21
+ horsies/core/models/tasks.py,sha256=nQb5z8S-GioO2aZATEvfubzqKtsNBGu-RcCn9kdituw,11592
22
+ horsies/core/models/workflow.py,sha256=_93sQ-He9gbHJ4zOpmjlQ0pcRUPWGEXqzwKUNmN9gRc,71757
23
23
  horsies/core/models/workflow_pg.py,sha256=Nd6JDy4Htft9qQ4xKW4U2vhKNe-uUMYYZfg14t-yJnk,9315
24
24
  horsies/core/registry/tasks.py,sha256=mm1xb-f2HLUcZJrLgx2ZS-FQtCkuJbfsR-SW2qiMsts,3708
25
25
  horsies/core/scheduler/__init__.py,sha256=m0GqCrdTbQNDUV1Fn3UZD5IewAYsV5olMrDRolg3a1I,699
@@ -35,8 +35,8 @@ horsies/core/workflows/__init__.py,sha256=JA-8wcYUHp-wF5OCEqJwKElT8PaZZB1G7iglDZ
35
35
  horsies/core/workflows/engine.py,sha256=8Gjmch52g1IObKqzGVMWxZaZDC0diB6TuAtRfqr67l0,86927
36
36
  horsies/core/workflows/recovery.py,sha256=xRKQPiYaLcX4vLuvcNGy5cV14eW4Cp_sewD1nXv8gc8,19445
37
37
  horsies/core/workflows/registry.py,sha256=ItpjTN8yK9NNSV8Q8OwDnHdQSO-hxDzxeWAyGvExpRk,3194
38
- horsies-0.1.0a2.dist-info/METADATA,sha256=XJ8Gw2iRCTlKSRkhU3mri_3Q4yuTxRA77lTMmqBJpYs,1321
39
- horsies-0.1.0a2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
40
- horsies-0.1.0a2.dist-info/entry_points.txt,sha256=6b_OhuNbJ1ky9WSOt3UfNQXETG2YuH0lKimcibILrEE,50
41
- horsies-0.1.0a2.dist-info/top_level.txt,sha256=ZQ_NurgT-yr3pE4a1svlO_VAhGJ6NFA31vJDLT1yyOA,8
42
- horsies-0.1.0a2.dist-info/RECORD,,
38
+ horsies-0.1.0a3.dist-info/METADATA,sha256=O-mV7PXCaiEBJfxNVh3sbYZ3qnfWzUU3W6aPGgb_55c,1321
39
+ horsies-0.1.0a3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
40
+ horsies-0.1.0a3.dist-info/entry_points.txt,sha256=6b_OhuNbJ1ky9WSOt3UfNQXETG2YuH0lKimcibILrEE,50
41
+ horsies-0.1.0a3.dist-info/top_level.txt,sha256=ZQ_NurgT-yr3pE4a1svlO_VAhGJ6NFA31vJDLT1yyOA,8
42
+ horsies-0.1.0a3.dist-info/RECORD,,