karton-core 5.6.1__py3-none-any.whl → 5.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.
- karton/core/__version__.py +1 -1
- karton/core/asyncio/__init__.py +21 -0
- karton/core/asyncio/backend.py +370 -0
- karton/core/asyncio/base.py +133 -0
- karton/core/asyncio/karton.py +359 -0
- karton/core/asyncio/logger.py +57 -0
- karton/core/asyncio/resource.py +384 -0
- karton/core/backend.py +160 -114
- karton/core/base.py +123 -94
- karton/core/config.py +11 -8
- karton/core/karton.py +14 -12
- karton/core/logger.py +33 -15
- karton/core/resource.py +44 -30
- karton/core/task.py +24 -2
- karton/core/test.py +3 -2
- karton/system/system.py +22 -7
- {karton_core-5.6.1.dist-info → karton_core-5.8.0.dist-info}/METADATA +3 -2
- karton_core-5.8.0.dist-info/RECORD +33 -0
- karton_core-5.6.1.dist-info/RECORD +0 -27
- /karton_core-5.6.1-nspkg.pth → /karton_core-5.8.0-nspkg.pth +0 -0
- {karton_core-5.6.1.dist-info → karton_core-5.8.0.dist-info}/LICENSE +0 -0
- {karton_core-5.6.1.dist-info → karton_core-5.8.0.dist-info}/WHEEL +0 -0
- {karton_core-5.6.1.dist-info → karton_core-5.8.0.dist-info}/entry_points.txt +0 -0
- {karton_core-5.6.1.dist-info → karton_core-5.8.0.dist-info}/namespace_packages.txt +0 -0
- {karton_core-5.6.1.dist-info → karton_core-5.8.0.dist-info}/top_level.txt +0 -0
karton/core/backend.py
CHANGED
@@ -21,6 +21,7 @@ from urllib3.response import HTTPResponse
|
|
21
21
|
|
22
22
|
from .config import Config
|
23
23
|
from .exceptions import InvalidIdentityError
|
24
|
+
from .resource import RemoteResource
|
24
25
|
from .task import Task, TaskPriority, TaskState
|
25
26
|
from .utils import chunks, chunks_iter
|
26
27
|
|
@@ -33,12 +34,20 @@ KARTON_OUTPUTS_NAMESPACE = "karton.outputs"
|
|
33
34
|
|
34
35
|
KartonBind = namedtuple(
|
35
36
|
"KartonBind",
|
36
|
-
[
|
37
|
+
[
|
38
|
+
"identity",
|
39
|
+
"info",
|
40
|
+
"version",
|
41
|
+
"persistent",
|
42
|
+
"filters",
|
43
|
+
"service_version",
|
44
|
+
"is_async",
|
45
|
+
],
|
37
46
|
)
|
38
47
|
|
39
48
|
|
40
49
|
KartonOutputs = namedtuple("KartonOutputs", ["identity", "outputs"])
|
41
|
-
logger = logging.getLogger(
|
50
|
+
logger = logging.getLogger(__name__)
|
42
51
|
|
43
52
|
|
44
53
|
class KartonMetrics(enum.Enum):
|
@@ -103,13 +112,13 @@ class KartonServiceInfo:
|
|
103
112
|
)
|
104
113
|
|
105
114
|
|
106
|
-
class
|
115
|
+
class KartonBackendBase:
|
107
116
|
def __init__(
|
108
117
|
self,
|
109
118
|
config: Config,
|
110
119
|
identity: Optional[str] = None,
|
111
120
|
service_info: Optional[KartonServiceInfo] = None,
|
112
|
-
)
|
121
|
+
):
|
113
122
|
self.config = config
|
114
123
|
|
115
124
|
if identity is not None:
|
@@ -117,59 +126,6 @@ class KartonBackend:
|
|
117
126
|
self.identity = identity
|
118
127
|
|
119
128
|
self.service_info = service_info
|
120
|
-
self.redis = self.make_redis(
|
121
|
-
config, identity=identity, service_info=service_info
|
122
|
-
)
|
123
|
-
|
124
|
-
endpoint = config.get("s3", "address")
|
125
|
-
access_key = config.get("s3", "access_key")
|
126
|
-
secret_key = config.get("s3", "secret_key")
|
127
|
-
iam_auth = config.getboolean("s3", "iam_auth")
|
128
|
-
|
129
|
-
if not endpoint:
|
130
|
-
raise RuntimeError("Attempting to get S3 client without an endpoint set")
|
131
|
-
|
132
|
-
if access_key and secret_key and iam_auth:
|
133
|
-
logger.warning(
|
134
|
-
"Warning: iam is turned on and both S3 access key and secret key are"
|
135
|
-
" provided"
|
136
|
-
)
|
137
|
-
|
138
|
-
if iam_auth:
|
139
|
-
s3_client = self.iam_auth_s3(endpoint)
|
140
|
-
if s3_client:
|
141
|
-
self.s3 = s3_client
|
142
|
-
return
|
143
|
-
|
144
|
-
if access_key is None or secret_key is None:
|
145
|
-
raise RuntimeError(
|
146
|
-
"Attempting to get S3 client without an access_key/secret_key set"
|
147
|
-
)
|
148
|
-
|
149
|
-
self.s3 = boto3.client(
|
150
|
-
"s3",
|
151
|
-
endpoint_url=endpoint,
|
152
|
-
aws_access_key_id=access_key,
|
153
|
-
aws_secret_access_key=secret_key,
|
154
|
-
)
|
155
|
-
|
156
|
-
def iam_auth_s3(self, endpoint: str):
|
157
|
-
boto_session = get_session()
|
158
|
-
iam_providers = [
|
159
|
-
ContainerProvider(),
|
160
|
-
InstanceMetadataProvider(
|
161
|
-
iam_role_fetcher=InstanceMetadataFetcher(timeout=1000, num_attempts=2)
|
162
|
-
),
|
163
|
-
]
|
164
|
-
|
165
|
-
for provider in iam_providers:
|
166
|
-
creds = provider.load()
|
167
|
-
if creds:
|
168
|
-
boto_session._credentials = creds # type: ignore
|
169
|
-
return boto3.Session(botocore_session=boto_session).client(
|
170
|
-
"s3",
|
171
|
-
endpoint_url=endpoint,
|
172
|
-
)
|
173
129
|
|
174
130
|
@staticmethod
|
175
131
|
def _validate_identity(identity: str):
|
@@ -179,48 +135,6 @@ class KartonBackend:
|
|
179
135
|
f"Karton identity should not contain {disallowed_chars}"
|
180
136
|
)
|
181
137
|
|
182
|
-
@staticmethod
|
183
|
-
def make_redis(
|
184
|
-
config,
|
185
|
-
identity: Optional[str] = None,
|
186
|
-
service_info: Optional[KartonServiceInfo] = None,
|
187
|
-
) -> StrictRedis:
|
188
|
-
"""
|
189
|
-
Create and test a Redis connection.
|
190
|
-
|
191
|
-
:param config: The karton configuration
|
192
|
-
:param identity: Karton service identity
|
193
|
-
:param service_info: Additional service identity metadata
|
194
|
-
:return: Redis connection
|
195
|
-
"""
|
196
|
-
if service_info is not None:
|
197
|
-
client_name: Optional[str] = service_info.make_client_name()
|
198
|
-
else:
|
199
|
-
client_name = identity
|
200
|
-
|
201
|
-
redis_args = {
|
202
|
-
"host": config["redis"]["host"],
|
203
|
-
"port": config.getint("redis", "port", 6379),
|
204
|
-
"db": config.getint("redis", "db", 0),
|
205
|
-
"username": config.get("redis", "username"),
|
206
|
-
"password": config.get("redis", "password"),
|
207
|
-
"client_name": client_name,
|
208
|
-
# set socket_timeout to None if set to 0
|
209
|
-
"socket_timeout": config.getint("redis", "socket_timeout", 30) or None,
|
210
|
-
"decode_responses": True,
|
211
|
-
}
|
212
|
-
try:
|
213
|
-
redis = StrictRedis(**redis_args)
|
214
|
-
redis.ping()
|
215
|
-
except AuthenticationError:
|
216
|
-
# Maybe we've sent a wrong password.
|
217
|
-
# Or maybe the server is not (yet) password protected
|
218
|
-
# To make smooth transition possible, try to login insecurely
|
219
|
-
del redis_args["password"]
|
220
|
-
redis = StrictRedis(**redis_args)
|
221
|
-
redis.ping()
|
222
|
-
return redis
|
223
|
-
|
224
138
|
@property
|
225
139
|
def default_bucket_name(self) -> str:
|
226
140
|
bucket_name = self.config.get("s3", "bucket")
|
@@ -270,6 +184,7 @@ class KartonBackend:
|
|
270
184
|
"filters": bind.filters,
|
271
185
|
"persistent": bind.persistent,
|
272
186
|
"service_version": bind.service_version,
|
187
|
+
"is_async": bind.is_async,
|
273
188
|
},
|
274
189
|
sort_keys=True,
|
275
190
|
)
|
@@ -294,6 +209,7 @@ class KartonBackend:
|
|
294
209
|
persistent=not identity.endswith(".test"),
|
295
210
|
filters=bind,
|
296
211
|
service_version=None,
|
212
|
+
is_async=False,
|
297
213
|
)
|
298
214
|
return KartonBind(
|
299
215
|
identity=identity,
|
@@ -302,6 +218,7 @@ class KartonBackend:
|
|
302
218
|
persistent=bind["persistent"],
|
303
219
|
filters=bind["filters"],
|
304
220
|
service_version=bind.get("service_version"),
|
221
|
+
is_async=bind.get("is_async", False),
|
305
222
|
)
|
306
223
|
|
307
224
|
@staticmethod
|
@@ -316,6 +233,126 @@ class KartonBackend:
|
|
316
233
|
output = [json.loads(output_type) for output_type in output_data]
|
317
234
|
return KartonOutputs(identity=identity, outputs=output)
|
318
235
|
|
236
|
+
@staticmethod
|
237
|
+
def _log_channel(logger_name: Optional[str], level: Optional[str]) -> str:
|
238
|
+
return ".".join(
|
239
|
+
[KARTON_LOG_CHANNEL, (level or "*").lower(), logger_name or "*"]
|
240
|
+
)
|
241
|
+
|
242
|
+
|
243
|
+
class KartonBackend(KartonBackendBase):
|
244
|
+
def __init__(
|
245
|
+
self,
|
246
|
+
config: Config,
|
247
|
+
identity: Optional[str] = None,
|
248
|
+
service_info: Optional[KartonServiceInfo] = None,
|
249
|
+
) -> None:
|
250
|
+
super().__init__(config, identity, service_info)
|
251
|
+
self.redis = self.make_redis(
|
252
|
+
config, identity=identity, service_info=service_info
|
253
|
+
)
|
254
|
+
|
255
|
+
endpoint = config.get("s3", "address")
|
256
|
+
access_key = config.get("s3", "access_key")
|
257
|
+
secret_key = config.get("s3", "secret_key")
|
258
|
+
iam_auth = config.getboolean("s3", "iam_auth")
|
259
|
+
|
260
|
+
if not endpoint:
|
261
|
+
raise RuntimeError("Attempting to get S3 client without an endpoint set")
|
262
|
+
|
263
|
+
if access_key and secret_key and iam_auth:
|
264
|
+
logger.warning(
|
265
|
+
"Warning: iam is turned on and both S3 access key and secret key are"
|
266
|
+
" provided"
|
267
|
+
)
|
268
|
+
|
269
|
+
if iam_auth:
|
270
|
+
s3_client = self.iam_auth_s3(endpoint)
|
271
|
+
if s3_client:
|
272
|
+
self.s3 = s3_client
|
273
|
+
return
|
274
|
+
|
275
|
+
if access_key is None or secret_key is None:
|
276
|
+
raise RuntimeError(
|
277
|
+
"Attempting to get S3 client without an access_key/secret_key set"
|
278
|
+
)
|
279
|
+
|
280
|
+
self.s3 = boto3.client(
|
281
|
+
"s3",
|
282
|
+
endpoint_url=endpoint,
|
283
|
+
aws_access_key_id=access_key,
|
284
|
+
aws_secret_access_key=secret_key,
|
285
|
+
)
|
286
|
+
|
287
|
+
def iam_auth_s3(self, endpoint: str):
|
288
|
+
boto_session = get_session()
|
289
|
+
iam_providers = [
|
290
|
+
ContainerProvider(),
|
291
|
+
InstanceMetadataProvider(
|
292
|
+
iam_role_fetcher=InstanceMetadataFetcher(timeout=1000, num_attempts=2)
|
293
|
+
),
|
294
|
+
]
|
295
|
+
|
296
|
+
for provider in iam_providers:
|
297
|
+
creds = provider.load()
|
298
|
+
if creds:
|
299
|
+
boto_session._credentials = creds # type: ignore
|
300
|
+
return boto3.Session(botocore_session=boto_session).client(
|
301
|
+
"s3",
|
302
|
+
endpoint_url=endpoint,
|
303
|
+
)
|
304
|
+
|
305
|
+
@staticmethod
|
306
|
+
def make_redis(
|
307
|
+
config,
|
308
|
+
identity: Optional[str] = None,
|
309
|
+
service_info: Optional[KartonServiceInfo] = None,
|
310
|
+
) -> StrictRedis:
|
311
|
+
"""
|
312
|
+
Create and test a Redis connection.
|
313
|
+
|
314
|
+
:param config: The karton configuration
|
315
|
+
:param identity: Karton service identity
|
316
|
+
:param service_info: Additional service identity metadata
|
317
|
+
:return: Redis connection
|
318
|
+
"""
|
319
|
+
if service_info is not None:
|
320
|
+
client_name: Optional[str] = service_info.make_client_name()
|
321
|
+
else:
|
322
|
+
client_name = identity
|
323
|
+
|
324
|
+
redis_args = {
|
325
|
+
"host": config["redis"]["host"],
|
326
|
+
"port": config.getint("redis", "port", 6379),
|
327
|
+
"db": config.getint("redis", "db", 0),
|
328
|
+
"username": config.get("redis", "username"),
|
329
|
+
"password": config.get("redis", "password"),
|
330
|
+
"client_name": client_name,
|
331
|
+
# set socket_timeout to None if set to 0
|
332
|
+
"socket_timeout": config.getint("redis", "socket_timeout", 30) or None,
|
333
|
+
"decode_responses": True,
|
334
|
+
}
|
335
|
+
try:
|
336
|
+
redis = StrictRedis(**redis_args)
|
337
|
+
redis.ping()
|
338
|
+
except AuthenticationError:
|
339
|
+
# Maybe we've sent a wrong password.
|
340
|
+
# Or maybe the server is not (yet) password protected
|
341
|
+
# To make smooth transition possible, try to login insecurely
|
342
|
+
del redis_args["password"]
|
343
|
+
redis = StrictRedis(**redis_args)
|
344
|
+
redis.ping()
|
345
|
+
return redis
|
346
|
+
|
347
|
+
def unserialize_resource(self, resource_spec: Dict[str, Any]) -> RemoteResource:
|
348
|
+
"""
|
349
|
+
Unserializes resource into a RemoteResource object bound with current backend
|
350
|
+
|
351
|
+
:param resource_spec: Resource specification
|
352
|
+
:return: RemoteResource object
|
353
|
+
"""
|
354
|
+
return RemoteResource.from_dict(resource_spec, backend=self)
|
355
|
+
|
319
356
|
def get_bind(self, identity: str) -> KartonBind:
|
320
357
|
"""
|
321
358
|
Get bind object for given identity
|
@@ -426,7 +463,9 @@ class KartonBackend:
|
|
426
463
|
task_data = self.redis.get(f"{KARTON_TASK_NAMESPACE}:{task_uid}")
|
427
464
|
if not task_data:
|
428
465
|
return None
|
429
|
-
return Task.unserialize(
|
466
|
+
return Task.unserialize(
|
467
|
+
task_data, resource_unserializer=self.unserialize_resource
|
468
|
+
)
|
430
469
|
|
431
470
|
def get_tasks(
|
432
471
|
self,
|
@@ -449,7 +488,11 @@ class KartonBackend:
|
|
449
488
|
chunk_size,
|
450
489
|
)
|
451
490
|
return [
|
452
|
-
Task.unserialize(
|
491
|
+
Task.unserialize(
|
492
|
+
task_data,
|
493
|
+
parse_resources=parse_resources,
|
494
|
+
resource_unserializer=self.unserialize_resource,
|
495
|
+
)
|
453
496
|
for chunk in keys
|
454
497
|
for task_data in self.redis.mget(chunk)
|
455
498
|
if task_data is not None
|
@@ -464,7 +507,9 @@ class KartonBackend:
|
|
464
507
|
for chunk in chunks_iter(task_keys, chunk_size):
|
465
508
|
yield from (
|
466
509
|
Task.unserialize(
|
467
|
-
task_data,
|
510
|
+
task_data,
|
511
|
+
parse_resources=parse_resources,
|
512
|
+
resource_unserializer=self.unserialize_resource,
|
468
513
|
)
|
469
514
|
for task_data in self.redis.mget(chunk)
|
470
515
|
if task_data is not None
|
@@ -550,7 +595,9 @@ class KartonBackend:
|
|
550
595
|
lambda task: task.root_uid == root_uid,
|
551
596
|
(
|
552
597
|
Task.unserialize(
|
553
|
-
task_data,
|
598
|
+
task_data,
|
599
|
+
parse_resources=parse_resources,
|
600
|
+
resource_unserializer=self.unserialize_resource,
|
554
601
|
)
|
555
602
|
for task_data in self.redis.mget(chunk)
|
556
603
|
if task_data is not None
|
@@ -798,12 +845,6 @@ class KartonBackend:
|
|
798
845
|
p.execute()
|
799
846
|
return new_task
|
800
847
|
|
801
|
-
@staticmethod
|
802
|
-
def _log_channel(logger_name: Optional[str], level: Optional[str]) -> str:
|
803
|
-
return ".".join(
|
804
|
-
[KARTON_LOG_CHANNEL, (level or "*").lower(), logger_name or "*"]
|
805
|
-
)
|
806
|
-
|
807
848
|
def produce_log(
|
808
849
|
self,
|
809
850
|
log_record: Dict[str, Any],
|
@@ -1040,9 +1081,11 @@ class KartonBackend:
|
|
1040
1081
|
"""
|
1041
1082
|
deletion_chunks = chunks(
|
1042
1083
|
[
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1084
|
+
(
|
1085
|
+
{"Key": uid, "VersionId": version_id}
|
1086
|
+
if version_id != "null" or explicit_version_null
|
1087
|
+
else {"Key": uid}
|
1088
|
+
)
|
1046
1089
|
for uid, versions in object_versions.items()
|
1047
1090
|
for version_id in versions
|
1048
1091
|
],
|
@@ -1070,17 +1113,20 @@ class KartonBackend:
|
|
1070
1113
|
raise e
|
1071
1114
|
return False
|
1072
1115
|
|
1073
|
-
def log_identity_output(
|
1116
|
+
def log_identity_output(
|
1117
|
+
self, identity: str, headers: Dict[str, Any], task_tracking_ttl: int
|
1118
|
+
) -> None:
|
1074
1119
|
"""
|
1075
1120
|
Store the type of task outputted for given producer to
|
1076
1121
|
be used in tracking karton service connections.
|
1077
1122
|
|
1078
1123
|
:param identity: producer identity
|
1079
1124
|
:param headers: outputted headers
|
1125
|
+
:param task_tracking_ttl: expire time (in seconds)
|
1080
1126
|
"""
|
1081
1127
|
|
1082
1128
|
self.redis.sadd(f"{KARTON_OUTPUTS_NAMESPACE}:{identity}", json.dumps(headers))
|
1083
|
-
self.redis.expire(f"{KARTON_OUTPUTS_NAMESPACE}:{identity}",
|
1129
|
+
self.redis.expire(f"{KARTON_OUTPUTS_NAMESPACE}:{identity}", task_tracking_ttl)
|
1084
1130
|
|
1085
1131
|
def get_outputs(self) -> List[KartonOutputs]:
|
1086
1132
|
"""
|
karton/core/base.py
CHANGED
@@ -9,33 +9,20 @@ from typing import Optional, Union, cast
|
|
9
9
|
from .__version__ import __version__
|
10
10
|
from .backend import KartonBackend, KartonServiceInfo
|
11
11
|
from .config import Config
|
12
|
-
from .logger import KartonLogHandler
|
13
|
-
from .task import Task
|
12
|
+
from .logger import KartonLogHandler, TaskContextFilter
|
13
|
+
from .task import Task, get_current_task, set_current_task
|
14
14
|
from .utils import HardShutdownInterrupt, StrictClassMethod, graceful_killer
|
15
15
|
|
16
16
|
|
17
|
-
class
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
You can set an informative version information by setting the ``version`` class
|
22
|
-
attribute.
|
23
|
-
"""
|
24
|
-
|
25
|
-
#: Karton service identity
|
26
|
-
identity: str = ""
|
27
|
-
#: Karton service version
|
28
|
-
version: Optional[str] = None
|
29
|
-
#: Include extended service information for non-consumer services
|
30
|
-
with_service_info: bool = False
|
17
|
+
class ConfigMixin:
|
18
|
+
identity: Optional[str]
|
19
|
+
version: Optional[str]
|
31
20
|
|
32
|
-
def __init__(
|
33
|
-
self,
|
34
|
-
config: Optional[Config] = None,
|
35
|
-
identity: Optional[str] = None,
|
36
|
-
backend: Optional[KartonBackend] = None,
|
37
|
-
) -> None:
|
21
|
+
def __init__(self, config: Optional[Config] = None, identity: Optional[str] = None):
|
38
22
|
self.config = config or Config()
|
23
|
+
self.enable_publish_log = self.config.getboolean(
|
24
|
+
"logging", "enable_publish", True
|
25
|
+
)
|
39
26
|
|
40
27
|
# If not passed via constructor - get it from class
|
41
28
|
if identity is not None:
|
@@ -47,25 +34,81 @@ class KartonBase(abc.ABC):
|
|
47
34
|
|
48
35
|
self.debug = self.config.getboolean("karton", "debug", False)
|
49
36
|
|
50
|
-
if self.debug:
|
37
|
+
if self.debug and self.identity:
|
51
38
|
self.identity += "-" + os.urandom(4).hex() + "-dev"
|
52
39
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
)
|
40
|
+
@classmethod
|
41
|
+
def args_description(cls) -> str:
|
42
|
+
"""Return short description for argument parser."""
|
43
|
+
if not cls.__doc__:
|
44
|
+
return ""
|
45
|
+
return textwrap.dedent(cls.__doc__).strip().splitlines()[0]
|
60
46
|
|
61
|
-
|
62
|
-
|
47
|
+
@classmethod
|
48
|
+
def args_parser(cls) -> argparse.ArgumentParser:
|
49
|
+
"""
|
50
|
+
Return ArgumentParser for main() class method.
|
51
|
+
|
52
|
+
This method should be overridden and call super methods
|
53
|
+
if you want to add more arguments.
|
54
|
+
"""
|
55
|
+
parser = argparse.ArgumentParser(description=cls.args_description())
|
56
|
+
parser.add_argument(
|
57
|
+
"--version", action="version", version=cast(str, cls.version)
|
58
|
+
)
|
59
|
+
parser.add_argument("--config-file", help="Alternative configuration path")
|
60
|
+
parser.add_argument(
|
61
|
+
"--identity", help="Alternative identity for Karton service"
|
63
62
|
)
|
63
|
+
parser.add_argument("--log-level", help="Logging level of Karton logger")
|
64
|
+
parser.add_argument(
|
65
|
+
"--debug", help="Enable debugging mode", action="store_true", default=None
|
66
|
+
)
|
67
|
+
return parser
|
64
68
|
|
65
|
-
|
66
|
-
|
69
|
+
@classmethod
|
70
|
+
def config_from_args(cls, config: Config, args: argparse.Namespace) -> None:
|
71
|
+
"""
|
72
|
+
Updates configuration with settings from arguments
|
73
|
+
|
74
|
+
This method should be overridden and call super methods
|
75
|
+
if you want to add more arguments.
|
76
|
+
"""
|
77
|
+
config.load_from_dict(
|
78
|
+
{
|
79
|
+
"karton": {
|
80
|
+
"identity": args.identity,
|
81
|
+
"debug": args.debug,
|
82
|
+
},
|
83
|
+
"logging": {"level": args.log_level},
|
84
|
+
}
|
67
85
|
)
|
68
|
-
|
86
|
+
|
87
|
+
@classmethod
|
88
|
+
def karton_from_args(cls, args: Optional[argparse.Namespace] = None):
|
89
|
+
"""
|
90
|
+
Returns Karton instance configured using configuration files
|
91
|
+
and provided arguments
|
92
|
+
|
93
|
+
Used by :py:meth:`KartonServiceBase.main` method
|
94
|
+
"""
|
95
|
+
if args is None:
|
96
|
+
parser = cls.args_parser()
|
97
|
+
args = parser.parse_args()
|
98
|
+
config = Config(path=args.config_file)
|
99
|
+
cls.config_from_args(config, args)
|
100
|
+
return cls(config=config)
|
101
|
+
|
102
|
+
|
103
|
+
class LoggingMixin:
|
104
|
+
config: Config
|
105
|
+
identity: Optional[str]
|
106
|
+
debug: bool
|
107
|
+
enable_publish_log: bool
|
108
|
+
|
109
|
+
def __init__(self, log_handler: logging.Handler, log_format: str) -> None:
|
110
|
+
self._log_handler = log_handler
|
111
|
+
self._log_format = log_format
|
69
112
|
|
70
113
|
def setup_logger(self, level: Optional[Union[str, int]] = None) -> None:
|
71
114
|
"""
|
@@ -91,6 +134,7 @@ class KartonBase(abc.ABC):
|
|
91
134
|
self._log_handler.setFormatter(logging.Formatter())
|
92
135
|
|
93
136
|
logger = logging.getLogger(self.identity)
|
137
|
+
logger.addFilter(TaskContextFilter())
|
94
138
|
|
95
139
|
if logger.handlers:
|
96
140
|
# If logger already have handlers set: clear them
|
@@ -103,16 +147,14 @@ class KartonBase(abc.ABC):
|
|
103
147
|
|
104
148
|
logger.setLevel(log_level)
|
105
149
|
stream_handler = logging.StreamHandler()
|
106
|
-
stream_handler.setFormatter(
|
107
|
-
logging.Formatter("[%(asctime)s][%(levelname)s] %(message)s")
|
108
|
-
)
|
150
|
+
stream_handler.setFormatter(logging.Formatter(self._log_format))
|
109
151
|
logger.addHandler(stream_handler)
|
110
152
|
|
111
|
-
if not self.debug:
|
153
|
+
if not self.debug and self.enable_publish_log:
|
112
154
|
logger.addHandler(self._log_handler)
|
113
155
|
|
114
156
|
@property
|
115
|
-
def log_handler(self) ->
|
157
|
+
def log_handler(self) -> logging.Handler:
|
116
158
|
"""
|
117
159
|
Return KartonLogHandler bound to this Karton service.
|
118
160
|
|
@@ -138,67 +180,54 @@ class KartonBase(abc.ABC):
|
|
138
180
|
"""
|
139
181
|
return logging.getLogger(self.identity)
|
140
182
|
|
141
|
-
@classmethod
|
142
|
-
def args_description(cls) -> str:
|
143
|
-
"""Return short description for argument parser."""
|
144
|
-
if not cls.__doc__:
|
145
|
-
return ""
|
146
|
-
return textwrap.dedent(cls.__doc__).strip().splitlines()[0]
|
147
183
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
Return ArgumentParser for main() class method.
|
184
|
+
class KartonBase(abc.ABC, ConfigMixin, LoggingMixin):
|
185
|
+
"""
|
186
|
+
Base class for all Karton services
|
152
187
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
parser = argparse.ArgumentParser(description=cls.args_description())
|
157
|
-
parser.add_argument(
|
158
|
-
"--version", action="version", version=cast(str, cls.version)
|
159
|
-
)
|
160
|
-
parser.add_argument("--config-file", help="Alternative configuration path")
|
161
|
-
parser.add_argument(
|
162
|
-
"--identity", help="Alternative identity for Karton service"
|
163
|
-
)
|
164
|
-
parser.add_argument("--log-level", help="Logging level of Karton logger")
|
165
|
-
parser.add_argument(
|
166
|
-
"--debug", help="Enable debugging mode", action="store_true", default=None
|
167
|
-
)
|
168
|
-
return parser
|
188
|
+
You can set an informative version information by setting the ``version`` class
|
189
|
+
attribute.
|
190
|
+
"""
|
169
191
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
192
|
+
#: Karton service identity
|
193
|
+
identity: str = ""
|
194
|
+
#: Karton service version
|
195
|
+
version: Optional[str] = None
|
196
|
+
#: Include extended service information for non-consumer services
|
197
|
+
with_service_info: bool = False
|
174
198
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
199
|
+
def __init__(
|
200
|
+
self,
|
201
|
+
config: Optional[Config] = None,
|
202
|
+
identity: Optional[str] = None,
|
203
|
+
backend: Optional[KartonBackend] = None,
|
204
|
+
) -> None:
|
205
|
+
ConfigMixin.__init__(self, config, identity)
|
206
|
+
|
207
|
+
self.service_info = None
|
208
|
+
if self.identity is not None and self.with_service_info:
|
209
|
+
self.service_info = KartonServiceInfo(
|
210
|
+
identity=self.identity,
|
211
|
+
karton_version=__version__,
|
212
|
+
service_version=self.version,
|
213
|
+
)
|
214
|
+
|
215
|
+
self.backend = backend or KartonBackend(
|
216
|
+
self.config, identity=self.identity, service_info=self.service_info
|
186
217
|
)
|
187
218
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
and provided arguments
|
219
|
+
log_handler = KartonLogHandler(backend=self.backend, channel=self.identity)
|
220
|
+
LoggingMixin.__init__(
|
221
|
+
self, log_handler, log_format="[%(asctime)s][%(levelname)s] %(message)s"
|
222
|
+
)
|
193
223
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
return cls(config=config)
|
224
|
+
@property
|
225
|
+
def current_task(self) -> Optional[Task]:
|
226
|
+
return get_current_task()
|
227
|
+
|
228
|
+
@current_task.setter
|
229
|
+
def current_task(self, task: Optional[Task]):
|
230
|
+
set_current_task(task)
|
202
231
|
|
203
232
|
|
204
233
|
class KartonServiceBase(KartonBase):
|