datashare-python 0.10.6__tar.gz → 0.10.8__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.6 → datashare_python-0.10.8}/PKG-INFO +1 -1
  2. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/objects.py +8 -0
  3. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/utils.py +50 -12
  4. datashare_python-0.10.8/datashare_python/worker-template.tar.gz +0 -0
  5. {datashare_python-0.10.6 → datashare_python-0.10.8}/pyproject.toml +1 -1
  6. datashare_python-0.10.6/datashare_python/worker-template.tar.gz +0 -0
  7. {datashare_python-0.10.6 → datashare_python-0.10.8}/.gitignore +0 -0
  8. {datashare_python-0.10.6 → datashare_python-0.10.8}/README.md +0 -0
  9. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/.gitignore +0 -0
  10. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/__init__.py +0 -0
  11. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/__main__.py +0 -0
  12. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/cli/__init__.py +0 -0
  13. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/cli/project.py +0 -0
  14. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/cli/task.py +0 -0
  15. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/cli/utils.py +0 -0
  16. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/cli/worker.py +0 -0
  17. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/config.py +0 -0
  18. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/conftest.py +0 -0
  19. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/constants.py +0 -0
  20. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/dependencies.py +0 -0
  21. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/discovery.py +0 -0
  22. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/exceptions.py +0 -0
  23. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/interceptors.py +0 -0
  24. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/logging_.py +0 -0
  25. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/mimetypes_.py +0 -0
  26. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/task_client.py +0 -0
  27. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/template.py +0 -0
  28. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/types_.py +0 -0
  29. {datashare_python-0.10.6 → datashare_python-0.10.8}/datashare_python/worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: datashare-python
3
- Version: 0.10.6
3
+ Version: 0.10.8
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,3 +1,5 @@
1
+ import hashlib
2
+ import json
1
3
  import logging
2
4
  import os
3
5
  from abc import ABC
@@ -63,6 +65,12 @@ Predicate = Callable[[T], bool] | Callable[[T], Awaitable[bool]]
63
65
  class BaseModel(_BaseModel):
64
66
  model_config = merge_configs(icij_config(), no_enum_values_config())
65
67
 
68
+ def __hash__(self) -> int:
69
+ digest = hashlib.md5(
70
+ json.dumps(self.model_dump(mode="json"), sort_keys=True).encode()
71
+ ).digest()
72
+ return int.from_bytes(digest[:8])
73
+
66
74
 
67
75
  class DatashareModel(BaseModel):
68
76
  model_config = merge_configs(BaseModel.model_config, lowercamel_case_config())
@@ -32,6 +32,7 @@ import temporalio
32
32
  from aiofile import async_open
33
33
  from lru import LRU
34
34
  from pydantic import ValidationError
35
+ from pydantic.alias_generators import to_snake
35
36
  from temporalio import activity, workflow
36
37
  from temporalio.api.common.v1 import Payload
37
38
  from temporalio.client import Client
@@ -62,6 +63,8 @@ from .objects import (
62
63
  )
63
64
  from .types_ import RawAsyncProgressHandler
64
65
 
66
+ _CACHE_DIR = "__cache__"
67
+
65
68
  logger = logging.getLogger(__name__)
66
69
 
67
70
  DependencyLabel = str | None
@@ -225,7 +228,7 @@ def _unpack_positional_args(
225
228
 
226
229
 
227
230
  def with_retriables[**P, T](
228
- retriables: set[type[Exception]] = None,
231
+ logger: logging.Logger, retriables: set[type[Exception]] = None
229
232
  ) -> Callable[[Callable[P, T]], Callable[P, T]]:
230
233
  if retriables is None:
231
234
 
@@ -237,6 +240,7 @@ def with_retriables[**P, T](
237
240
  try:
238
241
  return await activity_fn(*args, **kwargs)
239
242
  except Exception as e:
243
+ logger.exception("failing activity due to non retriable error")
240
244
  raise fatal_error_from_exception(e) from e
241
245
  else:
242
246
 
@@ -245,6 +249,7 @@ def with_retriables[**P, T](
245
249
  try:
246
250
  return activity_fn(*args, **kwargs)
247
251
  except Exception as e:
252
+ logger.exception("failing activity due to non retriable error")
248
253
  raise fatal_error_from_exception(e) from e
249
254
 
250
255
  return wrapper
@@ -259,8 +264,12 @@ def with_retriables[**P, T](
259
264
  try:
260
265
  return await activity_fn(*args, **kwargs)
261
266
  except retriables:
267
+ logger.exception(
268
+ "retriable error occurred during activity, will retry !"
269
+ )
262
270
  raise
263
271
  except Exception as e:
272
+ logger.exception("failing activity due to non retriable error")
264
273
  raise fatal_error_from_exception(e) from e
265
274
 
266
275
  else:
@@ -270,8 +279,12 @@ def with_retriables[**P, T](
270
279
  try:
271
280
  return activity_fn(*args, **kwargs)
272
281
  except retriables:
282
+ logger.exception(
283
+ "retriable error occurred during activity, will retry !"
284
+ )
273
285
  raise
274
286
  except Exception as e:
287
+ logger.exception("failing activity due to non retriable error")
275
288
  raise fatal_error_from_exception(e) from e
276
289
 
277
290
  return wrapper
@@ -280,12 +293,19 @@ def with_retriables[**P, T](
280
293
 
281
294
 
282
295
  def activity_defn[**P, T](
283
- name: str, retriables: set[type[Exception]] = None
296
+ name: str,
297
+ retriables: set[type[Exception]] = None,
298
+ logger: logging.Logger | None = None,
284
299
  ) -> Callable[[Callable[P, T]], Callable[P, T]]:
285
300
 
301
+ if logger is None:
302
+ frame = inspect.stack()[1]
303
+ caller_module = inspect.getmodule(frame[0])
304
+ logger = logging.getLogger(caller_module.__name__)
305
+
286
306
  def decorator(activity_fn: Callable[P, T]) -> Callable[P, T]:
287
307
  activity_fn = positional_args_only(activity_fn)
288
- activity_fn = with_retriables(retriables)(activity_fn)
308
+ activity_fn = with_retriables(logger, retriables)(activity_fn)
289
309
  activity_fn = activity.defn(activity_fn, name=name)
290
310
 
291
311
  is_async = inspect.iscoroutinefunction(activity_fn)
@@ -533,7 +553,11 @@ activity_contextual_id = contextual_id
533
553
 
534
554
 
535
555
  def _contextual_path(
536
- *, wf_context: bool = True, act_context: bool = True, run_context: bool = False
556
+ *,
557
+ wf_context: bool = True,
558
+ act_context: bool = True,
559
+ run_context: bool = False,
560
+ caching_key: str | None = None,
537
561
  ) -> Path:
538
562
  act_info = activity.info()
539
563
  path = []
@@ -541,14 +565,18 @@ def _contextual_path(
541
565
  raise ValueError("at least one of wf_context and act_context must be True")
542
566
  if wf_context:
543
567
  path = [act_info.workflow_type]
544
- path.append(act_info.workflow_id)
545
- if run_context:
546
- path.append(act_info.workflow_run_id)
568
+ if not caching_key:
569
+ path.append(act_info.workflow_id)
570
+ if run_context:
571
+ path.append(act_info.workflow_run_id)
547
572
  if act_context:
548
573
  path.append(act_info.activity_type)
549
- path.append(act_info.activity_id)
550
- if run_context:
551
- path.append(act_info.activity_run_id)
574
+ if not caching_key:
575
+ path.append(act_info.activity_id)
576
+ if run_context:
577
+ path.append(act_info.activity_run_id)
578
+ if caching_key:
579
+ path += [_CACHE_DIR, caching_key]
552
580
  return Path(*path)
553
581
 
554
582
 
@@ -559,9 +587,13 @@ def activity_workdir(
559
587
  wf_context: bool = True,
560
588
  act_context: bool = True,
561
589
  run_context: bool = False,
590
+ caching_key: str | None = None,
562
591
  ) -> Path:
563
592
  ctx_path = _contextual_path(
564
- wf_context=wf_context, act_context=act_context, run_context=run_context
593
+ wf_context=wf_context,
594
+ act_context=act_context,
595
+ run_context=run_context,
596
+ caching_key=caching_key,
565
597
  )
566
598
  return workdir.joinpath(project, ctx_path)
567
599
 
@@ -808,4 +840,10 @@ def async_enter_cm(
808
840
 
809
841
 
810
842
  def config_cache_key(config: BaseModel) -> str:
811
- return str(hash(config))
843
+ # BaseModel support deterministic hash we can call hash on it
844
+ config_label = (
845
+ to_snake(config.__class__.__name__.replace("Config", ""))
846
+ .lower()
847
+ .replace("_", "-")
848
+ )
849
+ return f"{config_label}-{str(hash(config))}"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datashare-python"
3
- version = "0.10.6"
3
+ version = "0.10.8"
4
4
  description = "Manage Python tasks and local resources in Datashare"
5
5
  authors = [
6
6
  { name = "Clément Doumouro", email = "cdoumouro@icij.org" },