datashare-python 0.7.3__py3-none-any.whl → 0.8.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.
@@ -1,3 +1,4 @@
1
+ from enum import StrEnum
1
2
  from pathlib import Path
2
3
  from typing import Literal
3
4
 
@@ -78,13 +79,21 @@ class TemporalClientConfig(BaseModel):
78
79
  LogLevel = Literal["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
79
80
 
80
81
 
82
+ class LogFormat(StrEnum):
83
+ JSON = "json"
84
+ LOGFMT = "logfmt"
85
+ DEFAULT = "default"
86
+
87
+
81
88
  class LoggingConfig(BaseModel):
82
- log_in_json: bool = False
89
+ format: LogFormat = LogFormat.DEFAULT
83
90
  loggers: dict[str, LogLevel]
84
91
 
85
92
 
86
93
  _DEFAULT_LOGGERS = {datashare_python.__name__: "INFO"}
87
- _DEFAULT_LOGGING_CONFIG = LoggingConfig(log_in_json=True, loggers=_DEFAULT_LOGGERS)
94
+ _DEFAULT_LOGGING_CONFIG = LoggingConfig(
95
+ format=LogFormat.DEFAULT, loggers=_DEFAULT_LOGGERS
96
+ )
88
97
 
89
98
 
90
99
  class WorkerConfig(ICIJSettings, BaseModel):
@@ -13,6 +13,7 @@ from temporalio import workflow
13
13
 
14
14
  from datashare_python.config import (
15
15
  DatashareClientConfig,
16
+ LogFormat,
16
17
  LoggingConfig,
17
18
  TemporalClientConfig,
18
19
  WorkerConfig,
@@ -93,7 +94,7 @@ def event_loop(
93
94
  @pytest.fixture(scope="session")
94
95
  def test_worker_config() -> WorkerConfig:
95
96
  logging_config = LoggingConfig(
96
- log_in_json=False,
97
+ format=LogFormat.DEFAULT,
97
98
  loggers={
98
99
  "datashare_python": "DEBUG",
99
100
  "icij_common": "DEBUG",
@@ -40,7 +40,7 @@ def set_loggers(
40
40
  worker_config: WorkerConfig, worker_id: str, loggers: dict[str, LogLevel]
41
41
  ) -> None:
42
42
  setup_worker_loggers(
43
- loggers=loggers, worker_id=worker_id, in_json=worker_config.logging.log_in_json
43
+ loggers=loggers, worker_id=worker_id, format=worker_config.logging.format
44
44
  )
45
45
  logger.info("worker loggers ready to log 💬")
46
46
 
@@ -70,9 +70,8 @@ def discover(
70
70
  deps = []
71
71
  if deps_name is not None:
72
72
  deps = discover_dependencies(deps_name)
73
- for mandatory in _MANDATORY_DEPS:
74
- if mandatory not in deps:
75
- deps.append(mandatory)
73
+ missing = [m for m in _MANDATORY_DEPS if m not in deps]
74
+ deps = missing + deps
76
75
  if deps:
77
76
  n_deps = len(deps)
78
77
  discovered += "\n"
@@ -1,20 +1,39 @@
1
1
  import logging
2
+ import numbers
2
3
  import sys
3
4
  from copy import copy
5
+ from typing import Any
4
6
 
7
+ import orjson
5
8
  from icij_common.logging_utils import DATE_FMT, STREAM_HANDLER_FMT
6
- from pythonjsonlogger.core import RESERVED_ATTRS, BaseJsonFormatter
9
+ from pythonjsonlogger.core import BaseJsonFormatter
7
10
  from pythonjsonlogger.orjson import OrjsonFormatter
8
11
  from temporalio import activity, workflow
9
12
 
10
- from .config import LogLevel
13
+ from .config import LogFormat, LogLevel
11
14
  from .interceptors import get_trace_context
12
15
 
16
+ _BASE_ATTRS = [
17
+ "asctime",
18
+ "exc_info",
19
+ "filename",
20
+ "funcName",
21
+ "levelname",
22
+ "levelno",
23
+ "lineno",
24
+ "module",
25
+ "msecs",
26
+ "message",
27
+ "msg",
28
+ "name",
29
+ "pathname",
30
+ ]
13
31
  _ACT_LOGGER_ATTRS = ["activity_type", "activity_id", "activity_run_id"]
14
32
  _WF_LOGGED_ATTRS = ["workflow_type", "workflow_id", "workflow_run_id"]
15
33
  _TRACE_CONTEXT_ATTRS = ["trace_id", "parent_id", "traceparent"]
34
+
16
35
  _LOGGED_ATTRIBUTES = (
17
- copy(RESERVED_ATTRS)
36
+ copy(_BASE_ATTRS)
18
37
  + _WF_LOGGED_ATTRS
19
38
  + _ACT_LOGGER_ATTRS
20
39
  + _TRACE_CONTEXT_ATTRS
@@ -28,7 +47,7 @@ _STREAM_HANDLER_FMT_WITH_WORKER_ID = (
28
47
 
29
48
 
30
49
  def setup_worker_loggers(
31
- loggers: dict[str, LogLevel], *, worker_id: str | None, in_json: bool
50
+ loggers: dict[str, LogLevel], *, worker_id: str | None, format: LogFormat
32
51
  ) -> None:
33
52
  worker_filter = WorkerFilter(worker_id)
34
53
  for logger_name, level_str in loggers.items():
@@ -36,7 +55,7 @@ def setup_worker_loggers(
36
55
  logger = logging.getLogger(logger_name)
37
56
  logger.setLevel(level)
38
57
  logger.handlers = []
39
- for handler in _get_worker_handlers(level, worker_filter, in_json=in_json):
58
+ for handler in _get_worker_handlers(level, worker_filter, format=format):
40
59
  logger.addHandler(handler)
41
60
 
42
61
 
@@ -64,23 +83,50 @@ class WorkerFilter(logging.Filter):
64
83
 
65
84
 
66
85
  def _get_worker_handlers(
67
- level: int, worker_filter: WorkerFilter, *, in_json: bool
86
+ level: int, worker_filter: WorkerFilter, *, format: LogFormat
68
87
  ) -> list[logging.Handler]:
69
88
  stream_handler = logging.StreamHandler(sys.stderr)
70
- if in_json:
71
- fmt = _json_formatter(datefmt=DATE_FMT)
72
- else:
73
- if worker_filter.worker_id is not None:
74
- fmt = _STREAM_HANDLER_FMT_WITH_WORKER_ID
75
- else:
76
- fmt = STREAM_HANDLER_FMT
77
- fmt = logging.Formatter(fmt, DATE_FMT)
89
+ match format:
90
+ case LogFormat.JSON:
91
+ fmt = _json_formatter(datefmt=DATE_FMT)
92
+ case LogFormat.LOGFMT:
93
+ fmt = LogFmtFormatter(datefmt=DATE_FMT)
94
+ case LogFormat.DEFAULT:
95
+ if worker_filter.worker_id is not None:
96
+ fmt = _STREAM_HANDLER_FMT_WITH_WORKER_ID
97
+ else:
98
+ fmt = STREAM_HANDLER_FMT
99
+ fmt = logging.Formatter(fmt, DATE_FMT)
100
+ case _:
101
+ raise NotImplementedError(f"invalid log format: {format}")
78
102
  stream_handler.setFormatter(fmt)
79
103
  stream_handler.setLevel(level)
80
104
  stream_handler.addFilter(worker_filter)
81
105
  return [stream_handler]
82
106
 
83
107
 
108
+ class LogFmtFormatter(logging.Formatter):
109
+ def format(self, record: logging.LogRecord) -> str:
110
+ logged = dict()
111
+ if record.exc_info and not record.exc_text:
112
+ record.exc_text = self.formatException(record.exc_info)
113
+ logged["exc_info"] = record.exc_text
114
+ for k, v in record.__dict__.items():
115
+ if k in _LOGGED_ATTRIBUTES and k != "exc_info":
116
+ logged[k] = _encode_value(v)
117
+ return " ".join(f"{k}={v}" for k, v in sorted(logged.items()))
118
+
119
+
120
+ def _encode_value(value: Any) -> str:
121
+ if value is None:
122
+ return ""
123
+ if isinstance(value, bool):
124
+ return "true" if value else "false"
125
+ if isinstance(value, numbers.Number):
126
+ return str(value)
127
+ return orjson.dumps(value).decode()
128
+
129
+
84
130
  def _json_formatter(datefmt: str) -> BaseJsonFormatter:
85
131
  fmt = OrjsonFormatter( # let's keep logging as fast as possible
86
132
  _LOGGED_ATTRIBUTES, datefmt=datefmt
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datashare-python
3
- Version: 0.7.3
3
+ Version: 0.8.0
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,27 +1,27 @@
1
1
  datashare_python/.gitignore,sha256=e-SRgnvGGdsjRrqgKsTzALz6Obx8IYiOjr0yaAxT6v8,22
2
2
  datashare_python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  datashare_python/__main__.py,sha256=g-fvS46zl9umKmGrSpl-OG-8PSuZgjqvTCqjpsZtSps,101
4
- datashare_python/config.py,sha256=_Cx4EB1yHXXcLUtw1OBlMk2SKcJRwxqJwgRu6klbxNg,3994
5
- datashare_python/conftest.py,sha256=MrmQKFcUipm_qn-cHsLovZMwMMtVxyK0s1lmKEx54bc,8651
4
+ datashare_python/config.py,sha256=kgrj--t8aH4TKQpcak6f8H5Zny13LwPwvFDJDQk5K10,4137
5
+ datashare_python/conftest.py,sha256=SiAVEwK5odlDljwyQMDxHlD-WEIem7oR04wAEoNH6jA,8673
6
6
  datashare_python/constants.py,sha256=a8-ceZKBVMXydcoNQ35fSjFjxeJ7dt-N6eAvqtPpf9g,320
7
- datashare_python/dependencies.py,sha256=KJuAp6Dmv8DQuFnGjbWiHu7StzZj97eBPDyZ_RfCQRc,4141
8
- datashare_python/discovery.py,sha256=BPB_Ak6d1-vcf9vAQA63IRb2U8h83_mIIi8MbKbFzQ0,7020
7
+ datashare_python/dependencies.py,sha256=ZqIT-LmMMsx3QWf5WSgIY4LsY9bJ6TFJtKQKrHHt2_s,4135
8
+ datashare_python/discovery.py,sha256=d8fpY9AUSH7WP6600TTmm1Cjr1TKOYBoO50cN61dPdU,6999
9
9
  datashare_python/exceptions.py,sha256=bVHEAXxDPKfxeeMC0hJXEsrJkgsKO2ESAhxWU96GA4M,496
10
10
  datashare_python/interceptors.py,sha256=Pl7GodPO4KbfflmacpW-vOUgLazjlXSlDNENbpOUt1c,6725
11
- datashare_python/logging_.py,sha256=XUhZTtofbOqJi1gwytYpUVqvoGPhoz5p2orsXs2FaWs,2968
11
+ datashare_python/logging_.py,sha256=uSTNr6Een9zXNisirPZxJQsbOXMIADLgIPJp370lXng,4252
12
12
  datashare_python/objects.py,sha256=pE0DGNNkl1etxz5ed7T-EaGo1o9TONjH2Lg9u1qdAWU,7571
13
13
  datashare_python/task_client.py,sha256=oTmP8bvZW0UyhLNMi1AV3XIAx7hrdbxNRss2Mw2azEc,8435
14
14
  datashare_python/template.py,sha256=RxKTYLXoS_EQ8Jc41JkBXppPdbCFqDWfP3BmC0gvB5o,4024
15
15
  datashare_python/types_.py,sha256=9Hk1XqpdXbM1TnEzwvJ5G9ABbaCZW9KgBTtiPBVn_7k,649
16
16
  datashare_python/utils.py,sha256=gX3_RJEJS0sAYBNVfBLoWJu7_hIANhylSAohzXVW-yQ,17982
17
- datashare_python/worker-template.tar.gz,sha256=bOqoF6xVJRyFQaRYHIXXre31WYdmEqDLGeiXRr4Inqg,287091
17
+ datashare_python/worker-template.tar.gz,sha256=g28XncbDlgLr1LS4WviGWZCS4V2jnrIt6ZlaXDI2ahs,287088
18
18
  datashare_python/worker.py,sha256=czrN9Z0fPFX-6KHinX8Orx4vb9tpta2e7Qs6H0NiYyE,7534
19
19
  datashare_python/cli/__init__.py,sha256=9BPWtssDgsVfWMsZ1TtZCla0EC_kai4RHttr8oNLYOE,1401
20
20
  datashare_python/cli/project.py,sha256=w32Gy9AOL5B00uDT4in7YUCt2g68FnNbvwg2M3a8G6o,946
21
21
  datashare_python/cli/task.py,sha256=8mvKGS21bZ14BgZ0Uo-dfameljkaI2ZBha80ywCy-E8,5822
22
22
  datashare_python/cli/utils.py,sha256=p69CQb0zfixuyBkiZprhdMCc_NuYwXyAn6vC9H1UzAw,911
23
23
  datashare_python/cli/worker.py,sha256=I4KTpFIpXFowioFn72Rm6LBCYlY-Dhp4NBIPvtRgUXE,5283
24
- datashare_python-0.7.3.dist-info/METADATA,sha256=sZEiq4mFYgmvkYuJOO6KWePleBYWDex722rVR3FSm3I,921
25
- datashare_python-0.7.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
26
- datashare_python-0.7.3.dist-info/entry_points.txt,sha256=ILE7auxabHWiu3GC-AunWnzjhOI_SbZp7D4GqZHlLw4,68
27
- datashare_python-0.7.3.dist-info/RECORD,,
24
+ datashare_python-0.8.0.dist-info/METADATA,sha256=FDdSE58wp8Z4wHXQF8FVngy279LWO4vRTj6wgfpzGxc,921
25
+ datashare_python-0.8.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
26
+ datashare_python-0.8.0.dist-info/entry_points.txt,sha256=ILE7auxabHWiu3GC-AunWnzjhOI_SbZp7D4GqZHlLw4,68
27
+ datashare_python-0.8.0.dist-info/RECORD,,