karton-core 5.3.3__py3-none-any.whl → 5.4.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/backend.py +56 -6
- karton/core/inspect.py +66 -28
- karton/core/karton.py +10 -6
- karton/core/task.py +76 -35
- karton/system/system.py +3 -3
- {karton_core-5.3.3.dist-info → karton_core-5.4.0.dist-info}/METADATA +1 -1
- {karton_core-5.3.3.dist-info → karton_core-5.4.0.dist-info}/RECORD +14 -14
- /karton_core-5.3.3-nspkg.pth → /karton_core-5.4.0-nspkg.pth +0 -0
- {karton_core-5.3.3.dist-info → karton_core-5.4.0.dist-info}/LICENSE +0 -0
- {karton_core-5.3.3.dist-info → karton_core-5.4.0.dist-info}/WHEEL +0 -0
- {karton_core-5.3.3.dist-info → karton_core-5.4.0.dist-info}/entry_points.txt +0 -0
- {karton_core-5.3.3.dist-info → karton_core-5.4.0.dist-info}/namespace_packages.txt +0 -0
- {karton_core-5.3.3.dist-info → karton_core-5.4.0.dist-info}/top_level.txt +0 -0
karton/core/__version__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "5.
|
1
|
+
__version__ = "5.4.0"
|
karton/core/backend.py
CHANGED
@@ -449,9 +449,7 @@ class KartonBackend:
|
|
449
449
|
chunk_size,
|
450
450
|
)
|
451
451
|
return [
|
452
|
-
Task.unserialize(task_data, backend=self)
|
453
|
-
if parse_resources
|
454
|
-
else Task.unserialize(task_data, parse_resources=False)
|
452
|
+
Task.unserialize(task_data, backend=self, parse_resources=parse_resources)
|
455
453
|
for chunk in keys
|
456
454
|
for task_data in self.redis.mget(chunk)
|
457
455
|
if task_data is not None
|
@@ -465,9 +463,9 @@ class KartonBackend:
|
|
465
463
|
) -> Iterator[Task]:
|
466
464
|
for chunk in chunks_iter(task_keys, chunk_size):
|
467
465
|
yield from (
|
468
|
-
Task.unserialize(
|
469
|
-
|
470
|
-
|
466
|
+
Task.unserialize(
|
467
|
+
task_data, backend=self, parse_resources=parse_resources
|
468
|
+
)
|
471
469
|
for task_data in self.redis.mget(chunk)
|
472
470
|
if task_data is not None
|
473
471
|
)
|
@@ -534,6 +532,58 @@ class KartonBackend:
|
|
534
532
|
self.iter_all_tasks(chunk_size=chunk_size, parse_resources=parse_resources)
|
535
533
|
)
|
536
534
|
|
535
|
+
def _iter_legacy_task_tree(
|
536
|
+
self, root_uid: str, chunk_size: int = 1000, parse_resources: bool = True
|
537
|
+
) -> Iterator[Task]:
|
538
|
+
"""
|
539
|
+
Processes tasks made by <5.4.0 (unrouted from <5.4.0 producers or existing
|
540
|
+
before upgrade)
|
541
|
+
|
542
|
+
Used internally by iter_task_tree.
|
543
|
+
"""
|
544
|
+
# Iterate over all karton tasks that do not match the new task id format
|
545
|
+
legacy_task_keys = self.redis.scan_iter(
|
546
|
+
match=f"{KARTON_TASK_NAMESPACE}:[^{{]*", count=chunk_size
|
547
|
+
)
|
548
|
+
for chunk in chunks_iter(legacy_task_keys, chunk_size):
|
549
|
+
yield from filter(
|
550
|
+
lambda task: task.root_uid == root_uid,
|
551
|
+
(
|
552
|
+
Task.unserialize(
|
553
|
+
task_data, backend=self, parse_resources=parse_resources
|
554
|
+
)
|
555
|
+
for task_data in self.redis.mget(chunk)
|
556
|
+
if task_data is not None
|
557
|
+
),
|
558
|
+
)
|
559
|
+
|
560
|
+
def iter_task_tree(
|
561
|
+
self, root_uid: str, chunk_size: int = 1000, parse_resources: bool = True
|
562
|
+
) -> Iterator[Task]:
|
563
|
+
"""
|
564
|
+
Iterates all tasks that belong to the same analysis task tree
|
565
|
+
and have the same root_uid
|
566
|
+
|
567
|
+
:param root_uid: Root identifier of task tree
|
568
|
+
:param chunk_size: Size of chunks passed to the Redis SCAN and MGET command
|
569
|
+
:param parse_resources: If set to False, resources are not parsed.
|
570
|
+
It speeds up deserialization. Read :py:meth:`Task.unserialize` documentation
|
571
|
+
to learn more.
|
572
|
+
:return: Iterator with task objects
|
573
|
+
"""
|
574
|
+
# Process <5.4.0 tasks (unrouted from <5.4.0 producers
|
575
|
+
# or existing before upgrade)
|
576
|
+
yield from self._iter_legacy_task_tree(
|
577
|
+
root_uid, chunk_size=chunk_size, parse_resources=parse_resources
|
578
|
+
)
|
579
|
+
# Process >=5.4.0 tasks
|
580
|
+
task_keys = self.redis.scan_iter(
|
581
|
+
match=f"{KARTON_TASK_NAMESPACE}:{{{root_uid}}}:*", count=chunk_size
|
582
|
+
)
|
583
|
+
yield from self._iter_tasks(
|
584
|
+
task_keys, chunk_size=chunk_size, parse_resources=parse_resources
|
585
|
+
)
|
586
|
+
|
537
587
|
def register_task(self, task: Task, pipe: Optional[Pipeline] = None) -> None:
|
538
588
|
"""
|
539
589
|
Register or update task in Redis.
|
karton/core/inspect.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from collections import defaultdict
|
2
|
-
from typing import Dict, List
|
2
|
+
from typing import Dict, List, Optional
|
3
3
|
|
4
4
|
from .backend import KartonBackend, KartonBind
|
5
5
|
from .task import Task, TaskState
|
@@ -9,9 +9,9 @@ class KartonQueue:
|
|
9
9
|
"""
|
10
10
|
View object representing a Karton queue
|
11
11
|
|
12
|
-
:param bind: :
|
12
|
+
:param bind: :class:`KartonBind` object representing the queue bind
|
13
13
|
:param tasks: List of tasks currently in queue
|
14
|
-
:param state: :
|
14
|
+
:param state: :class:`KartonState` object to be used
|
15
15
|
"""
|
16
16
|
|
17
17
|
def __init__(
|
@@ -48,7 +48,7 @@ class KartonAnalysis:
|
|
48
48
|
|
49
49
|
:param root_uid: Analysis root task uid
|
50
50
|
:param tasks: List of tasks
|
51
|
-
:param state: :
|
51
|
+
:param state: :class:`KartonState` object to be used
|
52
52
|
"""
|
53
53
|
|
54
54
|
def __init__(self, root_uid: str, tasks: List[Task], state: "KartonState") -> None:
|
@@ -89,7 +89,7 @@ def get_queues_for_tasks(
|
|
89
89
|
Group task objects by their queue name
|
90
90
|
|
91
91
|
:param tasks: Task objects to group
|
92
|
-
:param state: :
|
92
|
+
:param state: :class:`KartonState` object to be used
|
93
93
|
:return: A dictionary containing the queue names and lists of tasks
|
94
94
|
"""
|
95
95
|
tasks_per_queue = defaultdict(list)
|
@@ -119,30 +119,68 @@ class KartonState:
|
|
119
119
|
:param backend: :py:meth:`KartonBackend` object to use for data fetching
|
120
120
|
"""
|
121
121
|
|
122
|
-
def __init__(self, backend: KartonBackend) -> None:
|
122
|
+
def __init__(self, backend: KartonBackend, parse_resources: bool = False) -> None:
|
123
123
|
self.backend = backend
|
124
124
|
self.binds = {bind.identity: bind for bind in backend.get_binds()}
|
125
125
|
self.replicas = backend.get_online_consumers()
|
126
|
-
self.
|
127
|
-
|
128
|
-
|
129
|
-
]
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
126
|
+
self.parse_resources = parse_resources
|
127
|
+
|
128
|
+
self._tasks: Optional[List[Task]] = None
|
129
|
+
self._pending_tasks: Optional[List[Task]] = None
|
130
|
+
self._analyses: Optional[Dict[str, KartonAnalysis]] = None
|
131
|
+
self._queues: Optional[Dict[str, KartonQueue]] = None
|
132
|
+
|
133
|
+
@property
|
134
|
+
def tasks(self) -> List[Task]:
|
135
|
+
if self._tasks is None:
|
136
|
+
self._tasks = self.backend.get_all_tasks(
|
137
|
+
parse_resources=self.parse_resources
|
138
|
+
)
|
139
|
+
return self._tasks
|
140
|
+
|
141
|
+
@property
|
142
|
+
def pending_tasks(self) -> List[Task]:
|
143
|
+
if self._pending_tasks is None:
|
144
|
+
self._pending_tasks = [
|
145
|
+
task for task in self.tasks if task.status != TaskState.FINISHED
|
146
|
+
]
|
147
|
+
return self._pending_tasks
|
148
|
+
|
149
|
+
@property
|
150
|
+
def analyses(self) -> Dict[str, KartonAnalysis]:
|
151
|
+
if self._analyses is None:
|
152
|
+
# Tasks grouped by root_uid
|
153
|
+
tasks_per_analysis = defaultdict(list)
|
154
|
+
|
155
|
+
for task in self.pending_tasks:
|
156
|
+
tasks_per_analysis[task.root_uid].append(task)
|
157
|
+
|
158
|
+
self._analyses = {
|
159
|
+
root_uid: KartonAnalysis(root_uid=root_uid, tasks=tasks, state=self)
|
160
|
+
for root_uid, tasks in tasks_per_analysis.items()
|
161
|
+
}
|
162
|
+
return self._analyses
|
163
|
+
|
164
|
+
@property
|
165
|
+
def queues(self) -> Dict[str, KartonQueue]:
|
166
|
+
if self._queues is None:
|
167
|
+
queues = get_queues_for_tasks(self.pending_tasks, self)
|
168
|
+
# Present registered queues without tasks
|
169
|
+
for bind_name, bind in self.binds.items():
|
170
|
+
if bind_name not in queues:
|
171
|
+
queues[bind_name] = KartonQueue(
|
172
|
+
bind=self.binds[bind_name], tasks=[], state=self
|
173
|
+
)
|
174
|
+
self._queues = queues
|
175
|
+
return self._queues
|
176
|
+
|
177
|
+
def get_analysis(self, root_uid: str) -> KartonAnalysis:
|
178
|
+
return KartonAnalysis(
|
179
|
+
root_uid=root_uid,
|
180
|
+
tasks=list(
|
181
|
+
self.backend.iter_task_tree(
|
182
|
+
root_uid, parse_resources=self.parse_resources
|
147
183
|
)
|
148
|
-
|
184
|
+
),
|
185
|
+
state=self,
|
186
|
+
)
|
karton/core/karton.py
CHANGED
@@ -12,6 +12,7 @@ from .__version__ import __version__
|
|
12
12
|
from .backend import KartonBackend, KartonBind, KartonMetrics
|
13
13
|
from .base import KartonBase, KartonServiceBase
|
14
14
|
from .config import Config
|
15
|
+
from .exceptions import TaskTimeoutError
|
15
16
|
from .resource import LocalResource
|
16
17
|
from .task import Task, TaskState
|
17
18
|
from .utils import timeout
|
@@ -129,7 +130,10 @@ class Consumer(KartonServiceBase):
|
|
129
130
|
self.current_task: Optional[Task] = None
|
130
131
|
self._pre_hooks: List[Tuple[Optional[str], Callable[[Task], None]]] = []
|
131
132
|
self._post_hooks: List[
|
132
|
-
Tuple[
|
133
|
+
Tuple[
|
134
|
+
Optional[str],
|
135
|
+
Callable[[Task, Optional[BaseException]], None],
|
136
|
+
]
|
133
137
|
] = []
|
134
138
|
|
135
139
|
@abc.abstractmethod
|
@@ -179,14 +183,14 @@ class Consumer(KartonServiceBase):
|
|
179
183
|
self.process(self.current_task)
|
180
184
|
else:
|
181
185
|
self.process(self.current_task)
|
182
|
-
except Exception as exc:
|
186
|
+
except (Exception, TaskTimeoutError) as exc:
|
183
187
|
saved_exception = exc
|
184
188
|
raise
|
185
189
|
finally:
|
186
190
|
self._run_post_hooks(saved_exception)
|
187
191
|
|
188
192
|
self.log.info("Task done - %s", self.current_task.uid)
|
189
|
-
except Exception:
|
193
|
+
except (Exception, TaskTimeoutError):
|
190
194
|
exc_info = sys.exc_info()
|
191
195
|
exception_str = traceback.format_exception(*exc_info)
|
192
196
|
|
@@ -260,7 +264,7 @@ class Consumer(KartonServiceBase):
|
|
260
264
|
|
261
265
|
def add_post_hook(
|
262
266
|
self,
|
263
|
-
callback: Callable[[Task, Optional[
|
267
|
+
callback: Callable[[Task, Optional[BaseException]], None],
|
264
268
|
name: Optional[str] = None,
|
265
269
|
) -> None:
|
266
270
|
"""
|
@@ -289,7 +293,7 @@ class Consumer(KartonServiceBase):
|
|
289
293
|
else:
|
290
294
|
self.log.exception("Pre-hook failed")
|
291
295
|
|
292
|
-
def _run_post_hooks(self, exception: Optional[
|
296
|
+
def _run_post_hooks(self, exception: Optional[BaseException]) -> None:
|
293
297
|
"""
|
294
298
|
Run registered postprocessing hooks
|
295
299
|
|
@@ -431,7 +435,7 @@ class Karton(Consumer, Producer):
|
|
431
435
|
self._send_signaling_status_task("task_begin")
|
432
436
|
|
433
437
|
def _send_signaling_status_task_end(
|
434
|
-
self, task: Task, ex: Optional[
|
438
|
+
self, task: Task, ex: Optional[BaseException]
|
435
439
|
) -> None:
|
436
440
|
"""Send a begin status signaling task.
|
437
441
|
|
karton/core/task.py
CHANGED
@@ -106,13 +106,18 @@ class Task(object):
|
|
106
106
|
raise ValueError("Persistent headers should be an instance of a dict")
|
107
107
|
|
108
108
|
if uid is None:
|
109
|
-
|
109
|
+
task_uid = str(uuid.uuid4())
|
110
|
+
if root_uid is None:
|
111
|
+
self.root_uid = task_uid
|
112
|
+
else:
|
113
|
+
self.root_uid = root_uid
|
114
|
+
# New-style UID format introduced in v5.4.0
|
115
|
+
# {12345678-1234-1234-1234-12345678abcd}:12345678-1234-1234-1234-12345678abcd
|
116
|
+
self.uid = f"{{{self.root_uid}}}:{task_uid}"
|
110
117
|
else:
|
111
118
|
self.uid = uid
|
112
|
-
|
113
|
-
|
114
|
-
self.root_uid = self.uid
|
115
|
-
else:
|
119
|
+
if root_uid is None:
|
120
|
+
raise ValueError("root_uid cannot be None when uid is not None")
|
116
121
|
self.root_uid = root_uid
|
117
122
|
|
118
123
|
self.orig_uid = orig_uid
|
@@ -137,6 +142,21 @@ class Task(object):
|
|
137
142
|
def receiver(self) -> Optional[str]:
|
138
143
|
return self.headers.get("receiver")
|
139
144
|
|
145
|
+
@property
|
146
|
+
def task_uid(self) -> str:
|
147
|
+
return self.fquid_to_uid(self.uid)
|
148
|
+
|
149
|
+
@staticmethod
|
150
|
+
def fquid_to_uid(fquid: str) -> str:
|
151
|
+
"""
|
152
|
+
Gets task uid from fully-qualified fquid ({root_uid}:task_uid)
|
153
|
+
|
154
|
+
:return: Task uid
|
155
|
+
"""
|
156
|
+
if ":" not in fquid:
|
157
|
+
return fquid
|
158
|
+
return fquid.split(":")[-1]
|
159
|
+
|
140
160
|
def fork_task(self) -> "Task":
|
141
161
|
"""
|
142
162
|
Fork task to transfer single task to many queues (but use different UID).
|
@@ -212,38 +232,65 @@ class Task(object):
|
|
212
232
|
:meta private:
|
213
233
|
"""
|
214
234
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
235
|
+
def test_filter(headers: Dict[str, Any], filter: Dict[str, Any]) -> int:
|
236
|
+
"""
|
237
|
+
Filter match follows AND logic, but it's non-boolean because filters may be
|
238
|
+
negated (task:!platform).
|
239
|
+
|
240
|
+
Result values are as follows:
|
241
|
+
- 1 - positive match, no mismatched values in headers
|
242
|
+
(all matched)
|
243
|
+
- 0 - no match, found value that doesn't match to the filter
|
244
|
+
(some are not matched)
|
245
|
+
- -1 - negative match, found value that matches negated filter value
|
246
|
+
(all matched but found negative matches)
|
247
|
+
"""
|
248
|
+
matches = 1
|
249
|
+
for filter_key, filter_value in filter.items():
|
250
|
+
# Coerce filter value to string
|
221
251
|
filter_value_str = str(filter_value)
|
222
|
-
header_value_str = str(header_value)
|
223
|
-
|
224
252
|
negated = False
|
225
253
|
if filter_value_str.startswith("!"):
|
226
254
|
negated = True
|
227
255
|
filter_value_str = filter_value_str[1:]
|
228
256
|
|
229
|
-
|
230
|
-
|
231
|
-
|
257
|
+
# If expected key doesn't exist in headers
|
258
|
+
if filter_key not in headers:
|
259
|
+
# Negated filter ignores non-existent values
|
260
|
+
if negated:
|
261
|
+
continue
|
262
|
+
# But positive filter doesn't
|
263
|
+
return 0
|
232
264
|
|
265
|
+
# Coerce header value to string
|
266
|
+
header_value_str = str(headers[filter_key])
|
233
267
|
# fnmatch is great for handling simple wildcard patterns (?, *, [abc])
|
234
268
|
match = fnmatch.fnmatchcase(header_value_str, filter_value_str)
|
235
|
-
#
|
236
|
-
# since no matter the other filters
|
269
|
+
# If matches, but it's negated: it's negative match
|
237
270
|
if match and negated:
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
271
|
+
matches = -1
|
272
|
+
# If doesn't match but filter is not negated: it's not a match
|
273
|
+
if not match and not negated:
|
274
|
+
return 0
|
275
|
+
# If there are no mismatched values: filter is matched
|
276
|
+
return matches
|
277
|
+
|
278
|
+
# List of filter matches follow OR logic, but -1 is special
|
279
|
+
# If there is any -1, result is False
|
280
|
+
# (any matched, but it's negative match)
|
281
|
+
# If there is any 1, but no -1's: result is True
|
282
|
+
# (any matched, no negative match)
|
283
|
+
# If there are only 0's: result is False
|
284
|
+
# (none matched)
|
285
|
+
matches = False
|
286
|
+
for task_filter in filters:
|
287
|
+
match_result = test_filter(self.headers, task_filter)
|
288
|
+
if match_result == -1:
|
289
|
+
# Any negative match results in False
|
290
|
+
return False
|
291
|
+
if match_result == 1:
|
292
|
+
# Any positive match but without negative matches results in True
|
293
|
+
matches = True
|
247
294
|
return matches
|
248
295
|
|
249
296
|
def set_task_parent(self, parent: "Task"):
|
@@ -407,9 +454,7 @@ class Task(object):
|
|
407
454
|
process. This flag is used mainly for multiple task processing e.g.
|
408
455
|
filtering based on status.
|
409
456
|
When resource deserialization is turned off, Task.unserialize will try
|
410
|
-
to use faster 3rd-party JSON parser (orjson)
|
411
|
-
added as a required dependency but can speed up things if you need to check
|
412
|
-
status of multiple tasks at once.
|
457
|
+
to use faster 3rd-party JSON parser (orjson).
|
413
458
|
:return: Unserialized Task object
|
414
459
|
|
415
460
|
:meta private:
|
@@ -430,11 +475,7 @@ class Task(object):
|
|
430
475
|
if parse_resources:
|
431
476
|
task_data = json.loads(data, object_hook=unserialize_resources)
|
432
477
|
else:
|
433
|
-
|
434
|
-
task_data = orjson.loads(data)
|
435
|
-
except orjson.JSONDecodeError:
|
436
|
-
# fallback, in case orjson raises exception during loading
|
437
|
-
task_data = json.loads(data, object_hook=unserialize_resources)
|
478
|
+
task_data = orjson.loads(data)
|
438
479
|
|
439
480
|
# Compatibility with Karton <5.2.0
|
440
481
|
headers_persistent_fallback = task_data["payload_persistent"].get(
|
karton/system/system.py
CHANGED
@@ -103,7 +103,7 @@ class SystemService(KartonServiceBase):
|
|
103
103
|
and current_time > task.last_update + self.task_dispatched_timeout
|
104
104
|
):
|
105
105
|
to_delete.append(task)
|
106
|
-
self.log.
|
106
|
+
self.log.error(
|
107
107
|
"Task %s is in Dispatched state more than %d seconds. "
|
108
108
|
"Killed. (origin: %s)",
|
109
109
|
task.uid,
|
@@ -116,7 +116,7 @@ class SystemService(KartonServiceBase):
|
|
116
116
|
and current_time > task.last_update + self.task_started_timeout
|
117
117
|
):
|
118
118
|
to_delete.append(task)
|
119
|
-
self.log.
|
119
|
+
self.log.error(
|
120
120
|
"Task %s is in Started state more than %d seconds. "
|
121
121
|
"Killed. (receiver: %s)",
|
122
122
|
task.uid,
|
@@ -166,7 +166,7 @@ class SystemService(KartonServiceBase):
|
|
166
166
|
|
167
167
|
def route_task(self, task: Task, binds: List[KartonBind]) -> None:
|
168
168
|
# Performs routing of task
|
169
|
-
self.log.info("[%s] Processing task %s", task.root_uid, task.
|
169
|
+
self.log.info("[%s] Processing task %s", task.root_uid, task.task_uid)
|
170
170
|
# store the producer-task relationship in redis for task tracking
|
171
171
|
self.backend.log_identity_output(
|
172
172
|
task.headers.get("origin", "unknown"), task.headers
|
@@ -1,26 +1,26 @@
|
|
1
|
-
karton_core-5.
|
1
|
+
karton_core-5.4.0-nspkg.pth,sha256=vHa-jm6pBTeInFrmnsHMg9AOeD88czzQy-6QCFbpRcM,539
|
2
2
|
karton/core/__init__.py,sha256=QuT0BWZyp799eY90tK3H1OD2hwuusqMJq8vQwpB3kG4,337
|
3
|
-
karton/core/__version__.py,sha256=
|
4
|
-
karton/core/backend.py,sha256
|
3
|
+
karton/core/__version__.py,sha256=xjYaBGUFGg0kGZj_WhuoFyPD8NILPsr79SaMwmYQGSg,22
|
4
|
+
karton/core/backend.py,sha256=-sQG7utnaWLJOEcafeSwEDLnkflPqtSCwg_mn_nnFhg,36727
|
5
5
|
karton/core/base.py,sha256=C6Lco3E0XCsxvEjeVOLR9fxh_IWJ1vjC9BqUYsQyewE,8083
|
6
6
|
karton/core/config.py,sha256=7oKchitq6pWzPuXRfjBXqVT_BgGIz2p-CDo1RGaNJQg,8118
|
7
7
|
karton/core/exceptions.py,sha256=8i9WVzi4PinNlX10Cb-lQQC35Hl-JB5R_UKXa9AUKoQ,153
|
8
|
-
karton/core/inspect.py,sha256=
|
9
|
-
karton/core/karton.py,sha256
|
8
|
+
karton/core/inspect.py,sha256=aIJQEOEkD5q2xLlV8nhxY5qL5zqcnprP-2DdP6ecKlE,6150
|
9
|
+
karton/core/karton.py,sha256=9SOAviG42kSsPqc3EuaHzWtA_KywMtc01hmU6FaJpHo,15007
|
10
10
|
karton/core/logger.py,sha256=J3XAyG88U0cwYC9zR6E3QD1uJenrQh7zS9-HgxhqeAs,2040
|
11
11
|
karton/core/main.py,sha256=ir1-dhn3vbwfh2YHiM6ZYfRBbjwLvJSz0d8tuK1mb_4,8310
|
12
12
|
karton/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
karton/core/resource.py,sha256=tA3y_38H9HVKIrCeAU70zHUkQUv0BuCQWMC470JLxxc,20321
|
14
|
-
karton/core/task.py,sha256=
|
14
|
+
karton/core/task.py,sha256=diwg8uUl57NEYNRjT1l5CPiNw3EQcU11BnrLul33fx0,21350
|
15
15
|
karton/core/test.py,sha256=tms-YM7sUKQDHN0vm2_W7DIvHnO_ld_VPsWHnsbKSfk,9102
|
16
16
|
karton/core/utils.py,sha256=sEVqGdVPyYswWuVn8wYXBQmln8Az826N_2HgC__pmW8,4090
|
17
17
|
karton/system/__init__.py,sha256=JF51OqRU_Y4c0unOulvmv1KzSHSq4ZpXU8ZsH4nefRM,63
|
18
18
|
karton/system/__main__.py,sha256=QJkwIlSwaPRdzwKlNmCAL41HtDAa73db9MZKWmOfxGM,56
|
19
|
-
karton/system/system.py,sha256=
|
20
|
-
karton_core-5.
|
21
|
-
karton_core-5.
|
22
|
-
karton_core-5.
|
23
|
-
karton_core-5.
|
24
|
-
karton_core-5.
|
25
|
-
karton_core-5.
|
26
|
-
karton_core-5.
|
19
|
+
karton/system/system.py,sha256=yF_d71a8w7JYA7IXUt63d5_QBH6x1QplB-xcrzQTXL4,13792
|
20
|
+
karton_core-5.4.0.dist-info/LICENSE,sha256=o8h7hYhn7BJC_-DmrfqWwLjaR_Gbe0TZOOQJuN2ca3I,1519
|
21
|
+
karton_core-5.4.0.dist-info/METADATA,sha256=kopeYFCI9EoFQbc7J7woZWjI_5egy29-lYUW7UzEQ2I,6847
|
22
|
+
karton_core-5.4.0.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
23
|
+
karton_core-5.4.0.dist-info/entry_points.txt,sha256=FJj5EZuvFP0LkagjX_dLbRGBUnuLjgBiSyiFfq4c86U,99
|
24
|
+
karton_core-5.4.0.dist-info/namespace_packages.txt,sha256=X8SslCPsqXDCnGZqrYYolzT3xPzJMq1r-ZQSc0jfAEA,7
|
25
|
+
karton_core-5.4.0.dist-info/top_level.txt,sha256=X8SslCPsqXDCnGZqrYYolzT3xPzJMq1r-ZQSc0jfAEA,7
|
26
|
+
karton_core-5.4.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|