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/config.py CHANGED
@@ -111,12 +111,15 @@ class Config(object):
111
111
  return True
112
112
 
113
113
  @overload
114
- def getint(self, section_name: str, option_name: str, fallback: int) -> int:
115
- ...
114
+ def getint(self, section_name: str, option_name: str, fallback: int) -> int: ...
116
115
 
117
116
  @overload
118
- def getint(self, section_name: str, option_name: str) -> Optional[int]:
119
- ...
117
+ def getint(self, section_name: str, option_name: str) -> Optional[int]: ...
118
+
119
+ @overload
120
+ def getint(
121
+ self, section_name: str, option_name: str, fallback: Optional[int]
122
+ ) -> Optional[int]: ...
120
123
 
121
124
  def getint(
122
125
  self, section_name: str, option_name: str, fallback: Optional[int] = None
@@ -131,12 +134,12 @@ class Config(object):
131
134
  return int(value)
132
135
 
133
136
  @overload
134
- def getboolean(self, section_name: str, option_name: str, fallback: bool) -> bool:
135
- ...
137
+ def getboolean(
138
+ self, section_name: str, option_name: str, fallback: bool
139
+ ) -> bool: ...
136
140
 
137
141
  @overload
138
- def getboolean(self, section_name: str, option_name: str) -> Optional[bool]:
139
- ...
142
+ def getboolean(self, section_name: str, option_name: str) -> Optional[bool]: ...
140
143
 
141
144
  def getboolean(
142
145
  self, section_name: str, option_name: str, fallback: Optional[bool] = None
karton/core/karton.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Base library for karton subsystems.
3
3
  """
4
+
4
5
  import abc
5
6
  import argparse
6
7
  import sys
@@ -136,7 +137,7 @@ class Consumer(KartonServiceBase):
136
137
  )
137
138
  if self.task_timeout is None:
138
139
  self.task_timeout = self.config.getint("karton", "task_timeout")
139
- self.current_task: Optional[Task] = None
140
+
140
141
  self._pre_hooks: List[Tuple[Optional[str], Callable[[Task], None]]] = []
141
142
  self._post_hooks: List[
142
143
  Tuple[
@@ -169,19 +170,18 @@ class Consumer(KartonServiceBase):
169
170
  """
170
171
 
171
172
  self.current_task = task
172
- self.log_handler.set_task(self.current_task)
173
173
 
174
- if not self.current_task.matches_filters(self.filters):
174
+ if not task.matches_filters(self.filters):
175
175
  self.log.info("Task rejected because binds are no longer valid.")
176
- self.backend.set_task_status(self.current_task, TaskState.FINISHED)
176
+ self.backend.set_task_status(task, TaskState.FINISHED)
177
177
  # Task rejected: end of processing
178
178
  return
179
179
 
180
180
  exception_str = None
181
181
 
182
182
  try:
183
- self.log.info("Received new task - %s", self.current_task.uid)
184
- self.backend.set_task_status(self.current_task, TaskState.STARTED)
183
+ self.log.info("Received new task - %s", task.uid)
184
+ self.backend.set_task_status(task, TaskState.STARTED)
185
185
 
186
186
  self._run_pre_hooks()
187
187
 
@@ -189,22 +189,22 @@ class Consumer(KartonServiceBase):
189
189
  try:
190
190
  if self.task_timeout:
191
191
  with timeout(self.task_timeout):
192
- self.process(self.current_task)
192
+ self.process(task)
193
193
  else:
194
- self.process(self.current_task)
194
+ self.process(task)
195
195
  except (Exception, TaskTimeoutError) as exc:
196
196
  saved_exception = exc
197
197
  raise
198
198
  finally:
199
199
  self._run_post_hooks(saved_exception)
200
200
 
201
- self.log.info("Task done - %s", self.current_task.uid)
201
+ self.log.info("Task done - %s", task.uid)
202
202
  except (Exception, TaskTimeoutError):
203
203
  exc_info = sys.exc_info()
204
204
  exception_str = traceback.format_exception(*exc_info)
205
205
 
206
206
  self.backend.increment_metrics(KartonMetrics.TASK_CRASHED, self.identity)
207
- self.log.exception("Failed to process task - %s", self.current_task.uid)
207
+ self.log.exception("Failed to process task - %s", task.uid)
208
208
  finally:
209
209
  self.backend.increment_metrics(KartonMetrics.TASK_CONSUMED, self.identity)
210
210
 
@@ -214,9 +214,10 @@ class Consumer(KartonServiceBase):
214
214
  # if an exception was caught while processing
215
215
  if exception_str is not None:
216
216
  task_state = TaskState.CRASHED
217
- self.current_task.error = exception_str
217
+ task.error = exception_str
218
218
 
219
- self.backend.set_task_status(self.current_task, task_state)
219
+ self.backend.set_task_status(task, task_state)
220
+ self.current_task = None
220
221
 
221
222
  @property
222
223
  def _bind(self) -> KartonBind:
@@ -227,6 +228,7 @@ class Consumer(KartonServiceBase):
227
228
  filters=self.filters,
228
229
  persistent=self.persistent,
229
230
  service_version=self.__class__.version,
231
+ is_async=False,
230
232
  )
231
233
 
232
234
  @classmethod
karton/core/logger.py CHANGED
@@ -2,30 +2,32 @@ import logging
2
2
  import platform
3
3
  import traceback
4
4
  import warnings
5
- from typing import Optional
5
+ from typing import Any, Callable, Dict
6
6
 
7
7
  from .backend import KartonBackend
8
- from .task import Task
8
+ from .task import get_current_task
9
9
 
10
10
  HOSTNAME = platform.node()
11
11
 
12
12
 
13
- class KartonLogHandler(logging.Handler):
13
+ class TaskContextFilter(logging.Filter):
14
14
  """
15
- logging.Handler that passes logs to the Karton backend.
15
+ This is a filter which injects information about current task ID to the log.
16
16
  """
17
17
 
18
- def __init__(self, backend: KartonBackend, channel: str) -> None:
19
- logging.Handler.__init__(self)
20
- self.backend = backend
21
- self.task: Optional[Task] = None
22
- self.is_consumer_active: bool = True
23
- self.channel: str = channel
18
+ def filter(self, record: logging.LogRecord) -> bool:
19
+ current_task = get_current_task()
20
+ if current_task is not None:
21
+ record.task_id = current_task.task_uid
22
+ else:
23
+ record.task_id = "(no task)"
24
+ return True
24
25
 
25
- def set_task(self, task: Task) -> None:
26
- self.task = task
27
26
 
28
- def emit(self, record: logging.LogRecord) -> None:
27
+ class LogLineFormatterMixin:
28
+ format: Callable[[logging.LogRecord], str]
29
+
30
+ def prepare_log_line(self, record: logging.LogRecord) -> Dict[str, Any]:
29
31
  ignore_fields = [
30
32
  "args",
31
33
  "asctime",
@@ -54,11 +56,27 @@ class KartonLogHandler(logging.Handler):
54
56
  log_line["type"] = "log"
55
57
  log_line["message"] = self.format(record)
56
58
 
57
- if self.task is not None:
58
- log_line["task"] = self.task.serialize()
59
+ current_task = get_current_task()
60
+ if current_task is not None:
61
+ log_line["task"] = current_task.serialize()
59
62
 
60
63
  log_line["hostname"] = HOSTNAME
64
+ return log_line
65
+
66
+
67
+ class KartonLogHandler(logging.Handler, LogLineFormatterMixin):
68
+ """
69
+ logging.Handler that passes logs to the Karton backend.
70
+ """
71
+
72
+ def __init__(self, backend: KartonBackend, channel: str) -> None:
73
+ logging.Handler.__init__(self)
74
+ self.backend = backend
75
+ self.is_consumer_active: bool = True
76
+ self.channel: str = channel
61
77
 
78
+ def emit(self, record: logging.LogRecord) -> None:
79
+ log_line = self.prepare_log_line(record)
62
80
  log_consumed = self.backend.produce_log(
63
81
  log_line, logger_name=self.channel, level=record.levelname
64
82
  )
karton/core/resource.py CHANGED
@@ -39,6 +39,7 @@ class ResourceBase(object):
39
39
  bucket: Optional[str] = None,
40
40
  metadata: Optional[Dict[str, Any]] = None,
41
41
  sha256: Optional[str] = None,
42
+ fd: Optional[IO[bytes]] = None,
42
43
  _uid: Optional[str] = None,
43
44
  _size: Optional[int] = None,
44
45
  _flags: Optional[List[str]] = None,
@@ -66,6 +67,16 @@ class ResourceBase(object):
66
67
  for byte_block in iter(lambda: f.read(4096), b""):
67
68
  sha256_hash.update(byte_block)
68
69
  sha256 = sha256_hash.hexdigest()
70
+ elif fd is not None:
71
+ if calculate_hash:
72
+ # we need to calculate the whole hash and return pos as it was
73
+ sha256_hash = hashlib.sha256()
74
+ last_position = fd.tell()
75
+ fd.seek(0)
76
+ for byte_block in iter(lambda: fd.read(4096), b""):
77
+ sha256_hash.update(byte_block)
78
+ sha256 = sha256_hash.hexdigest()
79
+ fd.seek(last_position)
69
80
  elif content:
70
81
  if isinstance(content, str):
71
82
  self._content = content.encode()
@@ -139,34 +150,7 @@ class ResourceBase(object):
139
150
  }
140
151
 
141
152
 
142
- class LocalResource(ResourceBase):
143
- """
144
- Represents local resource with arbitrary binary data e.g. file contents.
145
-
146
- Local resources will be uploaded to object hub (S3) during
147
- task dispatching.
148
-
149
- .. code-block:: python
150
-
151
- # Creating resource from bytes
152
- sample = Resource("original_name.exe", content=b"X5O!P%@AP[4\\
153
- PZX54(P^)7CC)7}$EICAR-STANDARD-ANT...")
154
-
155
- # Creating resource from path
156
- sample = Resource("original_name.exe", path="sample/original_name.exe")
157
-
158
- :param name: Name of the resource (e.g. name of file)
159
- :param content: Resource content
160
- :param path: Path of file with resource content
161
- :param bucket: Alternative S3 bucket for resource
162
- :param metadata: Resource metadata
163
- :param uid: Alternative S3 resource id
164
- :param sha256: Resource sha256 hash
165
- :param fd: Seekable file descriptor
166
- :param _flags: Resource flags
167
- :param _close_fd: Close file descriptor after upload (default: False)
168
- """
169
-
153
+ class LocalResourceBase(ResourceBase):
170
154
  def __init__(
171
155
  self,
172
156
  name: str,
@@ -183,13 +167,14 @@ class LocalResource(ResourceBase):
183
167
  if len(list(filter(None, [path, content, fd]))) != 1:
184
168
  raise ValueError("You must exclusively provide a path, content or fd")
185
169
 
186
- super(LocalResource, self).__init__(
170
+ super().__init__(
187
171
  name,
188
172
  content=content,
189
173
  path=path,
190
174
  bucket=bucket,
191
175
  metadata=metadata,
192
176
  sha256=sha256,
177
+ fd=fd,
193
178
  _uid=uid,
194
179
  _flags=_flags,
195
180
  )
@@ -235,7 +220,7 @@ class LocalResource(ResourceBase):
235
220
  bucket: Optional[str] = None,
236
221
  metadata: Optional[Dict[str, Any]] = None,
237
222
  uid: Optional[str] = None,
238
- ) -> "LocalResource":
223
+ ) -> "LocalResourceBase":
239
224
  """
240
225
  Resource extension, allowing to pass whole directory as a zipped resource.
241
226
 
@@ -293,6 +278,35 @@ class LocalResource(ResourceBase):
293
278
  _close_fd=True,
294
279
  )
295
280
 
281
+
282
+ class LocalResource(LocalResourceBase):
283
+ """
284
+ Represents local resource with arbitrary binary data e.g. file contents.
285
+
286
+ Local resources will be uploaded to object hub (S3) during
287
+ task dispatching.
288
+
289
+ .. code-block:: python
290
+
291
+ # Creating resource from bytes
292
+ sample = Resource("original_name.exe", content=b"X5O!P%@AP[4\\
293
+ PZX54(P^)7CC)7}$EICAR-STANDARD-ANT...")
294
+
295
+ # Creating resource from path
296
+ sample = Resource("original_name.exe", path="sample/original_name.exe")
297
+
298
+ :param name: Name of the resource (e.g. name of file)
299
+ :param content: Resource content
300
+ :param path: Path of file with resource content
301
+ :param bucket: Alternative S3 bucket for resource
302
+ :param metadata: Resource metadata
303
+ :param uid: Alternative S3 resource id
304
+ :param sha256: Resource sha256 hash
305
+ :param fd: Seekable file descriptor
306
+ :param _flags: Resource flags
307
+ :param _close_fd: Close file descriptor after upload (default: False)
308
+ """
309
+
296
310
  def _upload(self, backend: "KartonBackend") -> None:
297
311
  """Internal function for uploading resources
298
312
 
karton/core/task.py CHANGED
@@ -3,6 +3,7 @@ import json
3
3
  import time
4
4
  import uuid
5
5
  import warnings
6
+ from contextvars import ContextVar
6
7
  from typing import (
7
8
  TYPE_CHECKING,
8
9
  Any,
@@ -24,6 +25,16 @@ if TYPE_CHECKING:
24
25
 
25
26
  import orjson
26
27
 
28
+ current_task: ContextVar[Optional["Task"]] = ContextVar("current_task")
29
+
30
+
31
+ def get_current_task() -> Optional["Task"]:
32
+ return current_task.get(None)
33
+
34
+
35
+ def set_current_task(task: Optional["Task"]):
36
+ current_task.set(task)
37
+
27
38
 
28
39
  class TaskState(enum.Enum):
29
40
  DECLARED = "Declared" # Task declared in TASKS_QUEUE
@@ -375,12 +386,15 @@ class Task(object):
375
386
  data: Union[str, bytes],
376
387
  backend: Optional["KartonBackend"] = None,
377
388
  parse_resources: bool = True,
389
+ resource_unserializer: Optional[Callable[[Dict], Any]] = None,
378
390
  ) -> "Task":
379
391
  """
380
392
  Unserialize Task instance from JSON string
381
393
 
382
394
  :param data: JSON-serialized task
383
- :param backend: Backend instance to be bound to RemoteResource objects
395
+ :param backend: |
396
+ Backend instance to be bound to RemoteResource objects.
397
+ Deprecated: pass resource_unserializer instead.
384
398
  :param parse_resources: |
385
399
  If set to False (default is True), method doesn't
386
400
  deserialize '__karton_resource__' entries, which speeds up deserialization
@@ -388,6 +402,9 @@ class Task(object):
388
402
  filtering based on status.
389
403
  When resource deserialization is turned off, Task.unserialize will try
390
404
  to use faster 3rd-party JSON parser (orjson).
405
+ :param resource_unserializer: |
406
+ Resource factory used for deserialization of __karton_resource__
407
+ dictionary values.
391
408
  :return: Unserialized Task object
392
409
 
393
410
  :meta private:
@@ -399,7 +416,12 @@ class Task(object):
399
416
  RemoteResource object instances
400
417
  """
401
418
  if isinstance(value, dict) and "__karton_resource__" in value:
402
- return RemoteResource.from_dict(value["__karton_resource__"], backend)
419
+ if resource_unserializer is None:
420
+ return RemoteResource.from_dict(
421
+ value["__karton_resource__"], backend
422
+ )
423
+ else:
424
+ return resource_unserializer(value["__karton_resource__"])
403
425
  return value
404
426
 
405
427
  if not isinstance(data, str):
karton/core/test.py CHANGED
@@ -1,11 +1,12 @@
1
1
  """
2
2
  Test stubs for karton subsystem unit tests
3
3
  """
4
+
4
5
  import hashlib
5
6
  import logging
6
7
  import unittest
7
8
  from collections import defaultdict
8
- from typing import Any, BinaryIO, Dict, List, Union, cast
9
+ from typing import Any, BinaryIO, Dict, List, Optional, Union, cast
9
10
  from unittest import mock
10
11
 
11
12
  from .backend import KartonBackend, KartonMetrics
@@ -66,7 +67,7 @@ class BackendMock:
66
67
  bucket: str,
67
68
  object_uid: str,
68
69
  content: Union[bytes, BinaryIO],
69
- length: int = None,
70
+ length: Optional[int] = None,
70
71
  ) -> None:
71
72
  log.debug("Uploading object %s to bucket %s", object_uid, bucket)
72
73
  if isinstance(content, bytes):
karton/system/system.py CHANGED
@@ -31,6 +31,7 @@ class SystemService(KartonServiceBase):
31
31
  TASK_DISPATCHED_TIMEOUT = 24 * 3600
32
32
  TASK_STARTED_TIMEOUT = 24 * 3600
33
33
  TASK_CRASHED_TIMEOUT = 3 * 24 * 3600
34
+ TASK_TRACKING_TTL = 30 * 24 * 3600
34
35
 
35
36
  def __init__(self, config: Optional[Config]) -> None:
36
37
  super().__init__(config=config)
@@ -52,6 +53,12 @@ class SystemService(KartonServiceBase):
52
53
  self.enable_null_version_deletion = self.config.getboolean(
53
54
  "system", "enable_null_version_deletion", False
54
55
  )
56
+ self.enable_task_tracking = self.config.getboolean(
57
+ "system", "enable_task_tracking", True
58
+ )
59
+ self.task_tracking_ttl = self.config.getint(
60
+ "system", "task_tracking_ttl", self.TASK_TRACKING_TTL
61
+ )
55
62
 
56
63
  self.last_gc_trigger = time.time()
57
64
 
@@ -65,6 +72,8 @@ class SystemService(KartonServiceBase):
65
72
  " enable_gc:\t%s\n"
66
73
  " enable_router:\t%s\n"
67
74
  " enable_null_version_deletion:\t%s\n"
75
+ " enable_task_tracking:\t%s\n"
76
+ " task_tracking_ttl:\t%s\n"
68
77
  " crash_started_tasks_on_timeout:\t%s",
69
78
  self.gc_interval,
70
79
  self.task_dispatched_timeout,
@@ -73,6 +82,8 @@ class SystemService(KartonServiceBase):
73
82
  self.enable_gc,
74
83
  self.enable_router,
75
84
  self.enable_null_version_deletion,
85
+ self.enable_task_tracking,
86
+ self.task_tracking_ttl,
76
87
  self.crash_started_tasks_on_timeout,
77
88
  )
78
89
 
@@ -227,10 +238,13 @@ class SystemService(KartonServiceBase):
227
238
  def route_task(self, task: Task, binds: List[KartonBind]) -> None:
228
239
  # Performs routing of task
229
240
  self.log.info("[%s] Processing task %s", task.root_uid, task.task_uid)
230
- # store the producer-task relationship in redis for task tracking
231
- self.backend.log_identity_output(
232
- task.headers.get("origin", "unknown"), task.headers
233
- )
241
+ # if enabled, store the producer-task relationship in redis for task tracking
242
+ if self.enable_task_tracking:
243
+ self.backend.log_identity_output(
244
+ task.headers.get("origin", "unknown"),
245
+ task.headers,
246
+ self.task_tracking_ttl,
247
+ )
234
248
 
235
249
  pipe = self.backend.make_pipeline()
236
250
  for bind in binds:
@@ -285,9 +299,10 @@ class SystemService(KartonServiceBase):
285
299
  operation_bodies.append(operation_body)
286
300
 
287
301
  self.backend.register_tasks(tasks)
288
- self.backend.produce_logs(
289
- operation_bodies, logger_name=KARTON_OPERATIONS_QUEUE, level="INFO"
290
- )
302
+ if self.enable_publish_log:
303
+ self.backend.produce_logs(
304
+ operation_bodies, logger_name=KARTON_OPERATIONS_QUEUE, level="INFO"
305
+ )
291
306
 
292
307
  def process_routing(self) -> None:
293
308
  # Order does matter! task dispatching must be before
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: karton-core
3
- Version: 5.6.1
3
+ Version: 5.8.0
4
4
  Summary: Distributed malware analysis orchestration framework
5
5
  Home-page: https://github.com/CERT-Polska/karton
6
6
  Classifier: Programming Language :: Python :: 3
@@ -9,7 +9,8 @@ Classifier: License :: OSI Approved :: BSD License
9
9
  Requires-Python: >=3.8
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
- Requires-Dist: boto3 <1.36.0
12
+ Requires-Dist: aioboto3 ==14.3.0
13
+ Requires-Dist: boto3 <1.37.4,>=1.37.2
13
14
  Requires-Dist: orjson
14
15
  Requires-Dist: redis
15
16
 
@@ -0,0 +1,33 @@
1
+ karton_core-5.8.0-nspkg.pth,sha256=vHa-jm6pBTeInFrmnsHMg9AOeD88czzQy-6QCFbpRcM,539
2
+ karton/core/__init__.py,sha256=QuT0BWZyp799eY90tK3H1OD2hwuusqMJq8vQwpB3kG4,337
3
+ karton/core/__version__.py,sha256=duDTiv1rL8Ee9_jtzzhpq-j4xkkpVPkUh2Daa_Ou-xA,22
4
+ karton/core/backend.py,sha256=3tmjAM35jaoZr_5QqarcVH81LvKMEB63vyvMKCYXPCM,39934
5
+ karton/core/base.py,sha256=6PEQiEWHjMtJKRj0dfgEEpNhgSRoKOBYF1WBlrmyBp0,9064
6
+ karton/core/config.py,sha256=UTd0hLqYNUttfI7FYUDOJPaz-C3uj1Kw7xxataTG_OM,8234
7
+ karton/core/exceptions.py,sha256=8i9WVzi4PinNlX10Cb-lQQC35Hl-JB5R_UKXa9AUKoQ,153
8
+ karton/core/inspect.py,sha256=aIJQEOEkD5q2xLlV8nhxY5qL5zqcnprP-2DdP6ecKlE,6150
9
+ karton/core/karton.py,sha256=4CISOmUTfaEaCJUtbYxJSBMzydT27o3a-R14VBNpmr0,15269
10
+ karton/core/logger.py,sha256=UhYCoVARXaatvoJ2lO2mfBHeODOS7z8O-vqdeQhNmV4,2654
11
+ karton/core/main.py,sha256=ir1-dhn3vbwfh2YHiM6ZYfRBbjwLvJSz0d8tuK1mb_4,8310
12
+ karton/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ karton/core/query.py,sha256=sf24DweVlXfJuBbBD_ns2LXhOV-IBwuPG3jBfTJu77s,12063
14
+ karton/core/resource.py,sha256=GkU3JGSP1kOsAoblon4BWbQp31eZiDsCjaaDKanEokk,20872
15
+ karton/core/task.py,sha256=QvAsSUIsyYiRmkgxgugrYkHWH2gczrFDfw2V4izdt1E,19566
16
+ karton/core/test.py,sha256=cj6W4gNt0BpRjsYiiBt0hPE8dmRfUeIc8sSVkxB50cU,9123
17
+ karton/core/utils.py,sha256=sEVqGdVPyYswWuVn8wYXBQmln8Az826N_2HgC__pmW8,4090
18
+ karton/core/asyncio/__init__.py,sha256=ZgndeKzS3Yg2o8hebwFYJWlCRdW3ImdCOShK4EVmZ14,457
19
+ karton/core/asyncio/backend.py,sha256=GF0z9YxWvUKYkvnBatCDthX0M9Kwt9cRfVSWQq5bc9E,12751
20
+ karton/core/asyncio/base.py,sha256=YDNGyWzgVvt2TnfKvHYbJbcNJaQl95bdBq45YGEo-3Q,4246
21
+ karton/core/asyncio/karton.py,sha256=LhzMGuJsmXdTEa323gZted8KgVfHH6l0j0_tTqMh4Z4,12932
22
+ karton/core/asyncio/logger.py,sha256=BjkbuAeWylTmFjWv8-ckmOGf4nL2Tma96W0nIOc2vwk,1752
23
+ karton/core/asyncio/resource.py,sha256=86AYm7JeVjEYRNw--h02HIS9xFvgddhktmDUp0qvTO4,12517
24
+ karton/system/__init__.py,sha256=JF51OqRU_Y4c0unOulvmv1KzSHSq4ZpXU8ZsH4nefRM,63
25
+ karton/system/__main__.py,sha256=QJkwIlSwaPRdzwKlNmCAL41HtDAa73db9MZKWmOfxGM,56
26
+ karton/system/system.py,sha256=d_5hhLTthJdr_4gZEGQ6Y-kHvxeBqyQxjjx_wRs3xMA,17285
27
+ karton_core-5.8.0.dist-info/LICENSE,sha256=o8h7hYhn7BJC_-DmrfqWwLjaR_Gbe0TZOOQJuN2ca3I,1519
28
+ karton_core-5.8.0.dist-info/METADATA,sha256=26n1VrX-0ESRMG7fug5hqKxYT1URIa_NgypF5N1E2gg,6860
29
+ karton_core-5.8.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
30
+ karton_core-5.8.0.dist-info/entry_points.txt,sha256=OgLlsXy61GP6-Yob3oXqeJ2hlRU6LBLj33fr0NufKz0,98
31
+ karton_core-5.8.0.dist-info/namespace_packages.txt,sha256=X8SslCPsqXDCnGZqrYYolzT3xPzJMq1r-ZQSc0jfAEA,7
32
+ karton_core-5.8.0.dist-info/top_level.txt,sha256=X8SslCPsqXDCnGZqrYYolzT3xPzJMq1r-ZQSc0jfAEA,7
33
+ karton_core-5.8.0.dist-info/RECORD,,
@@ -1,27 +0,0 @@
1
- karton_core-5.6.1-nspkg.pth,sha256=vHa-jm6pBTeInFrmnsHMg9AOeD88czzQy-6QCFbpRcM,539
2
- karton/core/__init__.py,sha256=QuT0BWZyp799eY90tK3H1OD2hwuusqMJq8vQwpB3kG4,337
3
- karton/core/__version__.py,sha256=-q9tSF5ofTJum4PMjvbhaE1xmTXehc_9rxMGcmfodcw,22
4
- karton/core/backend.py,sha256=g0BSQBsFAksRd_VY5QDjBJ8yIIyzAmwxy-kfJgAZ_lo,38628
5
- karton/core/base.py,sha256=C6Lco3E0XCsxvEjeVOLR9fxh_IWJ1vjC9BqUYsQyewE,8083
6
- karton/core/config.py,sha256=7oKchitq6pWzPuXRfjBXqVT_BgGIz2p-CDo1RGaNJQg,8118
7
- karton/core/exceptions.py,sha256=8i9WVzi4PinNlX10Cb-lQQC35Hl-JB5R_UKXa9AUKoQ,153
8
- karton/core/inspect.py,sha256=aIJQEOEkD5q2xLlV8nhxY5qL5zqcnprP-2DdP6ecKlE,6150
9
- karton/core/karton.py,sha256=Fi3wNqMGiKvHN2BECsqsvfxkiyuwPdlC21jpqQdkeak,15434
10
- karton/core/logger.py,sha256=J3XAyG88U0cwYC9zR6E3QD1uJenrQh7zS9-HgxhqeAs,2040
11
- karton/core/main.py,sha256=ir1-dhn3vbwfh2YHiM6ZYfRBbjwLvJSz0d8tuK1mb_4,8310
12
- karton/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- karton/core/query.py,sha256=sf24DweVlXfJuBbBD_ns2LXhOV-IBwuPG3jBfTJu77s,12063
14
- karton/core/resource.py,sha256=tA3y_38H9HVKIrCeAU70zHUkQUv0BuCQWMC470JLxxc,20321
15
- karton/core/task.py,sha256=gW1szMi5PN2Y06X-Ryo7cmEVluZv1r7W5tvmwIJiD94,18808
16
- karton/core/test.py,sha256=tms-YM7sUKQDHN0vm2_W7DIvHnO_ld_VPsWHnsbKSfk,9102
17
- karton/core/utils.py,sha256=sEVqGdVPyYswWuVn8wYXBQmln8Az826N_2HgC__pmW8,4090
18
- karton/system/__init__.py,sha256=JF51OqRU_Y4c0unOulvmv1KzSHSq4ZpXU8ZsH4nefRM,63
19
- karton/system/__main__.py,sha256=QJkwIlSwaPRdzwKlNmCAL41HtDAa73db9MZKWmOfxGM,56
20
- karton/system/system.py,sha256=cFE4hCS0LWnwdCiIjU0ym8dHujE5ORi4REJR_y5b2gA,16671
21
- karton_core-5.6.1.dist-info/LICENSE,sha256=o8h7hYhn7BJC_-DmrfqWwLjaR_Gbe0TZOOQJuN2ca3I,1519
22
- karton_core-5.6.1.dist-info/METADATA,sha256=AJoa9O_0SOYI3IuVHXhwB6lXoUSs7S4nU6QM8_xHxVI,6818
23
- karton_core-5.6.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
24
- karton_core-5.6.1.dist-info/entry_points.txt,sha256=OgLlsXy61GP6-Yob3oXqeJ2hlRU6LBLj33fr0NufKz0,98
25
- karton_core-5.6.1.dist-info/namespace_packages.txt,sha256=X8SslCPsqXDCnGZqrYYolzT3xPzJMq1r-ZQSc0jfAEA,7
26
- karton_core-5.6.1.dist-info/top_level.txt,sha256=X8SslCPsqXDCnGZqrYYolzT3xPzJMq1r-ZQSc0jfAEA,7
27
- karton_core-5.6.1.dist-info/RECORD,,