adapta 3.3.22a532.dev6__py3-none-any.whl → 3.5.13__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.
Files changed (132) hide show
  1. adapta/__init__.py +1 -1
  2. adapta/_version.py +1 -1
  3. adapta/connectors/__init__.py +1 -1
  4. adapta/connectors/service_bus/__init__.py +1 -1
  5. adapta/connectors/service_bus/_connector.py +2 -3
  6. adapta/logs/__init__.py +1 -1
  7. adapta/logs/_async_logger.py +34 -23
  8. adapta/logs/_base.py +21 -21
  9. adapta/logs/_internal.py +6 -7
  10. adapta/logs/_internal_logger.py +69 -15
  11. adapta/logs/_logger_interface.py +9 -10
  12. adapta/logs/handlers/__init__.py +1 -1
  13. adapta/logs/handlers/datadog_api_handler.py +7 -7
  14. adapta/logs/handlers/safe_stream_handler.py +4 -4
  15. adapta/logs/models/__init__.py +1 -1
  16. adapta/logs/models/_log_level.py +1 -1
  17. adapta/logs/models/_logs_metadata.py +4 -5
  18. adapta/metrics/__init__.py +1 -1
  19. adapta/metrics/_base.py +14 -15
  20. adapta/metrics/providers/__init__.py +1 -1
  21. adapta/metrics/providers/datadog_provider.py +21 -22
  22. adapta/metrics/providers/void_provider.py +34 -0
  23. adapta/ml/__init__.py +1 -1
  24. adapta/ml/_model.py +1 -1
  25. adapta/ml/mlflow/__init__.py +1 -1
  26. adapta/ml/mlflow/_client.py +24 -6
  27. adapta/ml/mlflow/_functions.py +23 -16
  28. adapta/process_communication/__init__.py +1 -1
  29. adapta/process_communication/_models.py +7 -6
  30. adapta/schema_management/README.md +0 -1
  31. adapta/schema_management/__init__.py +1 -1
  32. adapta/schema_management/schema_entity.py +3 -3
  33. adapta/security/__init__.py +1 -1
  34. adapta/security/clients/__init__.py +1 -1
  35. adapta/security/clients/_azure_client.py +13 -12
  36. adapta/security/clients/_base.py +6 -6
  37. adapta/security/clients/_local_client.py +6 -6
  38. adapta/security/clients/aws/__init__.py +1 -1
  39. adapta/security/clients/aws/_aws_client.py +10 -9
  40. adapta/security/clients/aws/_aws_credentials.py +7 -8
  41. adapta/security/clients/hashicorp_vault/__init__.py +1 -1
  42. adapta/security/clients/hashicorp_vault/hashicorp_vault_client.py +5 -5
  43. adapta/security/clients/hashicorp_vault/kubernetes_client.py +2 -2
  44. adapta/security/clients/hashicorp_vault/oidc_client.py +2 -2
  45. adapta/security/clients/hashicorp_vault/token_client.py +2 -2
  46. adapta/storage/__init__.py +1 -1
  47. adapta/storage/blob/README.md +14 -10
  48. adapta/storage/blob/__init__.py +1 -1
  49. adapta/storage/blob/azure_storage_client.py +15 -14
  50. adapta/storage/blob/base.py +11 -10
  51. adapta/storage/blob/local_storage_client.py +11 -10
  52. adapta/storage/blob/s3_storage_client.py +14 -13
  53. adapta/storage/cache/__init__.py +1 -1
  54. adapta/storage/cache/_base.py +5 -5
  55. adapta/storage/cache/redis_cache.py +5 -5
  56. adapta/storage/database/__init__.py +1 -1
  57. adapta/storage/database/v2/__init__.py +1 -1
  58. adapta/storage/database/v2/azure_sql.py +5 -6
  59. adapta/storage/database/v2/models/__init__.py +1 -1
  60. adapta/storage/database/v2/models/_models.py +2 -3
  61. adapta/storage/database/v2/odbc.py +13 -12
  62. adapta/storage/database/v2/snowflake_sql.py +12 -12
  63. adapta/storage/database/v2/trino_sql.py +7 -6
  64. adapta/storage/database/v3/__init__.py +1 -1
  65. adapta/storage/database/v3/azure_sql.py +5 -7
  66. adapta/storage/database/v3/models/__init__.py +1 -1
  67. adapta/storage/database/v3/models/_models.py +2 -3
  68. adapta/storage/database/v3/odbc.py +12 -11
  69. adapta/storage/database/v3/snowflake_sql.py +12 -12
  70. adapta/storage/database/v3/trino_sql.py +3 -2
  71. adapta/storage/delta_lake/__init__.py +1 -1
  72. adapta/storage/delta_lake/v2/__init__.py +1 -1
  73. adapta/storage/delta_lake/v2/_functions.py +23 -23
  74. adapta/storage/delta_lake/v2/_models.py +4 -5
  75. adapta/storage/delta_lake/v3/__init__.py +1 -1
  76. adapta/storage/delta_lake/v3/_functions.py +23 -23
  77. adapta/storage/delta_lake/v3/_models.py +4 -5
  78. adapta/storage/distributed_object_store/__init__.py +1 -1
  79. adapta/storage/distributed_object_store/v2/__init__.py +1 -1
  80. adapta/storage/distributed_object_store/v2/datastax_astra/__init__.py +1 -1
  81. adapta/storage/distributed_object_store/v2/datastax_astra/_models.py +1 -1
  82. adapta/storage/distributed_object_store/v2/datastax_astra/astra_client.py +52 -51
  83. adapta/storage/distributed_object_store/v3/__init__.py +1 -1
  84. adapta/storage/distributed_object_store/v3/datastax_astra/__init__.py +1 -1
  85. adapta/storage/distributed_object_store/v3/datastax_astra/_model_mappers.py +63 -64
  86. adapta/storage/distributed_object_store/v3/datastax_astra/_models.py +3 -3
  87. adapta/storage/distributed_object_store/v3/datastax_astra/astra_client.py +52 -42
  88. adapta/storage/exceptions.py +1 -1
  89. adapta/storage/models/__init__.py +1 -1
  90. adapta/storage/models/_functions.py +5 -32
  91. adapta/storage/models/astra.py +4 -4
  92. adapta/storage/models/aws.py +1 -1
  93. adapta/storage/models/azure.py +2 -3
  94. adapta/storage/models/base.py +9 -1
  95. adapta/storage/models/enum.py +2 -0
  96. adapta/storage/models/filter_expression.py +46 -5
  97. adapta/storage/models/format.py +16 -355
  98. adapta/storage/models/formatters/__init__.py +36 -0
  99. adapta/storage/models/formatters/dict.py +43 -0
  100. adapta/storage/models/formatters/exceptions.py +7 -0
  101. adapta/storage/models/formatters/metaframe.py +48 -0
  102. adapta/storage/models/formatters/pandas.py +139 -0
  103. adapta/storage/models/formatters/pickle.py +36 -0
  104. adapta/storage/models/formatters/polars.py +240 -0
  105. adapta/storage/models/formatters/unit.py +26 -0
  106. adapta/storage/models/hive.py +5 -6
  107. adapta/storage/models/local.py +1 -1
  108. adapta/storage/models/trino.py +56 -0
  109. adapta/storage/query_enabled_store/__init__.py +6 -1
  110. adapta/storage/query_enabled_store/_models.py +10 -8
  111. adapta/storage/query_enabled_store/_qes_astra.py +10 -9
  112. adapta/storage/query_enabled_store/_qes_delta.py +8 -7
  113. adapta/storage/query_enabled_store/_qes_local.py +2 -1
  114. adapta/storage/query_enabled_store/_qes_trino.py +133 -0
  115. adapta/storage/secrets/__init__.py +1 -1
  116. adapta/storage/secrets/_base.py +5 -4
  117. adapta/storage/secrets/azure_secret_client.py +3 -4
  118. adapta/storage/secrets/hashicorp_vault_secret_storage_client.py +5 -5
  119. adapta/utils/__init__.py +1 -1
  120. adapta/utils/_common.py +14 -14
  121. adapta/utils/_requests.py +2 -3
  122. adapta/utils/concurrent_task_runner.py +10 -9
  123. adapta/utils/data_structures/_functions.py +6 -6
  124. adapta/utils/decorators/_logging.py +3 -3
  125. adapta/utils/decorators/_rate_limit.py +2 -2
  126. adapta/utils/metaframe.py +32 -8
  127. adapta/utils/python_typing/_functions.py +5 -10
  128. {adapta-3.3.22a532.dev6.dist-info → adapta-3.5.13.dist-info}/METADATA +6 -5
  129. adapta-3.5.13.dist-info/RECORD +146 -0
  130. {adapta-3.3.22a532.dev6.dist-info → adapta-3.5.13.dist-info}/WHEEL +1 -1
  131. adapta-3.3.22a532.dev6.dist-info/RECORD +0 -135
  132. {adapta-3.3.22a532.dev6.dist-info → adapta-3.5.13.dist-info/licenses}/LICENSE +0 -0
adapta/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
  Global index.
3
3
  """
4
4
 
5
- # Copyright (c) 2023-2024. ECCO Sneaks & Data
5
+ # Copyright (c) 2023-2026. ECCO Data & AI and other project contributors.
6
6
  #
7
7
  # Licensed under the Apache License, Version 2.0 (the "License");
8
8
  # you may not use this file except in compliance with the License.
adapta/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = 'v3.3.22a532.dev6'
1
+ __version__ = '3.5.13'
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023-2024. ECCO Sneaks & Data
1
+ # Copyright (c) 2023-2026. ECCO Data & AI and other project contributors.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
1
1
  """init file"""
2
2
 
3
- # Copyright (c) 2023-2024. ECCO Sneaks & Data
3
+ # Copyright (c) 2023-2026. ECCO Data & AI and other project contributors.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Connector for Azure Service Bus.
3
3
  """
4
- # Copyright (c) 2023-2024. ECCO Sneaks & Data
4
+ # Copyright (c) 2023-2026. ECCO Data & AI and other project contributors.
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
- from typing import Optional
20
19
  import os
21
20
  from azure.servicebus import (
22
21
  ServiceBusSender,
@@ -31,7 +30,7 @@ class AzureServiceBusConnector:
31
30
  Connector for Azure Service Bus.
32
31
  """
33
32
 
34
- def __init__(self, conn_str: Optional[str] = None, queue_name: Optional[str] = None):
33
+ def __init__(self, conn_str: str | None = None, queue_name: str | None = None):
35
34
  self.service_bus_client: ServiceBusClient = ServiceBusClient.from_connection_string(
36
35
  conn_str=conn_str if conn_str is not None else os.environ["SERVICE_BUS_CONNECTION_STRING"],
37
36
  transport_type=TransportType.Amqp,
adapta/logs/__init__.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Module index.
3
3
  """
4
- # Copyright (c) 2023-2024. ECCO Sneaks & Data
4
+ # Copyright (c) 2023-2026. ECCO Data & AI and other project contributors.
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
@@ -3,7 +3,7 @@
3
3
  """
4
4
  import asyncio
5
5
 
6
- # Copyright (c) 2023-2024. ECCO Sneaks & Data
6
+ # Copyright (c) 2023-2026. ECCO Data & AI and other project contributors.
7
7
  #
8
8
  # Licensed under the Apache License, Version 2.0 (the "License");
9
9
  # you may not use this file except in compliance with the License.
@@ -19,11 +19,12 @@ import asyncio
19
19
  #
20
20
 
21
21
  import logging
22
+ import sys
22
23
  import threading
23
24
  from contextlib import asynccontextmanager
24
25
  from logging.handlers import QueueHandler, QueueListener
25
- from multiprocessing import Queue
26
- from typing import final, TypeVar, Generic, Type, List, Optional, Dict
26
+ from queue import Queue
27
+ from typing import final, TypeVar, Generic
27
28
 
28
29
  from adapta.logs._internal import MetadataLogger
29
30
  from adapta.logs._internal_logger import _InternalLogger
@@ -38,11 +39,11 @@ class _AsyncLogger(Generic[TLogger], _InternalLogger):
38
39
  Asyncio-safe wrapper for MetadataLogger
39
40
  """
40
41
 
41
- def redirect(self, tags: Optional[Dict[str, str]] = None, **kwargs):
42
+ def redirect(self, tags: dict[str, str] | None = None, **kwargs):
42
43
  return self._redirect(logger=self._logger, tags=tags)
43
44
 
44
45
  @asynccontextmanager
45
- async def redirect_async(self, tags: Optional[Dict[str, str]] = None, **kwargs):
46
+ async def redirect_async(self, tags: dict[str, str] | None = None, **kwargs):
46
47
  is_active = False
47
48
  tmp_symlink_out = b""
48
49
  tmp_symlink_err = b""
@@ -57,16 +58,26 @@ class _AsyncLogger(Generic[TLogger], _InternalLogger):
57
58
  # externally control the flushing process
58
59
  while is_active:
59
60
  start_position_out = self._flush_and_log(
60
- pos=start_position_out, tmp_symlink=tmp_symlink_out, logger=self._logger, tags=tags
61
+ pos=start_position_out,
62
+ tmp_symlink=tmp_symlink_out,
63
+ logger=self._logger,
64
+ tags=tags,
65
+ channel=sys.stdout,
61
66
  )
62
67
  start_position_err = self._flush_and_log(
63
- pos=start_position_err, tmp_symlink=tmp_symlink_err, logger=self._logger, tags=tags
68
+ pos=start_position_err,
69
+ tmp_symlink=tmp_symlink_err,
70
+ logger=self._logger,
71
+ tags=tags,
72
+ channel=sys.stderr,
64
73
  )
65
74
  await asyncio.sleep(0.1)
66
75
 
67
76
  return self._flush_and_log(
68
- pos=start_position_out, tmp_symlink=tmp_symlink_out, logger=self._logger, tags=tags
69
- ), self._flush_and_log(pos=start_position_err, tmp_symlink=tmp_symlink_err, logger=self._logger, tags=tags)
77
+ pos=start_position_out, tmp_symlink=tmp_symlink_out, logger=self._logger, tags=tags, channel=sys.stdout
78
+ ), self._flush_and_log(
79
+ pos=start_position_err, tmp_symlink=tmp_symlink_err, logger=self._logger, tags=tags, channel=sys.stderr
80
+ )
70
81
 
71
82
  self._handle_unsupported_redirect(tags)
72
83
  libc, saved_stdout, saved_stderr, tmp_file_out, tmp_file_err = self._prepare_redirect()
@@ -86,8 +97,8 @@ class _AsyncLogger(Generic[TLogger], _InternalLogger):
86
97
  self,
87
98
  name: str,
88
99
  min_log_level: LogLevel,
89
- log_handlers: List[logging.Handler],
90
- fixed_template: Optional[Dict[str, Dict[str, str]]] = None,
100
+ log_handlers: list[logging.Handler],
101
+ fixed_template: dict[str, dict[str, str]] | None = None,
91
102
  fixed_template_delimiter=", ",
92
103
  global_tags: dict[str, str] | None = None,
93
104
  ):
@@ -98,14 +109,14 @@ class _AsyncLogger(Generic[TLogger], _InternalLogger):
98
109
  self._logger_queue_handler = QueueHandler(self._logger_message_queue)
99
110
  self._logger.addHandler(self._logger_queue_handler)
100
111
  self._log_handlers = log_handlers
101
- self._listener: Optional[QueueListener] = None
112
+ self._listener: QueueListener | None = None
102
113
  self._is_active: bool = False
103
114
  self._lock = threading.RLock()
104
115
 
105
116
  def info(
106
117
  self,
107
118
  template: str,
108
- tags: Optional[Dict[str, str]] = None,
119
+ tags: dict[str, str] | None = None,
109
120
  **kwargs,
110
121
  ) -> None:
111
122
  """
@@ -121,8 +132,8 @@ class _AsyncLogger(Generic[TLogger], _InternalLogger):
121
132
  def warning(
122
133
  self,
123
134
  template: str,
124
- exception: Optional[BaseException] = None,
125
- tags: Optional[Dict[str, str]] = None,
135
+ exception: BaseException | None = None,
136
+ tags: dict[str, str] | None = None,
126
137
  **kwargs,
127
138
  ) -> None:
128
139
  """
@@ -139,8 +150,8 @@ class _AsyncLogger(Generic[TLogger], _InternalLogger):
139
150
  def error(
140
151
  self,
141
152
  template: str,
142
- exception: Optional[BaseException] = None,
143
- tags: Optional[Dict[str, str]] = None,
153
+ exception: BaseException | None = None,
154
+ tags: dict[str, str] | None = None,
144
155
  **kwargs,
145
156
  ) -> None:
146
157
  """
@@ -157,9 +168,9 @@ class _AsyncLogger(Generic[TLogger], _InternalLogger):
157
168
  def debug(
158
169
  self,
159
170
  template: str,
160
- exception: Optional[BaseException] = None,
161
- diagnostics: Optional[str] = None, # pylint: disable=R0913
162
- tags: Optional[Dict[str, str]] = None,
171
+ exception: BaseException | None = None,
172
+ diagnostics: str | None = None, # pylint: disable=R0913
173
+ tags: dict[str, str] | None = None,
163
174
  **kwargs,
164
175
  ) -> None:
165
176
  """
@@ -206,10 +217,10 @@ class _AsyncLogger(Generic[TLogger], _InternalLogger):
206
217
 
207
218
 
208
219
  def create_async_logger(
209
- logger_type: Type[TLogger],
210
- log_handlers: List[logging.Handler],
220
+ logger_type: type[TLogger],
221
+ log_handlers: list[logging.Handler],
211
222
  min_log_level: LogLevel = LogLevel.INFO,
212
- fixed_template: Optional[Dict[str, Dict[str, str]]] = None,
223
+ fixed_template: dict[str, dict[str, str]] | None = None,
213
224
  fixed_template_delimiter=", ",
214
225
  global_tags: dict[str, str] = None,
215
226
  ) -> _AsyncLogger[TLogger]:
adapta/logs/_base.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Adapta Logging Interface.
3
3
  """
4
- # Copyright (c) 2023-2024. ECCO Sneaks & Data
4
+ # Copyright (c) 2023-2026. ECCO Data & AI and other project contributors.
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
18
18
 
19
19
  import logging
20
20
  from logging import Handler, StreamHandler
21
- from typing import List, Optional, Dict, final
21
+ from typing import final
22
22
 
23
23
  from adapta.logs._internal_logger import _InternalLogger
24
24
  from adapta.logs.models import LogLevel
@@ -31,10 +31,10 @@ class SemanticLogger(_InternalLogger):
31
31
  Proxy for a collection of python loggers that use the same formatting interface.
32
32
  """
33
33
 
34
- async def redirect_async(self, tags: Optional[Dict[str, str]] = None, **kwargs):
34
+ async def redirect_async(self, tags: dict[str, str] | None = None, **kwargs):
35
35
  raise NotImplementedError("Async operations are not supported by this logger")
36
36
 
37
- def redirect(self, tags: Optional[Dict[str, str]] = None, log_source_name: Optional[str] = None, **kwargs):
37
+ def redirect(self, tags: dict[str, str] | None = None, log_source_name: str | None = None, **kwargs):
38
38
  return self._redirect(logger=self._get_logger(log_source_name), tags=tags)
39
39
 
40
40
  def start(self) -> None:
@@ -43,7 +43,7 @@ class SemanticLogger(_InternalLogger):
43
43
  def stop(self) -> None:
44
44
  pass
45
45
 
46
- def __init__(self, fixed_template: Optional[Dict[str, Dict[str, str]]] = None, fixed_template_delimiter=", "):
46
+ def __init__(self, fixed_template: dict[str, dict[str, str]] | None = None, fixed_template_delimiter=", "):
47
47
  """
48
48
  Creates a new instance of a SemanticLogger
49
49
 
@@ -51,7 +51,7 @@ class SemanticLogger(_InternalLogger):
51
51
  :param fixed_template_delimiter: Optional delimiter to use when appending fixed templates.
52
52
  """
53
53
  super().__init__(fixed_template, fixed_template_delimiter)
54
- self._loggers: Dict[str, logging.Logger] = {}
54
+ self._loggers: dict[str, logging.Logger] = {}
55
55
  self._default_log_source = None
56
56
  self._fixed_template = fixed_template
57
57
  self._fixed_template_delimiter = fixed_template_delimiter
@@ -62,7 +62,7 @@ class SemanticLogger(_InternalLogger):
62
62
  *,
63
63
  log_source_name: str,
64
64
  min_log_level: LogLevel,
65
- log_handlers: Optional[List[Handler]] = None,
65
+ log_handlers: list[Handler] | None = None,
66
66
  is_default=False,
67
67
  ) -> "SemanticLogger":
68
68
  """
@@ -90,13 +90,13 @@ class SemanticLogger(_InternalLogger):
90
90
 
91
91
  return self
92
92
 
93
- def __getattr__(self, log_source) -> Optional[logging.Logger]:
93
+ def __getattr__(self, log_source) -> logging.Logger | None:
94
94
  if log_source in self._loggers:
95
95
  return self._loggers[log_source]
96
96
 
97
97
  return None
98
98
 
99
- def _get_logger(self, log_source_name: Optional[str] = None) -> MetadataLogger:
99
+ def _get_logger(self, log_source_name: str | None = None) -> MetadataLogger:
100
100
  """
101
101
  Retrieves a logger by log source name, or a default logger is log source name is not provided.
102
102
 
@@ -117,8 +117,8 @@ class SemanticLogger(_InternalLogger):
117
117
  def info(
118
118
  self,
119
119
  template: str,
120
- tags: Optional[Dict[str, str]] = None,
121
- log_source_name: Optional[str] = None,
120
+ tags: dict[str, str] | None = None,
121
+ log_source_name: str | None = None,
122
122
  **kwargs,
123
123
  ) -> None:
124
124
  """
@@ -136,9 +136,9 @@ class SemanticLogger(_InternalLogger):
136
136
  def warning(
137
137
  self,
138
138
  template: str,
139
- exception: Optional[BaseException] = None,
140
- tags: Optional[Dict[str, str]] = None,
141
- log_source_name: Optional[str] = None,
139
+ exception: BaseException | None = None,
140
+ tags: dict[str, str] | None = None,
141
+ log_source_name: str | None = None,
142
142
  **kwargs,
143
143
  ) -> None:
144
144
  """
@@ -157,9 +157,9 @@ class SemanticLogger(_InternalLogger):
157
157
  def error(
158
158
  self,
159
159
  template: str,
160
- exception: Optional[BaseException] = None,
161
- tags: Optional[Dict[str, str]] = None,
162
- log_source_name: Optional[str] = None,
160
+ exception: BaseException | None = None,
161
+ tags: dict[str, str] | None = None,
162
+ log_source_name: str | None = None,
163
163
  **kwargs,
164
164
  ) -> None:
165
165
  """
@@ -178,10 +178,10 @@ class SemanticLogger(_InternalLogger):
178
178
  def debug(
179
179
  self,
180
180
  template: str,
181
- exception: Optional[BaseException] = None,
182
- diagnostics: Optional[str] = None, # pylint: disable=R0913
183
- tags: Optional[Dict[str, str]] = None,
184
- log_source_name: Optional[str] = None,
181
+ exception: BaseException | None = None,
182
+ diagnostics: str | None = None, # pylint: disable=R0913
183
+ tags: dict[str, str] | None = None,
184
+ log_source_name: str | None = None,
185
185
  **kwargs,
186
186
  ) -> None:
187
187
  """
adapta/logs/_internal.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """Classes for internal use by `adapta.logs` module. Should not be imported outside this module"""
2
- # Copyright (c) 2023-2024. ECCO Sneaks & Data
2
+ # Copyright (c) 2023-2026. ECCO Data & AI and other project contributors.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -15,7 +15,6 @@
15
15
  #
16
16
 
17
17
  import logging
18
- from typing import Optional, Dict
19
18
 
20
19
  from adapta.logs.models import CompositeLogMetadata, LogLevel
21
20
 
@@ -39,11 +38,11 @@ class MetadataLogger(logging.Logger):
39
38
  log_level: int,
40
39
  msg: str,
41
40
  template: str,
42
- tags: Optional[Dict[str, str]],
43
- diagnostics: Optional[str],
41
+ tags: dict[str, str] | None,
42
+ diagnostics: str | None,
44
43
  stack_info: bool,
45
- metadata_fields: Optional[Dict[str, str]],
46
- exception: Optional[BaseException],
44
+ metadata_fields: dict[str, str] | None,
45
+ exception: BaseException | None,
47
46
  ):
48
47
  """
49
48
  Creates log entry with metadata from Composite Logger
@@ -77,7 +76,7 @@ class MetadataLogger(logging.Logger):
77
76
  )
78
77
 
79
78
 
80
- def from_log_level(log_level: Optional[LogLevel]) -> Optional[int]:
79
+ def from_log_level(log_level: LogLevel | None) -> int | None:
81
80
  """
82
81
  Converts adapta log level to logging log level
83
82
  """
@@ -3,7 +3,7 @@
3
3
  """
4
4
  import ctypes
5
5
 
6
- # Copyright (c) 2023-2024. ECCO Sneaks & Data
6
+ # Copyright (c) 2023-2026. ECCO Data & AI and other project contributors.
7
7
  #
8
8
  # Licensed under the Apache License, Version 2.0 (the "License");
9
9
  # you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@ from abc import ABC
28
28
  from contextlib import contextmanager
29
29
  from threading import Thread
30
30
  from time import sleep
31
- from typing import Any
31
+ from typing import Any, TextIO
32
32
 
33
33
  from adapta.logs._internal import MetadataLogger, from_log_level
34
34
  from adapta.logs._logger_interface import LoggerInterface
@@ -65,6 +65,17 @@ class _InternalLogger(LoggerInterface, ABC):
65
65
 
66
66
  return fixed_args
67
67
 
68
+ def _get_format_args(self, **kwargs) -> tuple[dict, dict]:
69
+ base_args = self._get_fixed_args()
70
+ duplicates = {}
71
+ for arg_name, arg_value in kwargs.items():
72
+ if arg_name in base_args:
73
+ duplicates[arg_name] = arg_value
74
+ else:
75
+ base_args[arg_name] = arg_value
76
+
77
+ return base_args, duplicates
78
+
68
79
  def _get_template(self, template) -> str:
69
80
  return (
70
81
  self._fixed_template_delimiter.join([template, ", ".join(self._fixed_template.keys())])
@@ -72,6 +83,30 @@ class _InternalLogger(LoggerInterface, ABC):
72
83
  else template
73
84
  )
74
85
 
86
+ def _log_malformed_template(
87
+ self,
88
+ logger: MetadataLogger,
89
+ duplicates: dict[str, str],
90
+ tags: dict[str, str] = None,
91
+ **kwargs,
92
+ ) -> None:
93
+ dup_template = " ".join(
94
+ [
95
+ "Duplicated log properties provided:",
96
+ ", ".join(map(lambda key: "".join(["{", key, "}"]), duplicates.keys())),
97
+ ]
98
+ )
99
+ logger.log_with_metadata(
100
+ logging.WARN,
101
+ msg=dup_template.format(**duplicates),
102
+ template=dup_template,
103
+ tags=(tags or {}) | self._global_tags,
104
+ diagnostics=None,
105
+ stack_info=False,
106
+ exception=None,
107
+ metadata_fields=self._get_metadata_fields(kwargs),
108
+ )
109
+
75
110
  def _meta_info(
76
111
  self,
77
112
  template: str,
@@ -88,7 +123,8 @@ class _InternalLogger(LoggerInterface, ABC):
88
123
  :param kwargs: Templated arguments (key=value).
89
124
  :return:
90
125
  """
91
- msg = self._get_template(template).format(**self._get_fixed_args(), **kwargs)
126
+ log_args, duplicates = self._get_format_args(**kwargs)
127
+ msg = self._get_template(template).format(**log_args)
92
128
  logger.log_with_metadata(
93
129
  logging.INFO,
94
130
  msg=msg,
@@ -99,6 +135,8 @@ class _InternalLogger(LoggerInterface, ABC):
99
135
  exception=None,
100
136
  metadata_fields=self._get_metadata_fields(kwargs),
101
137
  )
138
+ if len(duplicates) > 0:
139
+ self._log_malformed_template(logger, duplicates, tags, **kwargs)
102
140
 
103
141
  def _meta_warning(
104
142
  self,
@@ -118,7 +156,8 @@ class _InternalLogger(LoggerInterface, ABC):
118
156
  :param kwargs: Templated arguments (key=value).
119
157
  :return:
120
158
  """
121
- msg = self._get_template(template).format(**self._get_fixed_args(), **kwargs)
159
+ log_args, duplicates = self._get_format_args(**kwargs)
160
+ msg = self._get_template(template).format(**log_args)
122
161
  logger.log_with_metadata(
123
162
  logging.WARN,
124
163
  msg=msg,
@@ -129,6 +168,8 @@ class _InternalLogger(LoggerInterface, ABC):
129
168
  exception=exception,
130
169
  metadata_fields=self._get_metadata_fields(kwargs),
131
170
  )
171
+ if len(duplicates) > 0:
172
+ self._log_malformed_template(logger, duplicates, tags, **kwargs)
132
173
 
133
174
  def _meta_error(
134
175
  self,
@@ -148,7 +189,8 @@ class _InternalLogger(LoggerInterface, ABC):
148
189
  :param kwargs: Templated arguments (key=value).
149
190
  :return:
150
191
  """
151
- msg = self._get_template(template).format(**self._get_fixed_args(), **kwargs)
192
+ log_args, duplicates = self._get_format_args(**kwargs)
193
+ msg = self._get_template(template).format(**log_args)
152
194
  logger.log_with_metadata(
153
195
  logging.ERROR,
154
196
  msg=msg,
@@ -159,6 +201,8 @@ class _InternalLogger(LoggerInterface, ABC):
159
201
  exception=exception,
160
202
  metadata_fields=self._get_metadata_fields(kwargs),
161
203
  )
204
+ if len(duplicates) > 0:
205
+ self._log_malformed_template(logger, duplicates, tags, **kwargs)
162
206
 
163
207
  def _meta_debug(
164
208
  self,
@@ -179,7 +223,8 @@ class _InternalLogger(LoggerInterface, ABC):
179
223
  :param kwargs: Templated arguments (key=value).
180
224
  :return:
181
225
  """
182
- msg = self._get_template(template).format(**self._get_fixed_args(), **kwargs)
226
+ log_args, duplicates = self._get_format_args(**kwargs)
227
+ msg = self._get_template(template).format(**log_args)
183
228
  logger.log_with_metadata(
184
229
  logging.DEBUG,
185
230
  msg=msg,
@@ -190,6 +235,8 @@ class _InternalLogger(LoggerInterface, ABC):
190
235
  exception=exception,
191
236
  metadata_fields=self._get_metadata_fields(kwargs),
192
237
  )
238
+ if len(duplicates) > 0:
239
+ self._log_malformed_template(logger, duplicates, tags, **kwargs)
193
240
 
194
241
  def _log_redirect_message(
195
242
  self,
@@ -255,20 +302,23 @@ class _InternalLogger(LoggerInterface, ABC):
255
302
  pos: int,
256
303
  tmp_symlink: bytes,
257
304
  logger: MetadataLogger,
305
+ channel: TextIO,
258
306
  tags: dict[str, str] | None = None,
259
307
  log_level: LogLevel | None = None,
260
308
  ) -> int:
261
- sys.stdout.flush()
262
- with open(tmp_symlink, "r", encoding="utf-8") as output:
309
+ channel.flush()
310
+ with open(tmp_symlink, encoding="utf-8") as output:
263
311
  output.seek(pos)
264
312
  for line in output.readlines():
265
- self._log_redirect_message(
266
- logger,
267
- base_template="Redirected output: {message}",
268
- message=line,
269
- tags=(tags or {}) | self._global_tags,
270
- log_level=log_level,
271
- )
313
+ clean_line = line.strip("\n")
314
+ if clean_line:
315
+ self._log_redirect_message(
316
+ logger,
317
+ base_template="Redirected output: {message}",
318
+ message=clean_line,
319
+ tags=(tags or {}) | self._global_tags,
320
+ log_level=log_level,
321
+ )
272
322
  return output.tell()
273
323
 
274
324
  def _handle_unsupported_redirect(
@@ -309,6 +359,7 @@ class _InternalLogger(LoggerInterface, ABC):
309
359
  logger=logger,
310
360
  tags=(tags or {}) | self._global_tags,
311
361
  log_level=log_level,
362
+ channel=sys.stdout,
312
363
  )
313
364
  start_position_err = self._flush_and_log(
314
365
  pos=start_position_err,
@@ -316,6 +367,7 @@ class _InternalLogger(LoggerInterface, ABC):
316
367
  logger=logger,
317
368
  tags=(tags or {}) | self._global_tags,
318
369
  log_level=log_level,
370
+ channel=sys.stderr,
319
371
  )
320
372
  sleep(0.1)
321
373
 
@@ -325,12 +377,14 @@ class _InternalLogger(LoggerInterface, ABC):
325
377
  logger=logger,
326
378
  tags=(tags or {}) | self._global_tags,
327
379
  log_level=log_level,
380
+ channel=sys.stdout,
328
381
  ), self._flush_and_log(
329
382
  pos=start_position_err,
330
383
  tmp_symlink=tmp_symlink_err,
331
384
  logger=logger,
332
385
  tags=(tags or {}) | self._global_tags,
333
386
  log_level=log_level,
387
+ channel=sys.stderr,
334
388
  )
335
389
 
336
390
  self._handle_unsupported_redirect(tags)
@@ -3,10 +3,9 @@
3
3
  """
4
4
  from abc import ABC, abstractmethod
5
5
  from contextlib import contextmanager, asynccontextmanager
6
- from typing import Optional, Dict
7
6
 
8
7
 
9
- # Copyright (c) 2023-2024. ECCO Sneaks & Data
8
+ # Copyright (c) 2023-2026. ECCO Data & AI and other project contributors.
10
9
  #
11
10
  # Licensed under the Apache License, Version 2.0 (the "License");
12
11
  # you may not use this file except in compliance with the License.
@@ -28,14 +27,14 @@ class LoggerInterface(ABC):
28
27
  """
29
28
 
30
29
  @abstractmethod
31
- def info(self, template: str, tags: Optional[Dict[str, str]] = None, **kwargs):
30
+ def info(self, template: str, tags: dict[str, str] | None = None, **kwargs):
32
31
  """
33
32
  Logs a message on INFO level
34
33
  """
35
34
 
36
35
  @abstractmethod
37
36
  def warning(
38
- self, template: str, exception: Optional[BaseException] = None, tags: Optional[Dict[str, str]] = None, **kwargs
37
+ self, template: str, exception: BaseException | None = None, tags: dict[str, str] | None = None, **kwargs
39
38
  ):
40
39
  """
41
40
  Logs a message on WARN level
@@ -43,7 +42,7 @@ class LoggerInterface(ABC):
43
42
 
44
43
  @abstractmethod
45
44
  def error(
46
- self, template: str, exception: Optional[BaseException] = None, tags: Optional[Dict[str, str]] = None, **kwargs
45
+ self, template: str, exception: BaseException | None = None, tags: dict[str, str] | None = None, **kwargs
47
46
  ):
48
47
  """
49
48
  Logs a message on ERROR level
@@ -53,9 +52,9 @@ class LoggerInterface(ABC):
53
52
  def debug(
54
53
  self,
55
54
  template: str,
56
- exception: Optional[BaseException] = None,
57
- diagnostics: Optional[str] = None,
58
- tags: Optional[Dict[str, str]] = None,
55
+ exception: BaseException | None = None,
56
+ diagnostics: str | None = None,
57
+ tags: dict[str, str] | None = None,
59
58
  **kwargs
60
59
  ):
61
60
  """
@@ -76,7 +75,7 @@ class LoggerInterface(ABC):
76
75
 
77
76
  @contextmanager
78
77
  @abstractmethod
79
- def redirect(self, tags: Optional[Dict[str, str]] = None, **kwargs):
78
+ def redirect(self, tags: dict[str, str] | None = None, **kwargs):
80
79
  """
81
80
  Redirects stdout to a temporary file and dumps its contents as INFO messages
82
81
  once the wrapped code block finishes execution. Stdout is restored after the block completes execution.
@@ -101,7 +100,7 @@ class LoggerInterface(ABC):
101
100
 
102
101
  @asynccontextmanager
103
102
  @abstractmethod
104
- async def redirect_async(self, tags: Optional[Dict[str, str]] = None, **kwargs):
103
+ async def redirect_async(self, tags: dict[str, str] | None = None, **kwargs):
105
104
  """
106
105
  Async version of a redirect. Not supported in sync client
107
106
  """
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2023-2024. ECCO Sneaks & Data
1
+ # Copyright (c) 2023-2026. ECCO Data & AI and other project contributors.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.