django-spire 0.23.9__py3-none-any.whl → 0.23.10__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.
- django_spire/consts.py +1 -1
- django_spire/contrib/progress/__init__.py +7 -13
- django_spire/contrib/progress/enums.py +2 -1
- django_spire/contrib/progress/session.py +283 -0
- django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +35 -57
- django_spire/contrib/progress/tasks.py +67 -0
- django_spire/contrib/progress/templates/django_spire/contrib/progress/card/card.html +25 -24
- django_spire/contrib/progress/templates/django_spire/contrib/progress/modal/content.html +58 -67
- {django_spire-0.23.9.dist-info → django_spire-0.23.10.dist-info}/METADATA +1 -1
- {django_spire-0.23.9.dist-info → django_spire-0.23.10.dist-info}/RECORD +13 -16
- django_spire/contrib/progress/mixins.py +0 -36
- django_spire/contrib/progress/runner.py +0 -140
- django_spire/contrib/progress/states.py +0 -64
- django_spire/contrib/progress/task.py +0 -40
- django_spire/contrib/progress/tracker.py +0 -245
- {django_spire-0.23.9.dist-info → django_spire-0.23.10.dist-info}/WHEEL +0 -0
- {django_spire-0.23.9.dist-info → django_spire-0.23.10.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.23.9.dist-info → django_spire-0.23.10.dist-info}/top_level.txt +0 -0
|
@@ -10,115 +10,106 @@
|
|
|
10
10
|
|
|
11
11
|
{% block modal_content_content %}
|
|
12
12
|
<div
|
|
13
|
-
data-stream-url="{% block progress_stream_url %}{% endblock %}"
|
|
14
|
-
data-redirect-url="{% block progress_redirect_url %}{% endblock %}"
|
|
15
13
|
data-redirect-delay="{% block progress_redirect_delay %}1000{% endblock %}"
|
|
14
|
+
data-redirect-url="{% block progress_redirect_url %}{% endblock %}"
|
|
15
|
+
data-stream-url="{% block progress_stream_url %}{% endblock %}"
|
|
16
16
|
x-data="{
|
|
17
|
-
tasks: {},
|
|
18
|
-
task_order: [],
|
|
19
|
-
overall_progress: 0,
|
|
20
|
-
message: 'Initializing...',
|
|
21
17
|
has_error: false,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
let stream_url = this.$el.dataset.streamUrl;
|
|
26
|
-
let redirect_url = this.$el.dataset.redirectUrl;
|
|
27
|
-
let redirect_delay = parseInt(this.$el.dataset.redirectDelay);
|
|
18
|
+
message: 'Initializing...',
|
|
19
|
+
overall_percent: 0,
|
|
20
|
+
tasks: {},
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (data.task_order) {
|
|
34
|
-
this.task_order = data.task_order;
|
|
35
|
-
}
|
|
36
|
-
if (data.tasks) {
|
|
37
|
-
this.tasks = data.tasks;
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
on_complete: (data) => {
|
|
41
|
-
setTimeout(() => {
|
|
42
|
-
close_modal();
|
|
43
|
-
}, 500);
|
|
22
|
+
async init() {
|
|
23
|
+
let stream = new ProgressStream(this.$el.dataset.streamUrl, {
|
|
24
|
+
on_complete: () => {
|
|
25
|
+
setTimeout(() => close_modal(), 500);
|
|
44
26
|
},
|
|
45
27
|
on_error: (data) => {
|
|
46
28
|
this.has_error = true;
|
|
47
|
-
this.message = data.message;
|
|
29
|
+
this.message = data.message || 'An error occurred';
|
|
30
|
+
},
|
|
31
|
+
on_update: (data) => {
|
|
32
|
+
this.overall_percent = data.overall_percent;
|
|
33
|
+
this.tasks = data.tasks;
|
|
48
34
|
},
|
|
49
|
-
|
|
50
|
-
|
|
35
|
+
redirect_delay: parseInt(this.$el.dataset.redirectDelay),
|
|
36
|
+
redirect_url: this.$el.dataset.redirectUrl,
|
|
51
37
|
});
|
|
52
|
-
this.stream.start();
|
|
53
|
-
},
|
|
54
38
|
|
|
55
|
-
|
|
56
|
-
return name.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
39
|
+
await stream.start();
|
|
57
40
|
},
|
|
58
41
|
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
if (
|
|
62
|
-
if (
|
|
63
|
-
return '
|
|
42
|
+
get_status_icon(status) {
|
|
43
|
+
if (status === 'complete') return 'bi-check-circle-fill text-success';
|
|
44
|
+
if (status === 'error') return 'bi-x-circle-fill text-danger';
|
|
45
|
+
if (status === 'running') return 'bi-arrow-repeat text-primary spin';
|
|
46
|
+
return 'bi-circle text-secondary';
|
|
64
47
|
}
|
|
65
48
|
}"
|
|
66
49
|
>
|
|
67
50
|
<div class="row g-3">
|
|
68
51
|
<div class="col-12" x-show="!has_error">
|
|
69
|
-
<template x-for="
|
|
70
|
-
<div class="mb-3"
|
|
71
|
-
<div class="d-flex
|
|
72
|
-
<
|
|
73
|
-
<span class="
|
|
52
|
+
<template x-for="(task, task_id) in tasks" :key="task_id">
|
|
53
|
+
<div class="mb-3">
|
|
54
|
+
<div class="d-flex align-items-center mb-1">
|
|
55
|
+
<i class="bi me-2" :class="get_status_icon(task.status)"></i>
|
|
56
|
+
<span class="fw-medium" x-text="task.name"></span>
|
|
57
|
+
<span class="ms-auto fs-7" x-text="task.percent + '%'"></span>
|
|
74
58
|
</div>
|
|
75
|
-
<div class="progress" style="height:
|
|
59
|
+
<div class="progress" style="height: 4px;">
|
|
76
60
|
<div
|
|
77
61
|
class="progress-bar"
|
|
78
|
-
:class="
|
|
62
|
+
:class="task.status === 'error' ? 'bg-danger' : 'bg-success'"
|
|
79
63
|
role="progressbar"
|
|
80
|
-
:style="'width: ' +
|
|
64
|
+
:style="'width: ' + task.percent + '%'"
|
|
81
65
|
></div>
|
|
82
66
|
</div>
|
|
67
|
+
<small class="text-muted" x-text="task.message"></small>
|
|
83
68
|
</div>
|
|
84
69
|
</template>
|
|
85
70
|
|
|
86
71
|
<div class="mt-4 pt-3 border-top">
|
|
87
72
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
88
|
-
<span class="
|
|
89
|
-
<span
|
|
73
|
+
<span class="fw-semibold">Overall Progress</span>
|
|
74
|
+
<span x-text="overall_percent + '%'"></span>
|
|
90
75
|
</div>
|
|
91
76
|
<div class="progress" style="height: 8px;">
|
|
92
77
|
<div
|
|
93
|
-
class="progress-bar bg-
|
|
78
|
+
class="progress-bar bg-primary"
|
|
94
79
|
role="progressbar"
|
|
95
|
-
:style="'width: ' +
|
|
80
|
+
:style="'width: ' + overall_percent + '%'"
|
|
96
81
|
></div>
|
|
97
82
|
</div>
|
|
98
83
|
</div>
|
|
99
|
-
</div>
|
|
100
84
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
{% block progress_wait_message %}Processing your request. This may take a moment.{% endblock %}
|
|
85
|
+
<p class="text-app-secondary mb-0 fs-7 text-center mt-3">
|
|
86
|
+
{% block progress_wait_message %}This may take a moment. Please do not close this window.{% endblock %}
|
|
104
87
|
</p>
|
|
105
88
|
</div>
|
|
106
89
|
|
|
107
90
|
<div class="col-12" x-show="has_error" x-cloak>
|
|
108
|
-
<div class="
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
{%
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
</span>
|
|
119
|
-
</div>
|
|
91
|
+
<div class="d-flex align-items-center justify-content-center py-4">
|
|
92
|
+
<i class="bi bi-x-circle-fill text-danger me-3 fs-4"></i>
|
|
93
|
+
<span class="fs-5" x-text="message"></span>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="text-center">
|
|
96
|
+
<span @click="close_modal()">
|
|
97
|
+
{% block progress_error_button %}
|
|
98
|
+
{% include 'django_spire/button/primary_button.html' with button_text='Close' %}
|
|
99
|
+
{% endblock %}
|
|
100
|
+
</span>
|
|
120
101
|
</div>
|
|
121
102
|
</div>
|
|
122
103
|
</div>
|
|
123
104
|
</div>
|
|
105
|
+
|
|
106
|
+
<style>
|
|
107
|
+
.spin {
|
|
108
|
+
animation: spin 1s linear infinite;
|
|
109
|
+
}
|
|
110
|
+
@keyframes spin {
|
|
111
|
+
from { transform: rotate(0deg); }
|
|
112
|
+
to { transform: rotate(360deg); }
|
|
113
|
+
}
|
|
114
|
+
</style>
|
|
124
115
|
{% endblock %}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-spire
|
|
3
|
-
Version: 0.23.
|
|
3
|
+
Version: 0.23.10
|
|
4
4
|
Summary: A project for Django Spire
|
|
5
5
|
Author-email: Brayden Carlson <braydenc@stratusadv.com>, Nathan Johnson <nathanj@stratusadv.com>
|
|
6
6
|
License: Copyright (c) 2025 Stratus Advanced Technologies and Contributors.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
django_spire/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
django_spire/conf.py,sha256=3oUB1mtgHRjvbJsxfQWG5uL1KUP9uGig3zdP2dZphe8,942
|
|
3
|
-
django_spire/consts.py,sha256=
|
|
3
|
+
django_spire/consts.py,sha256=d5_qKitqKzGvVZucpkte-GZyZj9s2j8C0KuzkalsyVQ,172
|
|
4
4
|
django_spire/exceptions.py,sha256=M7buFvm-K4lK09pH5fVcZ-MxsDIzdpEJBF33Xss5bSw,289
|
|
5
5
|
django_spire/settings.py,sha256=0xImrKpF7VW4wc9jLGO07e2ev6D4-i_TREgpTepLp3I,1042
|
|
6
6
|
django_spire/urls.py,sha256=wQx6R-nXx69MeOF-WmDxcEUM5WmUHGplbY5uZ_HnDp8,703
|
|
@@ -397,17 +397,14 @@ django_spire/contrib/performance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|
|
397
397
|
django_spire/contrib/performance/decorators.py,sha256=vCENOqcmXoEvi1Y3Uw55J21zB-ZvpWk7rRo2jqyg8pc,731
|
|
398
398
|
django_spire/contrib/performance/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
399
399
|
django_spire/contrib/performance/tests/test_performance.py,sha256=wVNzrUEG0bTMcvJrqkSWkAE27LSyJMxrRZRd04ZKDQM,3029
|
|
400
|
-
django_spire/contrib/progress/__init__.py,sha256=
|
|
401
|
-
django_spire/contrib/progress/enums.py,sha256=
|
|
402
|
-
django_spire/contrib/progress/
|
|
403
|
-
django_spire/contrib/progress/
|
|
404
|
-
django_spire/contrib/progress/states.py,sha256=V7JU_nzpZN77TQFW0S51Gjil8xn8CKLKkAWDOpqzA8k,1908
|
|
405
|
-
django_spire/contrib/progress/task.py,sha256=4pMbzOsjRRNqPKNohh08D39DRFpC3QomTCH5IdqQS0A,780
|
|
406
|
-
django_spire/contrib/progress/tracker.py,sha256=xjGlEyKe5aiKW_KC8NKZ8F0BjQdFpLUqHz5Gr_C7jws,6490
|
|
400
|
+
django_spire/contrib/progress/__init__.py,sha256=5a7LTmUyw_cfO-3_QuN5M6zAhrCNs7MjEypL13qhoQA,345
|
|
401
|
+
django_spire/contrib/progress/enums.py,sha256=Rexk804FhmVQou7UOKSOCnjaZrSDZdniV_MWLh-kGxI,218
|
|
402
|
+
django_spire/contrib/progress/session.py,sha256=Oo1QS8f7aTN7K_Y9bwXZHo6otZQkRSp6V_6c0stvYcI,8825
|
|
403
|
+
django_spire/contrib/progress/tasks.py,sha256=afC4vhWzvuRq6-P80LdB-BmDGD5kADtgYGM2SaKz8Ao,1625
|
|
407
404
|
django_spire/contrib/progress/static/django_spire/css/contrib/progress/progress.css,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
408
|
-
django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js,sha256=
|
|
409
|
-
django_spire/contrib/progress/templates/django_spire/contrib/progress/card/card.html,sha256=
|
|
410
|
-
django_spire/contrib/progress/templates/django_spire/contrib/progress/modal/content.html,sha256=
|
|
405
|
+
django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js,sha256=vhjwLWTqkjcuK7j_z9ndCGdWfBFmRAM4MvF8zpEKJL0,1730
|
|
406
|
+
django_spire/contrib/progress/templates/django_spire/contrib/progress/card/card.html,sha256=UB5PpaZzaSihiE6BnThYrM21qVrf1iT1fF-TbtkoZrc,3389
|
|
407
|
+
django_spire/contrib/progress/templates/django_spire/contrib/progress/modal/content.html,sha256=613hvePQFurk4Q0Id8NuQz9Umbh35eJSOR-IQipo_o4,4857
|
|
411
408
|
django_spire/contrib/queryset/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
412
409
|
django_spire/contrib/queryset/enums.py,sha256=dkzI464Q53huqtNGfnM-biuKpHUQU78WwuWvt3Pm8mI,145
|
|
413
410
|
django_spire/contrib/queryset/filter_tools.py,sha256=V_s6pG2uyqvoTNtBXXXNKXE69ipwexxWAR-X30H1-XI,1129
|
|
@@ -1372,8 +1369,8 @@ django_spire/theme/urls/page_urls.py,sha256=Oak3x_xwQEb01NKdrsB1nk6yPaOEnheuSG1m
|
|
|
1372
1369
|
django_spire/theme/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1373
1370
|
django_spire/theme/views/json_views.py,sha256=PWwVTaty0BVGbj65L5cxex6JNhc-xVAI_rEYjbJWqEM,1893
|
|
1374
1371
|
django_spire/theme/views/page_views.py,sha256=WenjOa6Welpu3IMolY56ZwBjy4aK9hpbiMNuygjAl1A,3922
|
|
1375
|
-
django_spire-0.23.
|
|
1376
|
-
django_spire-0.23.
|
|
1377
|
-
django_spire-0.23.
|
|
1378
|
-
django_spire-0.23.
|
|
1379
|
-
django_spire-0.23.
|
|
1372
|
+
django_spire-0.23.10.dist-info/licenses/LICENSE.md,sha256=ZAeCT76WvaoEZE9xPhihyWjTwH0wQZXQmyRsnV2VPFs,1091
|
|
1373
|
+
django_spire-0.23.10.dist-info/METADATA,sha256=tdZ2P4q_PlnHfvRd4XibSjAzFHyxjzKG9PDX3EjAVU4,5128
|
|
1374
|
+
django_spire-0.23.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
1375
|
+
django_spire-0.23.10.dist-info/top_level.txt,sha256=xf3QV1e--ONkVpgMDQE9iqjQ1Vg4--_6C8wmO-KxPHQ,13
|
|
1376
|
+
django_spire-0.23.10.dist-info/RECORD,,
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
|
-
from django_spire.contrib.progress.tracker import ProgressTracker
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
from typing import Any
|
|
9
|
-
|
|
10
|
-
from django_spire.contrib.progress.enums import ProgressStatus
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class ProgressTrackingMixin:
|
|
14
|
-
_tracker: ProgressTracker | None = None
|
|
15
|
-
|
|
16
|
-
def get_tracker_key(self) -> str:
|
|
17
|
-
raise NotImplementedError
|
|
18
|
-
|
|
19
|
-
@property
|
|
20
|
-
def tracker(self) -> ProgressTracker:
|
|
21
|
-
if self._tracker is None:
|
|
22
|
-
self._tracker = ProgressTracker(self.get_tracker_key())
|
|
23
|
-
|
|
24
|
-
return self._tracker
|
|
25
|
-
|
|
26
|
-
def progress_error(self, message: str) -> None:
|
|
27
|
-
self.tracker.error(message)
|
|
28
|
-
|
|
29
|
-
def update_progress(
|
|
30
|
-
self,
|
|
31
|
-
status: ProgressStatus,
|
|
32
|
-
message: str,
|
|
33
|
-
progress: int,
|
|
34
|
-
**kwargs: Any
|
|
35
|
-
) -> None:
|
|
36
|
-
self.tracker.update(status, message, progress, **kwargs)
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import math
|
|
4
|
-
import random
|
|
5
|
-
import threading
|
|
6
|
-
import time
|
|
7
|
-
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
9
|
-
|
|
10
|
-
from django_spire.contrib.progress.enums import ProgressStatus
|
|
11
|
-
from django_spire.contrib.progress.task import ProgressMessages, Task, TaskResult
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from typing import Any, Callable
|
|
15
|
-
|
|
16
|
-
from django_spire.contrib.progress.tracker import ProgressTracker
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class TaskProgressUpdater:
|
|
20
|
-
def __init__(self, tracker: ProgressTracker, task: Task) -> None:
|
|
21
|
-
self._messages = ProgressMessages()
|
|
22
|
-
self._task = task
|
|
23
|
-
self._tracker = tracker
|
|
24
|
-
|
|
25
|
-
def complete(self, message: str | None = None) -> None:
|
|
26
|
-
self._tracker._update_task(
|
|
27
|
-
self._task.name,
|
|
28
|
-
ProgressStatus.COMPLETE,
|
|
29
|
-
self._format(message or self._messages.complete),
|
|
30
|
-
100
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
def error(self, message: str) -> None:
|
|
34
|
-
self._tracker._update_task(
|
|
35
|
-
self._task.name,
|
|
36
|
-
ProgressStatus.ERROR,
|
|
37
|
-
self._format(message),
|
|
38
|
-
0
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
def start(self) -> None:
|
|
42
|
-
self._tracker._update_task(
|
|
43
|
-
self._task.name,
|
|
44
|
-
ProgressStatus.PROCESSING,
|
|
45
|
-
self._format(self._messages.starting),
|
|
46
|
-
2
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
def update(self, message: str, progress: int) -> None:
|
|
50
|
-
self._tracker._update_task(
|
|
51
|
-
self._task.name,
|
|
52
|
-
ProgressStatus.PROCESSING,
|
|
53
|
-
self._format(message),
|
|
54
|
-
progress
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
def _format(self, message: str) -> str:
|
|
58
|
-
return f'{self._task.label}: {message}'
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class ProgressSimulator:
|
|
62
|
-
def __init__(
|
|
63
|
-
self,
|
|
64
|
-
updater: TaskProgressUpdater,
|
|
65
|
-
max_progress: int = 90,
|
|
66
|
-
update_interval: float = 0.15
|
|
67
|
-
) -> None:
|
|
68
|
-
self._max_progress = max_progress
|
|
69
|
-
self._messages = ProgressMessages()
|
|
70
|
-
self._update_interval = update_interval
|
|
71
|
-
self._updater = updater
|
|
72
|
-
|
|
73
|
-
def run(self, stop_event: threading.Event) -> None:
|
|
74
|
-
start_time = time.time()
|
|
75
|
-
duration = random.uniform(8.0, 15.0)
|
|
76
|
-
|
|
77
|
-
while not stop_event.is_set():
|
|
78
|
-
elapsed = time.time() - start_time
|
|
79
|
-
t = min(elapsed / duration, 1.0)
|
|
80
|
-
|
|
81
|
-
progress = self._ease_out_expo(t) * self._max_progress
|
|
82
|
-
progress = min(int(progress), self._max_progress)
|
|
83
|
-
|
|
84
|
-
jitter = random.uniform(-1.5, 1.5) if progress < self._max_progress - 5 else 0
|
|
85
|
-
progress = max(2, min(int(progress + jitter), self._max_progress))
|
|
86
|
-
|
|
87
|
-
message = self._get_message_for_progress(progress)
|
|
88
|
-
self._updater.update(message, progress)
|
|
89
|
-
|
|
90
|
-
if progress >= self._max_progress:
|
|
91
|
-
break
|
|
92
|
-
|
|
93
|
-
time.sleep(self._update_interval + random.uniform(0, 0.1))
|
|
94
|
-
|
|
95
|
-
def _ease_out_expo(self, t: float) -> float:
|
|
96
|
-
if t >= 1.0:
|
|
97
|
-
return 1.0
|
|
98
|
-
|
|
99
|
-
return 1.0 - math.pow(2, -10 * t)
|
|
100
|
-
|
|
101
|
-
def _get_message_for_progress(self, progress: int) -> str:
|
|
102
|
-
steps = self._messages.steps
|
|
103
|
-
index = min(int(progress / (self._max_progress / len(steps))), len(steps) - 1)
|
|
104
|
-
|
|
105
|
-
return steps[index]
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
class TaskRunner:
|
|
109
|
-
def __init__(self, tracker: ProgressTracker, task: Task) -> None:
|
|
110
|
-
self._stop_event = threading.Event()
|
|
111
|
-
self._task = task
|
|
112
|
-
self._tracker = tracker
|
|
113
|
-
self._updater = TaskProgressUpdater(tracker, task)
|
|
114
|
-
|
|
115
|
-
def run_parallel(self, results: dict[str, TaskResult]) -> None:
|
|
116
|
-
self._execute(results, lambda: self._task.func())
|
|
117
|
-
|
|
118
|
-
def run_sequential(self, results: dict[str, TaskResult]) -> None:
|
|
119
|
-
unwrapped = {name: result.value for name, result in results.items()}
|
|
120
|
-
self._execute(results, lambda: self._task.func(unwrapped))
|
|
121
|
-
|
|
122
|
-
def stop(self) -> None:
|
|
123
|
-
self._stop_event.set()
|
|
124
|
-
|
|
125
|
-
def _execute(self, results: dict[str, TaskResult], executor: Callable[[], Any]) -> None:
|
|
126
|
-
simulator = ProgressSimulator(self._updater)
|
|
127
|
-
progress_thread = threading.Thread(target=simulator.run, args=(self._stop_event,))
|
|
128
|
-
progress_thread.start()
|
|
129
|
-
|
|
130
|
-
try:
|
|
131
|
-
self._updater.start()
|
|
132
|
-
value = executor()
|
|
133
|
-
results[self._task.name] = TaskResult(value=value)
|
|
134
|
-
self._updater.complete()
|
|
135
|
-
except BaseException as e:
|
|
136
|
-
results[self._task.name] = TaskResult(error=e)
|
|
137
|
-
self._updater.error(str(e))
|
|
138
|
-
finally:
|
|
139
|
-
self._stop_event.set()
|
|
140
|
-
progress_thread.join(timeout=1)
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
5
|
-
|
|
6
|
-
from django_spire.contrib.progress.enums import ProgressStatus
|
|
7
|
-
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from typing import Any
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@dataclass
|
|
13
|
-
class TaskState:
|
|
14
|
-
message: str = 'Waiting...'
|
|
15
|
-
progress: int = 0
|
|
16
|
-
status: ProgressStatus = ProgressStatus.PENDING
|
|
17
|
-
|
|
18
|
-
@classmethod
|
|
19
|
-
def from_dict(cls, data: dict[str, Any]) -> TaskState:
|
|
20
|
-
return cls(
|
|
21
|
-
message=data.get('message', 'Waiting...'),
|
|
22
|
-
progress=data.get('progress', 0),
|
|
23
|
-
status=ProgressStatus(data.get('step', ProgressStatus.PENDING)),
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
def to_dict(self) -> dict[str, Any]:
|
|
27
|
-
return {
|
|
28
|
-
'message': self.message,
|
|
29
|
-
'progress': self.progress,
|
|
30
|
-
'step': self.status.value,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@dataclass
|
|
35
|
-
class TrackerState:
|
|
36
|
-
message: str = 'Initializing...'
|
|
37
|
-
progress: int = 0
|
|
38
|
-
status: ProgressStatus = ProgressStatus.PENDING
|
|
39
|
-
task_order: list[str] = field(default_factory=list)
|
|
40
|
-
tasks: dict[str, TaskState] = field(default_factory=dict)
|
|
41
|
-
|
|
42
|
-
@classmethod
|
|
43
|
-
def from_dict(cls, data: dict[str, Any]) -> TrackerState:
|
|
44
|
-
tasks = {
|
|
45
|
-
name: TaskState.from_dict(task_data)
|
|
46
|
-
for name, task_data in data.get('tasks', {}).items()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return cls(
|
|
50
|
-
message=data.get('message', 'Initializing...'),
|
|
51
|
-
progress=data.get('progress', 0),
|
|
52
|
-
status=ProgressStatus(data.get('step', ProgressStatus.PENDING)),
|
|
53
|
-
task_order=data.get('task_order', []),
|
|
54
|
-
tasks=tasks,
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
def to_dict(self) -> dict[str, Any]:
|
|
58
|
-
return {
|
|
59
|
-
'message': self.message,
|
|
60
|
-
'progress': self.progress,
|
|
61
|
-
'step': self.status.value,
|
|
62
|
-
'task_order': self.task_order,
|
|
63
|
-
'tasks': {name: task.to_dict() for name, task in self.tasks.items()},
|
|
64
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
5
|
-
|
|
6
|
-
if TYPE_CHECKING:
|
|
7
|
-
from typing import Any, Callable
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@dataclass
|
|
11
|
-
class ProgressMessages:
|
|
12
|
-
complete: str = 'Complete'
|
|
13
|
-
error: str = 'Error'
|
|
14
|
-
starting: str = 'Starting...'
|
|
15
|
-
steps: list[str] = field(default_factory=lambda: [
|
|
16
|
-
'Initializing...',
|
|
17
|
-
'Processing...',
|
|
18
|
-
'Analyzing...',
|
|
19
|
-
'Working...',
|
|
20
|
-
'Almost there...',
|
|
21
|
-
'Finalizing...',
|
|
22
|
-
])
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@dataclass
|
|
26
|
-
class Task:
|
|
27
|
-
func: Callable
|
|
28
|
-
label: str
|
|
29
|
-
name: str
|
|
30
|
-
parallel: bool = False
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@dataclass
|
|
34
|
-
class TaskResult:
|
|
35
|
-
error: BaseException | None = None
|
|
36
|
-
value: Any = None
|
|
37
|
-
|
|
38
|
-
@property
|
|
39
|
-
def failed(self) -> bool:
|
|
40
|
-
return self.error is not None
|