datashare-python 0.10.1__tar.gz → 0.10.5__tar.gz

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.
Files changed (29) hide show
  1. {datashare_python-0.10.1 → datashare_python-0.10.5}/PKG-INFO +2 -2
  2. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/interceptors.py +48 -5
  3. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/objects.py +11 -1
  4. datashare_python-0.10.5/datashare_python/worker-template.tar.gz +0 -0
  5. {datashare_python-0.10.1 → datashare_python-0.10.5}/pyproject.toml +1 -1
  6. datashare_python-0.10.1/datashare_python/worker-template.tar.gz +0 -0
  7. {datashare_python-0.10.1 → datashare_python-0.10.5}/.gitignore +0 -0
  8. {datashare_python-0.10.1 → datashare_python-0.10.5}/README.md +0 -0
  9. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/.gitignore +0 -0
  10. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/__init__.py +0 -0
  11. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/__main__.py +0 -0
  12. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/cli/__init__.py +0 -0
  13. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/cli/project.py +0 -0
  14. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/cli/task.py +0 -0
  15. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/cli/utils.py +0 -0
  16. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/cli/worker.py +0 -0
  17. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/config.py +0 -0
  18. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/conftest.py +0 -0
  19. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/constants.py +0 -0
  20. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/dependencies.py +0 -0
  21. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/discovery.py +0 -0
  22. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/exceptions.py +0 -0
  23. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/logging_.py +0 -0
  24. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/mimetypes_.py +0 -0
  25. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/task_client.py +0 -0
  26. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/template.py +0 -0
  27. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/types_.py +0 -0
  28. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/utils.py +0 -0
  29. {datashare_python-0.10.1 → datashare_python-0.10.5}/datashare_python/worker.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: datashare-python
3
- Version: 0.10.1
3
+ Version: 0.10.5
4
4
  Summary: Manage Python tasks and local resources in Datashare
5
5
  Project-URL: Homepage, https://icij.github.io/datashare-python/
6
6
  Project-URL: Documentation, https://icij.github.io/datashare-python/
@@ -1,18 +1,20 @@
1
1
  import asyncio
2
2
  import dataclasses
3
3
  import datetime
4
+ import logging
4
5
  import secrets
5
6
  from collections.abc import Callable, Generator, Mapping
6
7
  from contextlib import contextmanager
7
8
  from contextvars import ContextVar
8
9
  from copy import deepcopy
9
10
  from functools import wraps
10
- from inspect import signature
11
+ from inspect import iscoroutinefunction, signature
11
12
  from types import UnionType
12
13
  from typing import (
13
14
  Annotated,
14
15
  Any,
15
16
  NoReturn,
17
+ ParamSpec,
16
18
  Self,
17
19
  TypeVar,
18
20
  get_args,
@@ -27,6 +29,7 @@ from temporalio.activity import _Definition
27
29
  from temporalio.api.common.v1 import Payload
28
30
  from temporalio.client import WorkflowHandle
29
31
  from temporalio.converter import DataConverter
32
+ from temporalio.service import RPCError
30
33
  from temporalio.worker import (
31
34
  ActivityInboundInterceptor,
32
35
  ActivityOutboundInterceptor,
@@ -66,6 +69,8 @@ from .utils import (
66
69
  ProgressSignal,
67
70
  )
68
71
 
72
+ logger = logging.getLogger(__name__)
73
+
69
74
  _TRACEPARENT = "traceparent"
70
75
  _DEFAULT_PAYLOAD_CONVERTER = DataConverter.default.payload_converter
71
76
  _PROGRESS_TYPES = {
@@ -190,6 +195,8 @@ class _TraceContextWorkflowOutboundInterceptor(WorkflowOutboundInterceptor):
190
195
 
191
196
  class _TraceContextActivityInboundInterceptor(ActivityInboundInterceptor):
192
197
  async def execute_activity(self, input: ExecuteActivityInput) -> Any: # noqa: A002
198
+ info = activity.info()
199
+ logger.info("start executing activity: %s", info.activity_id)
193
200
  with _trace_context(input.headers):
194
201
  return await super().execute_activity(input)
195
202
 
@@ -327,7 +334,8 @@ def _get_progress_handler(
327
334
  weight=weight,
328
335
  min_progress_interval_s=min_progress_interval_s,
329
336
  )
330
- return handler.progress
337
+ handler = _fail_safe(handler.progress, "emit progress", excs=(RPCError,))
338
+ return handler
331
339
 
332
340
 
333
341
  def _is_progress(t: type) -> bool:
@@ -431,9 +439,10 @@ class _HeartbeatInboundInterceptor(ActivityInboundInterceptor):
431
439
  heartbeat_task = None
432
440
  if heartbeat_timeout:
433
441
  period = heartbeat_timeout.total_seconds() / self._n_missed_before_timeout
434
- heartbeat_task = asyncio.create_task(
435
- _heartbeat_every(self._outbound.heartbeat, period)
436
- )
442
+ # We don't want a failing hearbeat to fail the worker task so we just
443
+ # ignore any exception
444
+ heartbeat_fn = _fail_safe(self._outbound.heartbeat, "heartbeat")
445
+ heartbeat_task = asyncio.create_task(_heartbeat_every(heartbeat_fn, period))
437
446
  try:
438
447
  activity.heartbeat()
439
448
  return await super().execute_activity(input)
@@ -443,6 +452,40 @@ class _HeartbeatInboundInterceptor(ActivityInboundInterceptor):
443
452
  await asyncio.wait([heartbeat_task])
444
453
 
445
454
 
455
+ P = ParamSpec("P")
456
+ T = TypeVar("T")
457
+
458
+
459
+ def _fail_safe[P, T](
460
+ fn: Callable[P, T],
461
+ action: str,
462
+ excs: tuple[type[Exception]] | None = None,
463
+ ) -> Callable[P, T]:
464
+ if excs is None:
465
+ excs = (Exception,)
466
+ if iscoroutinefunction(fn):
467
+
468
+ @wraps(fn)
469
+ async def wrapper(*args, **kwargs) -> None:
470
+ try:
471
+ await fn(*args, **kwargs)
472
+ except excs as exc:
473
+ msg = f"failed to {action} due to {exc}"
474
+ logger.exception(msg)
475
+
476
+ else:
477
+
478
+ @wraps(fn)
479
+ def wrapper(*args, **kwargs) -> None:
480
+ try:
481
+ fn(*args, **kwargs)
482
+ except excs as exc:
483
+ msg = f"failed to {action} due to {exc}"
484
+ logger.exception(msg)
485
+
486
+ return wrapper
487
+
488
+
446
489
  def _sync_progress(
447
490
  progress_handler: AsyncProgressRateHandler,
448
491
  ) -> SyncProgressRateHandler:
@@ -14,7 +14,7 @@ from icij_common.registrable import Registrable
14
14
  from pydantic_core import PydanticCustomError, ValidationError, core_schema
15
15
  from pydantic_core.core_schema import PlainValidatorFunctionSchema
16
16
  from pydantic_extra_types.language_code import LanguageName
17
- from temporalio import workflow
17
+ from temporalio import activity, workflow
18
18
 
19
19
  from .constants import TIKA_METADATA_RESOURCENAME
20
20
 
@@ -325,6 +325,8 @@ A = TypeVar("A", bound=TaskArgs)
325
325
 
326
326
  class ManifestEntry[A](DatashareModel, ABC):
327
327
  status: ManifestEntryStatus
328
+ # TODO: make this one non optional in the next major !
329
+ task_id: str | None
328
330
  label: str | None = None
329
331
  input: Annotated[
330
332
  dict[str, Any] | None,
@@ -336,7 +338,11 @@ class ManifestEntry[A](DatashareModel, ABC):
336
338
 
337
339
  @classmethod
338
340
  def complete(cls, args: A, label: str | None = None, **kwargs) -> Self:
341
+ task_id = None
342
+ if activity.in_activity():
343
+ task_id = activity.info().workflow_id
339
344
  return cls(
345
+ task_id=task_id,
340
346
  input=args.as_manifest_task_input(),
341
347
  label=label,
342
348
  status=ManifestEntryStatus.COMPLETE,
@@ -345,7 +351,11 @@ class ManifestEntry[A](DatashareModel, ABC):
345
351
 
346
352
  @classmethod
347
353
  def partial(cls, args: A, label: str | None = None, **kwargs) -> Self:
354
+ task_id = None
355
+ if activity.in_activity():
356
+ task_id = activity.info().workflow_id
348
357
  return cls(
358
+ task_id=task_id,
349
359
  input=args.as_manifest_task_input(),
350
360
  label=label,
351
361
  status=ManifestEntryStatus.PARTIAL,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datashare-python"
3
- version = "0.10.1"
3
+ version = "0.10.5"
4
4
  description = "Manage Python tasks and local resources in Datashare"
5
5
  authors = [
6
6
  { name = "Clément Doumouro", email = "cdoumouro@icij.org" },